Making an application based on user input

28/2/2018: Wolfram Cloud now supports (quite possibly since several years ago) cross origin requests, so that part of this answer can be ignored. Ajax may be used, just remember to set the API permissions to "public".

Support for the mechanism that makes the CDF browser plugin possible is being phased out, and the CDF player already does not work on the newest versions of Google Chrome. In light of this there is no better time than now to start mixing the Wolfram Language with HTML and Javascript.

The concerns of using Wolfram Language as a backend/server language is no different than when using some other server language. You always have to ask yourself whether you want to implement a certain functionality in the frontend or in the backend. Since Javascript is not very good at math there will probably be lots of time that even a Javascript expert would choose to use the Wolfram Language.

Getting started

In order to get started you need a good HTML/CSS framework and a good Javascript framework that contain all the things you need so you don't have to write them.

  • I recommend to start using Bootstrap for building your designs. The documentation is very nice and it has most of the things you'll need.
  • For Javascript I recommend jQuery which the most popular Javascript framework by far. The documentation is good, there are lots of tutorials online and there are many "plugins" available that provide additional functionality.

I will use this combination in my examples below.

How to deploy Wolfram Language

Wolfram Language provides CloudDeploy and APIFunction which combine to create a RESTful API, and this is how we will call on our Wolfram Language code.

Websites such as Mathematica.Stackexchange or New York Times rely heavily on backend scripts/RESTful APIs. They run Javascript in the frontend that call these scripts when it is appropriate to update the content that the user sees. The technique they use is called "AJAX". Unfortunately it is one thing to send an AJAX request to New York Times' server from newyorktimes.com and it's another thing to send an AJAX request to New York Times' server from your own website. Such "cross domain requests" have been prevented by browsers since the inception of AJAX, and browsers only recently started allowing them and only if the server respond with a specific "header". WRI's server does not respond with this header, which means that we cannot communicate with it using AJAX.

We will see later that this does not matter for images, because we don't need AJAX for those. But it matters when the result is data. Luckily there is a workaround known as "jsonp". It's not the normal type of AJAX, but it works like it. That's what we will be using instead. jQuery will take care of most of the work behind the scenes, but we need to modify our APIFunction as well. This auxiliary function will take care of that:

jsonpAPIFunction[args_, f_] := APIFunction[
  Append[args, "callback" -> "String"],
  StringJoin[#callback, "(", ToString@f[#], ")"] &,
  "String"
  ]

BMI counter

In order to create a BMI counter we need to create a form that lets the user input his height and his weight. Then we need to apply the BMI formula to these numbers and show the result. Since this is an exercise in how to do computations with Wolfram Language, not Javascript, the formula (weight/height^2) will be computed in the cloud. In fact this is the APIFunction:

CloudDeploy@jsonpAPIFunction[{
   "weight" -> "Number",
   "height" -> "Number"
   },
  N[#weight/#height^2] &]

The Javascript and HTML is appended to the end of the post, you will see that only very little code is needed. There are instructions there as well that explain how you can try this yourself. This animation shows how smooth it works, even though it asks for the BMI from a server instead of computing it locally:

BMI counter

Domain coloring

This example, which you can also try with the code at the end of the post, is meant to show how to handle APIs that return images. In order to build the example I took Simon Woods' domain coloring function from this question. You have to define his domainPlot function as well, after that you can use this APIFunction:

deployableDomainPlot[assoc_] := domainPlot@ToExpression@assoc["f"]
CloudDeploy@APIFunction[{
   "f" -> "String"
   },
  deployableDomainPlot,
  "PNG"
  ]

The result will look like this:

Domain coloring

HTML/CSS/Javascript Manipulate clone

This example is not included in the code at the end of the post, but I thought I might show the result anyway. It works the same way as the domain coloring example above but the interface is a bit more complicated. I made it with jQuery, jQuery UI and a jQuery tooltip plugin.

Example Manipulate

HTML/CSS/Javascript code

Here is the code you need to test the first two examples above. Note that you need to run the CloudDeploy code given for the examples above to generate your own API URLs. Then you have to put the URLs into the Javascript code, which is in the beginning of the document, in the <head> part of the document.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Wolfram Language API examples</title>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap-theme.min.css">

        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>

        <!--- Javascript to interface with the API goes here -->
        <script type="text/javascript">
        BMI_calculator_URL = "<YOUR API URL HERE>";
        domain_coloring_URL = "<YOUR API URL HERE>";

        $(document).ready(function() {
	  		// BMI calculator
		  	$("#height, #weight").change(function() {
			  	if($("#weight").val() > 0 && $("#height").val() > 0) {
			  	$.getJSON(
			  		BMI_calculator_URL+"?weight="+$("#weight").val()+"&height="+$("#height").val()+"&callback=?", 
			  		function(result){
				  		$("#result").text(result);
                    });
                }

                /*
                Note:
                Can't use Ajax like in this code here below because the header
                "Access-Control-Allow-Origin: *" is missing in the API response.
                $("#result").load(
			  		BMI_calculator_URL+"?weight="+$("#weight").val()+"&height="+$("#height").val()
                );
                */
            });

            // Domain coloring
            $(".btn").click(function(){
				$("#function").val($(this).val());
				$("#result2").html(
					"<img src=\""+domain_coloring_URL+"?f="+$(this).val()+"\" />"
                );
            });

            $("#function").change(function(){
				$("#result2").html(
					"<img src=\""+domain_coloring_URL+"?f="+$(this).val()+"\" />"
                );
            });
        });
        </script>
    </head>

    <body>

        <div class="container"><!--- Your code interface goes in this container --->

              <h1>Wolfram Language API examples</h1>

              <h2>BMI calculator</h2>
              <form class="form-horizontal">
                <div class="form-group">
                    <label for="height" class="col-md-2 control-label">Height</label>
                    <div class="col-md-10">
                        <input type="number" class="form-control" id="height" placeholder="Height">
                    </div>
                </div>
                <div class="form-group">
                    <label for="weight" class="col-md-2 control-label">Weight</label>
                    <div class="col-md-10">
                        <input type="number" class="form-control" id="weight" placeholder="Weight">
                    </div>
                </div>
              </form>
              <div class="panel panel-success">
                <div class="panel-heading">
                    Result
                </div>
                <div class="panel-body" id="result">
                    Undefined
                </div>
              </div>

          <h2>Domain coloring</h2>
          <div class="form-group btn-group">
              <button type="button" class="btn btn-default" value="Identity">Identity</button>
              <button type="button" class="btn btn-default" value="Sqrt">Sqrt</button>
              <button type="button" class="btn btn-default" value="Log">Log</button>
          </div>
          <div class="form-group">
              <input type="text" class="form-control" id="function" placeholder="Function">
          </div>

              <div class="panel panel-success">
                <div class="panel-heading">
                    Result
                </div>
                <div class="panel-body" id="result2">
                    Undefined
                </div>
              </div>

    </div><!--- /container --->

  </body>
</html>

Here is a basic example of how Mathematica code would be deployed.

Get["http://exampledata.wolfram.com/Collatz.m"];
?? Collatz

enter image description here

Collatz[5]

{5, 16, 8, 4, 2, 1}

You can look into APIFunction that you access directly if you want to build an interface with HTML/Javascript, or if you want a simple interface running on Wolfram's cloud you can use FormFunction. The return value of the function can be any expression, but more complex expressions may not work well in their cloud environment.

form = FormFunction[{"x" -> "Integer"}, Collatz[#x] &]
form[]

enter image description here

If I enter 5 and submit, the form disappears and is replaced with {5, 16, 8, 4, 2, 1}. To deploy this function to Wolfram's cloud, use

CloudDeploy[form]

which will return a CloudObject containing a link. You will need to use the Permissions option to control access. Following the link will take you to a page displaying a form, which will disappear and be replaced with the resolved expression after it has been submitted.

enter image description here