Onion MLOnion ML is an XML template system designed with a bias toward modularity. Onion ML lets you easily custom XML tags to make modular content design simple and easy to mix with HTML. It is somewhat comparable to XSLT and JSF, but intended to be easier to understand. You define custom tags either as markup in XML files or as custom JavaScript functions which generate output. Onion ML also provides several control flow methods necessary for dynamic content. Methods for iterating over data sets and conditionally displaying tags are core to Onion ML's functionality. Introduction to Onion MLOnion ML is a meta syntax language for XML/HTML. At its core it allows you to define XML tags, and as a result, define layouts, templates, widgets, and so on. For example, to define a tag, you would write: <tag:mytag>This is <i>my</i> tag.</tag:mytag> Then if you were to use this tag somewhere, for example: <p>Check out my tag: <mytag/></p> It would be rendered as: <p>Check out my tag: This is <i>my</i> tag.</p> Because you can use tags with-in tags, the power increase. For example, if you wanted to make a web-site layout, you might write the tag: <tag:mylayout> <html> <head> <title><arg:title/></title> <style> ... </style> </head> <body> Now, to use this tag, you might have a page that looks like: <mylayout> <title>Hello world!</title> <body>This is my <b>world</b>, too.</body> <year>2008</year> </mylayout> This would be rendered as: <html> <head> <title>Hello world!</title> <style> ... </style> </head> <body> Congratulations! You have now successfully separated your content from your layout! Let us try to understand what's happening.
By separating TagsTags are simply defined as Within tags you can reference any other tag. If it does not exist, it will be used as-is. AttributesYou can set attributes with the special <set:attribute> tag. For example: <tag:link> <a><set:href><arg:url/></set:href><arg:name/></a> </tag:link> This could then be used like so: <link> <url>http://alt.cellosoft.com/</url> <name>Alt website</name> </link> Arguments can be passed as tags or attributes, for example, the following is equivalent short-hand for the four lines above: <link url="http://alt.cellosoft.com/" name="Alt website"/> If both an attribute and child tag are specified, behavior is undefined. Both of the examples above will evaluate to: <a href="http://alt.cellosoft.com/">Alt website</a> Processing Data [under construction]Data is an important part of a template engine. While the XML format does not allow for logic, several special tags are defined to conditionally display content. For example, to display a list of users, you may do something like: <if data="users"> <table> <for item="user" data="users"> <tr> <td><get data="user.name"/></td> <td><get data="user.email/></td> <td><date><get data="user.registerDate"/></date></td> </tr> </for> </table> <else>No users found.</else> </if> This demonstrates all three special tags: for, if, and else. [Note: I am undecided on the syntax for these tags. I am also considering the idea of an alternative shorthand syntax (currently not supported):] <table o:if="users"> <tr o:for="user" o:data="users"> <td><data get="user.name"/></td> <td><data get="user.email"/></td> <td><date><data get="user.registerDate"/></date></td> </tr> </table> <if data="!users">No users found.</if> Onion ML in the Alt FrameworkTo use Onion ML in JavaScript you first make an OnionML object with XML: var onion = new Onion( <onion> <tag:mytag>woot, <arg:all/>!</tag:mytag> </onion> ); onion.add( <tag:anothertag>play the <mytag>tuba</mytag></tag:anothertag> ); Then you can use that object to evaluate tags: var output = onion.evaluate(<mytag>ice cream</mytag>); The output is XML, so if you want to print it to the screen, write: response.write(onion.evaluate(<mytag><b>ice cream</b></mytag>).toXMLString()); You pass variables as an optional second argument: onion.evaluate(<mytag/>, { name: "Onion Man" }); Functional TagsYou can specify tags as functions in JavaScript: var onion = new Onion; onion.add("date", function() { return new Date().toString() }); onion.add({ multiply: function(onion,xml) { return contents.x * contents.y; }, repeat: function(onion,xml) { var result = <></>; for (var i=0; i<xml.count; i++) result += onion.evaluateChildren(xml.body); return result; } }); These functions take an optional XML object with the entire XML tag and contents and should return a new XML object. Important Note: by design the contents are not automatically evaluate. You can evaluate contents with the provided Onion.evaluate and Onion.evaluateChildren methods. Advanced Functional TagsFunctional tags give you a surprising amount of power for very little code, for example, one could write an import tag for loading in other xml files with tags (usage: <import file="foo.xml"/>): onion.add("import", function(onion,xml) { onion.add(XML.read(xml.@filename)); return <></>; }); Or the data processing outlined above could be implemented as:
// this translates getItem({foo:{bar:1}},"foo.bar" to 1
function getItem(data,path) {
var path = path.toString().split(/\./);
while (path.length)
data = data[path.shift()];
return data;
}
onion.add({
get: function(onion,xml,data) {
return getItem(data, xml.@data);
},
"if": function(onion,xml,data) {
if (getItem(data,xml.@data)) {
delete xml['else'];
return onion.evaluateChildren(xml).children();
} else
return onion.evaluateChildren(xml['else']).children();
},
"for": function(onion,xml,data) {
var result = <></>;
for each (var item in getItem(data,xml.@data)) {
data[xml.@item] = item;
result += onion.evaluateChildren(xml.copy(),data).children();
}
return result;
}
});
Engine BackgroundOnion ML was designed so that it has very few moving parts. In fact, all tags are simply standard functional tags. Generic tags are defined as a functional tag that call Onion.evaluateChildren: Onion.prototype.getTagFunction = function(tag) { // return tag if it is defined if (this.tags[tag]) return this.tags[tag]; // otherwise return generic function return function(onion, xml, data) { return onion.evaluateChildren(xml,data); } } Onion.evaluateChildren: Onion.prototype.evaluateChildren = function (xml, data) { Onion.evaluate is defined as: Onion.prototype.evaluate = function(xml, data) {
// get the associated tag function and call it
return (this.getTagFunction(xml.localName()))(this,xml,data);
}
XML-defined tags are defined as (args is an array of args used, tagxml is the XML of the tag): function(onion, xml, data) { E4X does not support "replaceWith" so we wrote this helper function: Onion.replaceWith = function(node,newnode) { |
|
page last updated: Saturday, February 16, 2008 |