February 28th, 2012
by Robbert Broersma
XSLT for hipsters, or how to win the demojam
Every year at XML Prague the people from MarkLogic organize a contest where contestants have 300 seconds to impress and entertain the audience during the conference dinner. For this year's demojam I rendered a chart from the Ajax.org website using Frameless, my XSLT 2.0 engine for the web. I won.
It is a tech demo: technologies used are novel and currently it only runs well on Safari. Although my XSLT processor runs on all browsers, Safari is the only browser that supports both MathML and fast CSS 3D transforms.
The demo was scheduled to put be on-line right after I had returned from Prague, but lingering problems with Closure Compiler have held back publication of the demo. Because Mohamed Zergaoui is so anxious to scrutinize the code I've already put the XSLT files on GitHub.
My best guess is that refactoring the XPath engine will allow Closure Compiler to 'grasp' the code, but that will take at least until the end of this week to finish. I'll put word out on Twitter when it's done.
How is it done?
The rendering of the whole page is done in XSLT, the squares are generated using two xsl:for-each loops. The rotation around the Y-axis is implemented in a simple CSS transition loop. Apart from the animation and interactivity, this demo should work with any XSLT 2.0 processor. (Try it!)
For solving the formula several thousand times each second the formula is implemented in JavaScript, to minimize the CPU-load and to obtain a decent frame rate. I provided a xsl:function implementation of the formula in graph-fallback.xsl, which perhaps in some future can be used when I have implemented compiling of hotspots to native JavaScript code.
Rendering new animation frames is done using the ixsl:schedule-action extension instruction, which draws a new frame right after the last frame.
<xsl:template name="animate"> <xsl:call-template name="animation-frame"/> <ixsl:schedule-action wait="0"> <xsl:call-template name="animate"/> </ixsl:schedule-action> </xsl:template>
The full code of the demo can be found on GitHub, I'm releasing it under LGPL. Distribution of the XSLT processor will not be until the first private beta release in a couple of months. Please sign up for beta invites on XSLT2.org.
The gory details
When you look at the page source, you'll see the XML source and no HTML... Right after returning from XML Prague, I implemented support for stylesheets referenced by a <?xml-stylesheet processing instruction, making the demojam.html file obsolete. So now the demo consists of just three files!
The MathML is embedded in the stylesheet. Clicking on one of the squares focuses that element (because of tabindex="0"), the click event will be checked against ixsl: mode templates. The processor looks for templates matching template the event target, applying only templates that are in the ixsl:onEventType mode, ixsl:onclick in this particular example.
<xsl:template match="span" mode="ixsl:onclick"> ... </xsl:template>
That templates stores some state info in attributes, and finds the MathML element after the equals operator to render the current Y value of that square.
Setting hundreds of CSS style properties cost precious milliseconds, so I propose removing unsupported instructions at compile-time using feature detection in the XSLT 2.0 use-when attribute. When the property isn't supported, the instruction is simply ignored and won't affect performance.
<ixsl:set-attribute name="style:-ms-transform" select="..." use-when="x:css-property-available('-ms-transform')"/>
While in the current stylesheet this optimization is done explicitly, I will make sure that Frameless will run this check for ixsl:set-attribute instructions out of the box so you don't need to clutter your stylesheets.
Feature detection in use-when is the next big thing, believe me!
There's all kinds of workarounds for the processor being under heavy development. For example, XPath 2 syntax mostly isn't supported yet because that tokenizer is still under development. That's why you'll see op:to($a, $b) instead of $a to $b, and same goes for the choose function.
Thanks
It's been great showing you my latest project, thanks for your cheers and to those who came up to me to congratulate. Of course thanks to MarkLogic for organizing the demojam and awarding me and Gerrit Imsieke an iPad.
See you all next time in Prague!