Skip to content
This repository has been archived by the owner on Sep 25, 2018. It is now read-only.

Custom Action Tags (DEPRECATED)

Jacob Beard edited this page Jul 21, 2015 · 1 revision

This document describes the SCION API for defining custom tags.

This document refers to APIs which are valid for SCION v0.0.10, and no longer applies to the current SCION release

Concepts

SCION has built-in support for the tags described as Executable Content in the SCXML specification.

It is also possible to add support for custom action tags, as described in the SCXML specification. Custom actions allow arbitrary code to be executed when states are entered, exited, or transitions are taken. This code has access to the SCXML datamodel, as well as programmatic access to built-in action tags.

Implementation in SCION

SCION uses lightweight code generation internally. This means that a function is called for each action tag in a document. The function is determined by the action tag's local-name and namespace URI. The function returns a string, which is evaluated when the document is parsed.

Why does SCION use a code-generation approach, such that each function generates a string which is evaluated at runtime? Why does SCION not just call a function which gets executed at runtime? The reason for this is that, in order to reference variables in the datamodel as local variables, SCION dynamically constructs a scope in which datamodel variables are declared as local variables. By generating one large template string for all the action code, all action code is able to be evaluated once when the model is parsed, and then called when the model is executed, with datamodel variables accessible as local variables.

Custom actions can be added to SCION by extending the code-gen module. This is done by adding an XML namespace object to SCION module scion.ext.actionCodeGeneratorModule.gen.actionTags, and then adding a property to the namespace object which references a function which accepts the action's XML DOM node as an argument. You can use scion.ext.platformModule.dom to access DOM node attributes portably, or just reference them directly if you don't care about portability.

scion.ext.actionCodeGeneratorModule.gen.actionTags[<namespace>][<tagName>] = function(action){}

Examples

Internal module code-gen handles code generation for SCION. The following code snippet from the code-gen module illustrates how code is generated for the built-in SCXML action tags, and can serve as an example for defining your custom action tags.

var actionTags = {
    "" : {
        "script" : function(action){
            return pm.platform.dom.textContent(action);
        },

        "assign" : function(action){
            return pm.platform.dom.getAttribute(action,"location") + " = " + pm.platform.dom.getAttribute(action,"expr") + ";";
        },

        "if" : function(action){
            var s = "";
            s += "if(" + pm.platform.dom.getAttribute(action,"cond") + "){\n";

            var childNodes = pm.platform.dom.getElementChildren(action);

            for(var i = 0; i < childNodes.length; i++){
                var child = childNodes[i];

                if(pm.platform.dom.localName(child) === "elseif" || pm.platform.dom.localName(child) === "else"){ 
                    break;
                }else{
                    s += actionTagToFnBody(child) + "\n;;\n";
                }
            }

            //process if/else-if, and recurse
            for(; i < childNodes.length; i++){
                child = childNodes[i];

                if(pm.platform.dom.localName(child) === "elseif"){
                    s+= "}else if(" + pm.platform.dom.getAttribute(child,"cond") + "){\n";
                }else if(pm.platform.dom.localName(child) === "else"){
                    s += "}";
                    break;
                }else{
                    s+= actionTagToFnBody(child)  + "\n;;\n";
                }
            }

            for(; i < childNodes.length; i++){
                child = childNodes[i];

                //this should get encountered first
                if(pm.platform.dom.localName(child) === "else"){
                    s+= "else{\n";
                }else{
                    s+= actionTagToFnBody(child)  + "\n;;\n";
                }
            }
            s+= "}";

            return s;
        },

        "elseif" : function(){
            throw new Error("Encountered unexpected elseif tag.");
        },

        "else" : function(){
            throw new Error("Encountered unexpected else tag.");
        },

        "log" : function(action){
            var params = [];

            if(pm.platform.dom.hasAttribute(action,"label")) params.push( JSON.stringify(pm.platform.dom.getAttribute(action,"label")));
            if(pm.platform.dom.hasAttribute(action,"expr")) params.push( pm.platform.dom.getAttribute(action,"expr"));

            return "$log(" + params.join(",") + ");";
        },

        "raise" : function(action){
            return "$raise(" + JSON.stringify(pm.platform.dom.getAttribute(action,"event")) + ");";
        },

        "cancel" : function(action){
            return "$cancel(" + JSON.stringify(pm.platform.dom.getAttribute(action,"sendid")) + ");";
        },

        "send" : function(action){
            return "$send({\n" + 
                "target: " + (pm.platform.dom.hasAttribute(action,"targetexpr") ? pm.platform.dom.getAttribute(action,"targetexpr") : JSON.stringify(pm.platform.dom.getAttribute(action,"target"))) + ",\n" +
                "name: " + (pm.platform.dom.hasAttribute(action,"eventexpr") ? pm.platform.dom.getAttribute(action,"eventexpr") : JSON.stringify(pm.platform.dom.getAttribute(action,"event"))) + ",\n" + 
                "type: " + (pm.platform.dom.hasAttribute(action,"typeexpr") ? pm.platform.dom.getAttribute(action,"typeexpr") : JSON.stringify(pm.platform.dom.getAttribute(action,"type"))) + ",\n" +
                "data: " + constructSendEventData(action) + ",\n" +
                "origin: $origin\n" +
            "}, {\n" + 
                "delay: " + (pm.platform.dom.hasAttribute(action,"delayexpr") ? pm.platform.dom.getAttribute(action,"delayexpr") : getDelayInMs(pm.platform.dom.getAttribute(action,"delay"))) + ",\n" + 
                "sendId: " + (pm.platform.dom.hasAttribute(action,"idlocation") ? pm.platform.dom.getAttribute(action,"idlocation") : JSON.stringify(pm.platform.dom.getAttribute(action,"id"))) + "\n" + 
            "});";
        },

        "foreach" : function(action){
            var isIndexDefined = pm.platform.dom.hasAttribute(action,"index"),
                index = pm.platform.dom.getAttribute(action,"index") || "$i",        //FIXME: the index variable could shadow the datamodel. We should pick a unique temperorary variable name
                item = pm.platform.dom.getAttribute(action,"item"),
                arr = pm.platform.dom.getAttribute(action,"array"),
                foreachBody = pm.platform.dom.getElementChildren(action).map(actionTagToFnBody).join("\n;;\n");

            return "(function(){\n" + 
                "if(Array.isArray(" + arr + ")){\n" +
                    arr + ".forEach(function(" + item + "," + index + "){\n" +
                        foreachBody +
                    "\n});\n" + 
                "}else{\n" +
                    //assume object
                    "Object.keys(" + arr + ").forEach(function(" + index + "){\n" +
                        item + " = " + arr + "[" + index + "];\n" + 
                        foreachBody +
                    "\n});\n" + 
                "}\n" + 
            "})();";
        }
    }
}

Another example can be found in project archive.org-twilio-browser, which adds a custom action tag Response tag to SCION. Response sends the contents of the Response tag as the contents of an HTTP response. An example of its usage can be found in archive.xml from the same project.

scion.ext.actionCodeGeneratorModule.gen.actionTags[""].Response = function(action){
    var s = "_event.data.response.writeHead(200, {'Content-Type': 'application/xml'});\n" +
        "_event.data.response.end(" + JSON.stringify((new xmldom.XMLSerializer()).serializeToString(action)) + ");";
    //console.log("generated Response",s);
    return s;
};