-
Notifications
You must be signed in to change notification settings - Fork 29
Custom Action Tags (DEPRECATED)
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
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.
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){}
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;
};