Relay and JavaScript Generators

Relay pipelines and XSLT 2.0 make for some nifty resue of code.

I’m using XSLT 2.0 to generate JavaScript objects from XML. The JavaScript objects can be evaluated on the client site to perform actions in response to UI events, or in response to updates from XMLHttpRequest.

Here is a base example. For each article in a taggable feed in the new version of Think New Orleans, I create the following structure.

new Syndibase.Tagger({
    "article": 1,
    "itemGuid": "tag:thinknola.com,2005:/taggable/articles/nola.com/forums/townhall/2005-09-21/2005-09-21T02-50-48.003-1",
    "userTags": [
        {
            "label": "hello"
        },
        {
            "label": "nola"
        }
    ],
    "itemTags": {
        "displayLabel": "Suggested Tags:",
        "tags": [
            {
                "label": "hello"
            },
            {
                "label": "nola"
            }
        ]
    }
})

That JavaScript is generated by an pipeline of XSLT 2.0 transforms. It starts from a transform of an annotated Atom 1.0 feed. The feed contains tags which are added using a Think New Orleans namespace.

An XSLT 2.0 template that matches a <atom:entry/> element contains the following code to generate an a a document that can be transformed into JavaScript.

<ht:div class="article-record">
  <ht:pre class="article-data" id="Syndibase.Article.TagData.{position()}">
    <js:data-callback name="new Syndibase.Tagger">
      <js:object>
        <js:entry>
          <js:name>article</js:name>
          <js:number><xsl:value-of select="position()"/></js:number>
        </js:entry>
        <js:entry>
          <js:name>itemGuid</js:name>
          <js:string><xsl:value-of select="atom:id"/></js:string>
        </js:entry>
        <js:entry>
          <js:name>userTags</js:name>
          <js:array>
            <xsl:for-each select="tno:tag[@type = 'person']">
              <js:object>
                <js:entry>
                  <js:name>label</js:name>
                  <js:string><xsl:value-of select="."/></js:string>
                </js:entry>
              </js:object>
            </xsl:for-each>
          </js:array>
        </js:entry>
       <js:entry>
          <js:name>itemTags</js:name>
          <js:object>
            <js:entry>
              <js:name>displayLabel</js:name>
              <js:string>Suggested Tags:</js:string>
            </js:entry>
            <js:entry>
              <js:name>tags</js:name>
              <js:array>
                <xsl:for-each select="tno:tag[@type = 'item']">
                  <!--| TODO: Add frequency. |-->
                  <js:object>
                    <js:entry>
                      <js:name>label</js:name>
                      <js:string><xsl:value-of select="."/></js:string>
                    </js:entry>
                  </js:object>
                </xsl:for-each>
              </js:array>
            </js:entry>
          </js:object>
        </js:entry>
      </js:object>
    </js:data-callback>
  </ht:pre>
</ht:div>

The elements in the namspace mapped to the js prefix are picked up by an XSLT 2.0 transform and turned into the JavaScript object tree in the first listing.

You’ll note that the JavaScript will not be contained withtin a code tag when it generated. Rather, it is contained in a pre tag which has a class called article-data. The article data class is defined display: none so this code is hidden from the user, but also hidden from the browser’s JavaScript interpreter.

If you look at the first listing closely, you’ll see that it is actually a single statement that is a constructor call.

When the user clicks on an “add tags” link, this pre element is found by it’s associated id element, and the text value of the element is passed to JavaScript eval. The statement is executed.

If you look at the first listing again, you’ll notice that the JavaScript is actually a single constructor call. The constructor is as follows.

Syndibase.Tagger.prototype = {
    initialize: function(articleTags) {
        this.articleTags = articleTags;
        this.element = $('Syndibase.Article.Tagger.' + articleTags.article);

        this.element.innerHTML = Syndibase.Tagger.generateTagger(articleTags);

        var cancel = $('Syndibase.Article.Cancel.' + articleTags.article);
        var edit = $('Syndibase.Tagger.Tags.' + articleTags.article);
        var send = $('Syndibase.Tagger.Send.' + articleTags.article);
        var userTags = $('Syndibase.Tagger.UserTags.' + articleTags.article);
        var itemTags = $('Syndibase.Tagger.ItemTags.' + articleTags.article);

        // Turn off IE autocomplete.
        this.edit = edit
        this.edit.setAttribute('autocomplete','off');
        this.onkeypressListener = this.onKeyPress.bindAsEventListener(this);
        Event.observe(this.edit, 'keyup', this.onkeypressListener);

        // Connect the submit button.
        this.send = send
        this.onsubmitListener = this.onSubmit.bindAsEventListener(this);
        Event.observe(send, 'click', this.onsubmitListener);

        // Setup the cancel button.
        this.cancel = cancel;
        this.cancel.style.display = 'block';
        this.oncancelListener = this.onCancel.bindAsEventListener(this);
        Event.observe(this.cancel, 'click', this.oncancelListener);
    }
}

If you’ve worked with script.aculo.us and Prototype you’ll see code and conventions from that library.

The article tagger does as script.aculo.us does. It creates a controller object that handles events for the entire article tagging control. It then hooks events in such a way, that the this argument passed to an event handler is the controller object, not control that fired the event. (That’s the big difference right?) The control that fired can be had through the target property of the event object anyway, so it tends to be redundant (not always because events bubble).

This makes for a meaningful this that can be initialized in a constructor. Otherwise, you’d have a this object that referenced a DOM element node, which means finding a place to store data. Is it in the this node, the target node, a common parent node, or in an external JavaScript object? How about a combination of the four? Prototype’s event handling pattern makes state management look and feel familiar to OO programmers.

Getting back to the example…

In the listing three note that close to the start I generate the HTML for the tagger control by calling Syndibase.Tagger.generateTagger and passing the articleTags data structure. This method is the focus of this article. It generates the HTML for the control, it will populate the HTML elements with values from the structure, and the element ids generated will contain the article number so that the id for each control element with an id is unique.

Here’s the code for that method.

<?xml version="1.0"?>
<js:document
    xmlns:jh="tag:engrm.com,2005-09:schema/javascript/html"
    xmlns:js="tag:engrm.com,2005-09:schema/javascript"
    xmlns:ht="http://www.w3.org/1999/xhtml">
  <js:package name="Syndibase">
    <js:package name="Tagger">
      <jh:generator name="generateTagger">
        <jh:param>articleTags</jh:param>
        <ht:div>
          <ht:input id="Tags.{articleTags.article}" name="Tags" type="text" class="wide">
            <jh:attribute name="value">
              <jh:for item="tag" select="articleTags.userTags">
                <jh:if test="index != 0">
                  <jh:text> </jh:text>
                </jh:if>
                <jh:value-of select="tag.label"/>
              </jh:for>
            </jh:attribute>
          </ht:input>
          <ht:input id="Send.{articleTags.article}" name="Send" type="submit" value="OK"/>
        </ht:div>
      </jh:generator>
    </js:package>
  </js:package>
</js:document>

Yes. It’s XML. No. Not really.

That is the definition of the JavaScript generator in XML. I run it through an XSLT 2.0 pipeline before I send it to the browser. The same pipeline that created the article tag data structure in listing one takes the XML generator definition and creates the following JavaScript method.

Syndibase.Tagger.generateTagger= function (articleTags) {
    var textEscape = function(string) {
        return string.replace(/&/g, '&amp;')
                     .replace(/</g, '&lt;')
                     .replace(/>/g, '&gt;');
    }
    var attributeEscape = function(string) {
        return textEscape(string).replace(/"/g, '&quot;');
    }
    return '' +
        '<div>' +
            '<input' +
                ' id="Syndibase.Tagger.Tags.' + articleTags.article + '"' +
                ' name="Tags"' +
                ' type="text"' +
                ' class="wide"' +
                ' value="' +
                    (function() {
                        var html = '';
                        for (var index = 0; index < arguments[0].length; index++) {
                            var tag = arguments[0][index];
                            html += '' +
                                (function () {
                                    var html = '';
                                    if (index != 0) {
                                        html = '' +
                                        ' ' +
                                        ''
                                    }
                                    return html;
                                })() +
                                attributeEscape(tag.label) +
                            '';
                        }
                        return html;
                    })(articleTags.userTags) +
                '"' +
            '/>' +
            '<input id="Syndibase.Tagger.Send.' + articleTags.article + '" name="Send" type="submit" value="OK"/>' +
        '</div>' +
    ''
}

The resulting JavaScript HTML generator makes use of a JavaScript functions acting as closures to implement the looping and branching constructs of the generator. It turns out that everything that should be an XML template named variable becomes visible to the XML template language within the context of the XML template construct, while things like the arguments array and the index have a local scope for the XML template construct.

The JavaScript emitted by the transform uses closures to create scoped named variables. JavaScript methods have function scope, meaning no matter where you declare then within a function, they remain visible to every block in the remainder of the function after they are first declared. That’s why the nested closures. They create a new function scope, and hide the nested named template variables from the function that contains them.

Thus, html is a variable visible to any template in the XML template language, but it will always contain the html for the scope of that template construct. Same goes for index. As you can see, I can reference the index to determine if whitespace is needed.

The named parameters of the template language, like the method parameters and the item attribute of the for element are going to be visible to nested XML template statements, but not visible to parent or sibling XML template statements.

Future directions…

It’s entirely possible to generate JavaScript that assmbles HTML DOM for those cases, such as table rows, where innerHTML is known to fail. It would use the same XML templating langauge, but generate JavaScript that creates document nodes and appends them to a fragment.

I’m going to continue to add new language constructs, and keep a close eye on the performance of these nested closures.

Also, since the generators are specified in XML, I can generate the generator specifications using XSLT 2.0. A custom generator can be created for users with particular roles, for example, so that an extra control panel for adminstrators is completely removed from the gernation function for non-adminstrators. Thus, the resulting generator, and the resulting size of the JavaScript will be smaller when that panel is not needed.

In closing…

This template system is well suited for client side HTML generation, where the HTML generated tends to be snippets rather than full pages. The larger page should be hashed out on the server-side, but updating content on the client side can be much easier to implement with this framework.

This JavaScript generator framework allows me to create client side JavaScript HTML generators. With these generators I can generator HTML from JavaScript data structures. Those structures could be in memory, created lazy by evaluating JavaScript objects embeded as text in the document, or by fetching JavaScript as text using XMLHttpRequeset.

The HTML generation is quick and efficent since it the HTML generation is done in JavaScript, not in an external templating langauge like client side XSLT.

It allows me to write the HTML in XHTML. The code is cleaner. (The generated JavaScript is frightful, but we’re not editing that.) Controller objects do not have to take element names and css class names as parameters. They can take a view generator as a parameter instead. One step closer to a JavaScript Model-View-Controller architecture.

2 Responses to “Relay and JavaScript Generators”

  1. Alan’s Blogometer » Blog Archive » Limits of innerHTML Says:

    […] Fortunantly for me, I hacked my client side templating language to also build content using DOM, so I don’t have to change the symantics of my markup. I can change the means to generate it with the flip of a swtich (or the change of an element name). More later. […]

  2. Alan’s Blogometer » Blog Archive » innerHTML Quirk / List Items With Inline Display Says:

    […] Fortunantly for me, I hacked my client side templating language to also build content using DOM, so I don’t have to change the symantics of my markup. I can change the means to generate it with the flip of a swtich (or the change of an element name). More later. […]

Leave a Reply