Incapture Technologies

Inside the Cloud

Incapture Technologies Blog

 

A Reflex Sandbox

Published:
December 10, 2014
Author:

If you’ve been reading this blog for a while you may be interested in having a little play with Rapture and perhaps Reflex. We’re in the process of setting up a sandbox environment for this and wanted to create some simple web pages for interacting with the environment. This post shows how I created a simple page that can be used to edit and run Reflex scripts. The real page will have a number of additions to help guide the novice user and will probably look a little different but the core aspects of the work will be the same.

In a previous post I talked about an architectural approach we’ve used have a Web page (Javascript) talk to a Rapture back end — we used Reflex scripts as the “processing” on the server, returning json formatted documents back to the client. The code on the page ends up being simple Ajax calls to Rapture.

Let’s start with a simple screen shot of the page in action:

Simple Reflex View

In this page we have a Reflex script being displayed on the left, the output on the right, some buttons and parameter information at the bottom left and that’s about it. The real “function” of the web page could therefore be broken down into:

  • Read a Reflex script from Rapture and display it.
  • Edit that script
  • Save that script
  • Run that script on Rapture, capturing the output
  • Display that output
  • Alter parameters passed to script execution

We also did not want to write everything from scratch so we leaned on the large body of open source and free software available in this area:

The code for the page consists of the html for the general layout and some javascript for the main interaction. A truncated form of the page is reproduced below – it’s a simple bootstrap container:

</pre>
<div class="container-fluid">
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">Reflex Script</div>
<div class="panel-body"></div>
<div class="panel-footer">
<div></div>
<div><button class="btn btn-info" id="saveScript">Save</button>
 <button class="btn btn-error" id="runScript">Run</button></div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">Output</div>
<div class="panel-body"></div>
<div class="panel-footer">Return value from execution:
<div class="alert alert-info" id="execOutput"></div>
</div>
</div>
</div>
</div>
</div>
<pre>

It’s worth pointing out at this point that I’m not a UI or UX expert and Incapture Technologies is hiring. After you’ve finished critiquing my little hack I’d love to hear from you if you want to join our team.

The layout above contains simple placeholders for the main functionality. The main work is done in the associated javascript code. I’ll split that up and explain each in turn.

The first snippet of code is about parsing the parameters passed to the web page. In this example the script to edit/run and parameters are passed in the url string. We of course rely on entitlements to protect the environment from callers manipulating those parameters – if you recall from an earlier post every api call in Rapture is protected by entitlements.

var QueryString = function() {
    var query_string = {};
    var query = window.location.search.substring(1);
    var vars = query.split("&");
    for ( var i = 0; i < vars.length; i++) {
        var pair = vars[i].split("=");
        if (typeof query_string[pair[0]] === "undefined") {
            query_string[pair[0]] = pair[1];
        } else if (typeof query_string[pair[0]] === "string") {
            var arr = [ query_string[pair[0]], pair[1] ];
            query_string[pair[0]] = arr;
        } else {
            query_string[pair[0]].push(pair[1]);
        }
    }
    return query_string;
}();

I found this code on the internet a while back. There are now better ways of doing this but for my little page this does the trick – after execution the variable QueryString will be a dictionary with the parameter names and values.

The first real work I needed to do was to load the Reflex script associated with the parameter “id” passed to the page. This snippet does that job:

function startup() {
    hider();
    setupParameters();

    var id = QueryString.id;
    $.ajax({
        url : "../web/getScript.rrfx?id=" + id,
        dataType : 'json',
        success : function(data) {
            editor.setValue(data.content, -1);
            editor.resize();
            editor.setTheme("ace/theme/monokai");
            editor.getSession().setMode("ace/mode/reflex");
            window.scrollTo(0, 0);
        }
    });

}

The variable editor is setup earlier on – it’s an instance of an “ACE” editor:

var editor = ace.edit("editor");
editor.setTheme("ace/theme/monokai");

Basically the loading code makes an Ajax call to the endpoint “web/getScript.rrfx” with the parameter “id” being the script to load. Rapture interprets this as “run the Reflex script web/getScript setting the variable web.id to the passed parameter”. I’d previously installed this Reflex script in the Rapture environment, it looks like this:

response = {};
script = #script.getScript(web.id);
response.content = script.script;
println(json(response));

The simple Reflex script loads the Reflex script (which contains information about the script as well as the script itself, and then returns just the script as the content of the response. The script prints out the json format of the map which is what is returned to the JavaScript page. If you look at the Javascript code above you see it setting the editor content to being “data.content”.

So after this process we will have a nice editor showing our Reflex script, which we can edit and save. The saving code is attached to the save button and it looks like this:

$("#saveScript").on("click", function() {
    var d = editor.getValue();
    var id = QueryString.id;
    var vals = {};

    vals['id'] = id
    vals['contents'] = encodeURIComponent(d);

    $.ajax({
        url : "../web/putScript.rrfx",
        dataType : 'json',
        type : 'POST',
        data : vals,

        success : function(data, textStatus, jqXHR) {
            $("#message").text("Saved document");
        }
    });
    return false;
});

This code should feel similar to the earlier one – it’s taking the content of the editor and the script to save – it’s packaging them up as a set of parameters to be sent to a Reflex “web/putScript” call. The contents of that Reflex script is reproduced below:

id = web.id;
contents = web.contents;
if #script.doesScriptExist(id) do
   rscript = #script.getScript(id);
   rscript.script = contents;
   #script.putScript(id, rscript);
else do
   #script.createScript(id, "REFLEX", "PROGRAM", contents);
end
println({ "status" : "ok"});

This code basically checks to see if the script already exists – if it does it loads the script, updates the contents and then saves the script back. If it doesn’t it calls a specific Rapture API call to create a new script. Again note that the script above is run in the security context of the logged in user and every API call made has its entitlements checked.

So we’ve loaded and saved a script, the final real part of our page is running the script and seeing the output. The first part (running the script) follows our usual pattern:

$('#runScript').click(function() {
    hider();
    var scriptURI = $('#contentName').text();
    $('#runningProgress').show();
    $.ajax({
          url: "../web/runScriptView.rrfx",
          dataType: 'json',
          data: QueryString,
          success: function(data) {
                $('#runningProgress').hide();
                $('#scriptOut').show();
                $('#execOutput').text(data.returnValue);
                $('#execOutput').show();
                processOutput(data.output);
          },
          error: function (error) {
               $('#runningProgress').hide();
               $('#scriptOut').show();
               $('#execOutput').text(error.responseText);
               $('#execOutput').show();
          }
    });
});

Here there’s a little complexity processing the return value (we’ll look at that in a moment) but the execution of the script is done by executing the web url “/web/runScriptView” and that is itself a Reflex script that looks like the following:

scriptURI = web['id'];
res = #script.runScriptExtended(scriptURI, web);
println(json(res));

The API call runScriptExtended basically runs the script and captures all of the output that script does (println calls for example) and the return value from the execution of the script. This structure is then returned to the caller – in this case the JavaScript ajax success or error functions.

In our little page I wanted to do very trivial display parsing – if the return was json then I could display that in an editor type context – if it was html I could display it directly, otherwise I’d just treat it as raw text. So the complicated looking code in the success/error functions of our execution code is there to show the output in the right format and to display the return value. In this example I did the rather fragile technique of looking at the first character returned to determine the format and called different display code based on the format:

function processOutput(output) {
    if (output[0][0] == '{') {
        processJson(output);
    } else if (output[0][0] == '<') {
        processHtml(output);
    } else {
        processText(output);
    }
}

function processJson(output) {
    $('#outputJson').show();
    outputJ.setValue(output[0], -1);
    outputJ.resize();
    outputJ.setTheme("ace/theme/monokai");
    outputJ.getSession().setMode("ace/mode/javascript");
}

function processHtml(output) {
        var div = document.getElementById('htmlOut');
        // data is raw html
        var data = "";
        for(var i=0; i< output.length; i++) {
            data = data + output[i];
        }
        div.innerHTML = data;
        $('#outputHtml').show();
}

function processText(output) {
    $('#outputText').show();
    var oldTable = document.getElementById('execOutputList'),
    newTable = oldTable.cloneNode(false),tr,td;
    var tbody = document.createElement('tbody');
    for(var i=0; i< output.length; i++) {
       tr = document.createElement('tr');
      addCell(tr,output[i]);
      tbody.appendChild(tr);
    }
    newTable.appendChild(tbody);
    oldTable.parentNode.replaceChild(newTable, oldTable);
}

function addCell(tr, element) {
    var td = document.createElement('td');
    if (element == undefined) {
       td.appendChild(document.createTextNode(''));
    } else {
     td.appendChild(document.createTextNode(element));
    }
    tr.appendChild(td);
}

For html output the picture looks something like:

Screen Shot 2014-12-10 at 8.25.16 AM

And for simple text we have:

Screen Shot 2014-12-10 at 8.26.43 AM

And that is pretty much it! The code for our simple page is split across three different layers – the html layout, the Javascript client side plumbing and the server side Reflex processing sitting on top of Rapture. Together they can make rich user experiences with a short development lifecycle. Look out for the sandbox Reflex explorer in the wild soon!

Layers

As before – if you’d like more information about Incapture or Rapture please drop me a line personally or to our general email address info@incapturetechnologies.com and we will get back to you for a more in depth discussion.


Subscribe for updates