Skip to main content

skip to main content

developerWorks  >  XML | Web development  >

XML Matters: MochiKit

Lift up your DOM manipulation of XML

developerWorks
Document options

Document options requiring JavaScript are not displayed


Rate this page

Help us improve this content


Level: Intermediate

David Mertz, Ph.D (mertz@gnosis.cx), Dialectician, Gnosis Software, Inc.

21 Nov 2006

MochiKit is a useful and high-level library for JavaScript. MochiKit takes its main inspiration from Python, and from the many conveniences the Python standard library offers; but on the side it also smooths over the inconsistencies among browser versions. MochiKit.DOM is a particularly handy component that lets you work with DOM objects in much friendlier ways than raw"JavaScript provides. A lot of MochiKit.DOM is customized for XHTML work, which possibly makes its use of XHTML wrapped microformats particularly convenient when combining MochiKit and Ajax.

Introduction

Dethe Elza, my recent guest columnist, has presented a number of interesting ideas surrounding Ajax, microformats, and Atom that I think bear further exploration. As a first step, this article will look at a wonderful ECMAScript library that Dethe touched on briefly in his picoformats article (see Resources): Bob Ippolito's MochiKit. MochiKit contains a number of nice enhancements to basic JavaScript (ECMAScript, technically) capabilities, mostly inspired by a combination of Ippolito's fondness for functional programming, and his love of Python's rich standard library and flexible constructs. In many ways, with MochiKit you can program in JavaScript while pretending you are using what many developers consider to be the friendlier Python language.

Why XML?

As readers of this column will know, the "X" in Ajax is there largely because ECMAScript, as implemented in Web browsers, more-or-less supports the W3C Document Object Model (DOM) specification. (Depending on your precise browser version, the "less" may dominate the "more".) This is just a fancy way to say that your JavaScript code automatically has a parser for XML and (X)HTML documents, along with a collection of API calls to traverse, search, and modify those documents as abstract structures. It seems like a natural fit, except for the awkward fact that the DOM is a remarkably awkward API. While you might make an argument to use the formality of the W3C DOM for a strongly and statically typed, highly structured, and carefully encapsulated, language like Java -- what we programmers call a bondage-and-discipline language -- there seems little motivation for it in a comparatively agile language like ECMAScript.

A reader might be inclined to wonder why she should bother with the XML part at all, even given what MochiKit.DOM makes easier. After all, JSON is essentially just a native JavaScript data structure (that by happy coincidence is simultaneously valid Python), which is lighter still. Nonetheless, XML retains some advantages. On one hand, in presentation contexts, XML is well able to be styled directly with CSS2 (Cascading Style Sheets 2). You can, of course, transform JSON into a stylable DOM object, but essentially that just means moving back to XML or (X)HTML. On the other hand, a lot more tools outside the ECMAScript interpreter itself talk XML than they do JSON. Data can arrive from -- or be delivered back to -- servers that use XML to define structured data. In some cases, this XML follows well-known and well-defined schemas, including ones that conform to published standards. If some other system in the overall communication flow wants to communicate using SVG, or OpenDocument, or TEI, or some ebXML standard, there are probably good reasons not to insert JSON as an extra layer in that mix.

Simple magic

Fortunately, MochiKit.DOM builds on what W3C DOM is intended to do -- provide an API for abstract document structures -- while making the easy things easy, and the hard things a lot less hard than they are in W3C DOM. The real magic in MochiKit.DOM is its willingness to flexibly coerce various types of objects into the right types during method calls, including doing so recursively. MochiKit assumes that if there is one obvious right thing to do, there is no need to make a programmer jump through hoops in casting types or calling methods to extract or mutate out the thing he needs. Moreover, MochiKit's FP-inspired partial application lets a programmer (and a program) be lazy when it is most convenient to be so.



Back to top


What's the big deal about HTML?

Obviously Web pages are usually written in (X)HTML, or often in something resembling HTML while not actually quite being HTML. I confess that the quirks mode of browsers does pretty remarkable jobs. But with wide implementation of CSS in modern browsers, any XML document with suitable styling works equally well, and can, in fact, let you use the specific tags of interest to the semantics of your document. Nowadays, it is largely possible to think of (X)HTML as just that XML schema that comes with a default CSS stylesheet.

The parity of generalized XML with (X)HTML is nice for Ajax contexts. Most of the examples that come with MochiKit wind up parsing XML or JSON into HTML tags; but a conceptually purer style of sticking with XML is largely possible. For a few examples, I will use the same <datatable> XML format that MochiKit uses for its ajax_tables example, but put it to slightly different use. First, try to style the table suitably. A simple CSS stylesheet might look like:


Listing 1. table.css

datatable {
    display: table;
    width: 99%;
    border: solid 1px;
    margin-left: auto;
    margin-right: auto
    }
columns {
    display: table-header-group;
    background-color: lightblue;
    }
column {
    display: table-cell;
    font-weight: bold;
    border-right: solid 1px;
    }
rows {
    display: table-row-group
    }
row {
    display: table-row
    }
cell {
    display: table-cell; border-right: solid 1px;
    }

You can pretty well infer the structure of a styled XML document from this CSS, but go ahead and look at one of the data files:


Listing 2. table1.xml

<?xml version="1.0"?>
<?xml-stylesheet href="table.css" type="text/css"?>
<datatable>
    <columns>
        <column>First_name</column>
        <column>Last_name</column>
        <column>Domain_name</column>
    </columns>
    <rows>
        <row>
            <cell>Dethe</cell>
            <cell>Elza</cell>
            <cell>livingcode.org</cell>
        </row>
        <row>
            <cell>Bob</cell>
            <cell>Ippolito</cell>
            <cell>bob.pythonmac.org</cell>
        </row>
    </rows>
</datatable>

Not much to it, and you can certainly publish this static document. Readers of it can see something like:


Figure 1. Screenshot of Firefox displaying table1.xml
Screenshot of Firefox displaying table1.xml


Back to top


Manipulating the XML

Just displaying styled XML does not need Ajax, and does not need MochiKit. So let's work up to some code that does something with the XML. For a start, wrap up your XML goodness with some old-fashioned HTML. Sure, you could use the magic xml:link attribute to add XLINK capabilities; but keep it simple for now:


Listing 3. table project index.html home


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
               "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>DOM made simple</title>
    <script type="text/javascript"
            src="../../lib/MochiKit/MochiKit.js"></script>
    <script type="text/javascript"
            src="../../lib/MochiKit/DOM.js"></script>
    <script type="text/javascript"
            src="table.js"></script>
  </head>
  <body>
  <p id="actions">
    <b onclick="showXML()">[Show current XML]</b>
    <b onclick="showText()">[Show text of table]</b> <br/>
    <b onclick="newChildContent('table1.xml')">[Load table1.xml]</b>
    <b onclick="newChildContent('table2.xml')">[Load table2.xml]</b>
    <b onclick="addRow()">[Add row of data]</b>        </p>
  <iframe name="child" id="child" src="table.xml" width="600" />
  </body>
</html>

The example HTML page in Listing 3 has three basic parts:

  1. Load some ECMAScript from two MochiKit components and one custom script file.
  2. Define a few clickable actions.
  3. Use in <iframe> to display the table, as in Figure 1.

Obviously, a more full-fledged application is likely to interact with a page in more sophisticated ways, perhaps using forms, page regions and custom widgets. But that is a different article.

Dissecting the JavaScript

Supporting the functions called in the HTML are a couple small support functions. Listing 4 looks at them:


Listing 4. Support routines in table.js

childDoc = function() {
    return frames['child'].document;
};
getRows = function() {
    return childDoc().getElementsByTagName('rows')[0];
};
randint = function(n) {
    return Math.floor(Math.random()*n);
}

These are fairly self-evident short-hands, and do not use any MochiKit capabilities. The two display functions use some minor MochiKit conveniences:


Listing 5. Display the XML content in table.js

showXML = function() {
    alert(toHTML(childDoc().documentElement));
};
showText = function() {
    alert(scrapeText(childDoc().documentElement));
};

Not a whole lot here either. In a non-toy program, I presume you would do more with the XML than just pop it up in an alert() box: maybe send it back to a server; or at least process and scan it further as part of the client application. Still, the scrapeText() is a convenient function to extract all the text nodes in a document; and toHTML() does proper escaping of reserved characters to render a DOM tree as an XML string (despite the name). Those at least let you examine the status of our table XML.

(Slightly) Real XML work

Where things start to get interesting is when you load and modify the XML content. Before I comment, let me present the code that loads new XML data:


Listing 6. Loading XML in table.js



newChildContent = function(url) {
    var req = getXMLHttpRequest();
    req.overrideMimeType("text/xml");
    req.open("GET", url, true);
    d = sendXMLHttpRequest(req).addCallback(datatableFromXMLRequest);
};
datatableFromXMLRequest = function (req) {
    var xml = req.responseXML;
    var new_rose = xml.getElementsByTagName('rows')[0]
    swapDOM(getRows(),new_rose);
    rows = getRows().getElementsByTagName('row');
    for (var i=0; i < rows.length; i++) {
        setNodeAttribute(rows[i], 'id', 'row'+i);
    }
};

The first nice thing to notice is that MochiKit -- like some other JavaScript libraries -- wraps the peculiarities of the various Microsoft Internet Explorer versions in a friendly function, getXMLHttpRequest() , that stops you from worrying about the details. It also, of course, is happy to talk with standards-compliant browsers that support XMLHttpRequest(). The still more interesting part is with the sendXMLHttpRequest(), which utilizes the MochiKit.Async toolkit. As the name suggests, this lets you retrieve requests asynchronously, and do other work while you wait to find out whether the request succeeds. In this toy code, you do not take advantage of it, but real code can decide what to do based on the result. For example, MochiKit's ajax_tables.js example does this with the deferred object d:


Listing 7. Handling a deferred object in ajax_tables.js


// call this.initWithData(data) once it's ready
d.addCallback(this.initWithData);
// except for a simple cancellation, log the error
d.addErrback(function (err) {
    if (err instanceof CancelledError) {
        return;
    }
    logError(err);
});

In table.js I just assume the request succeeds in loading a local file. From there, I pull out the <rows> element, using the standard DOM .getElementsByTagName() method. But the swapDOM() in MochiKit is a nice shortcut around more convoluted DOM syntax. However, my first hunch was to swap the whole document element with swapDOM(childDoc().documentElement,xml.documentElement). Unfortunately, that fails to add any nodes; perhaps readers can explain to me why this does not succeed. Obviously, what you want to swap depends on application specifics, but I was frustrated to be unable to just choose the top of the hierarchy.

In my callback function I also add, somewhat superfluously, id attributes to my <row> elements. A bunch of MochiKit elements shortcut around the DOM method .getElementsById(), but they presume a context in the main document rather than in the iframe DOM. So it's not obvious how to use most of them for my toy application. If I only worried about the main document, I might have used shortcuts like:


Listing 8. Firebug interactive console

>>> $('child')
<iframe width="600" src="table.xml" id="child" name="child">
>>> $('actions')
<p id="actions">

Several MochiKit functions let you name nodes by their id, and perform some action on them that way. For example, addElementClass("foo","newClass") would add newClass to the element with id 'foo', but not disrupt existing classes it had.

Tweaking the XML

Loading documents is a pretty bare start, and my sample code does not do much more than that. But another nice XML and (X)HTML convenience in MochiKit.DOM is its simple DOM creation functions, which play nicely with MochiKit's iterators and functional programming style. Most of the HTML functions already have pre-built convenience functions (conventionally named with all-caps), but it is easy to make your own for XML formats. For example, here is how I add (silly) rows to the XML table:


Listing 9. DOM creation magic in table.js


ROW = createDOMFunc('row');
CELL = createDOMFunc('cell');
newRow = function(cells) {
    return ROW({'type':'name'}, map(partial(CELL, null), cells));
};
addRow = function() {
    fn = ['John','Jane','Jim','Jill','June'][randint(5)];
    ln = ['Wu','Williams','Wegner','Wulu','Watanabi'][randint(5)];
    dn = ['gnosis.cx','google.com','gmail.com',
               'gnu.org','groklaw.net'][randint(5)];
    appendChildNodes(getRows(), newRow([fn,ln,dn]));
};

You can see that the rows I add are fairly pointless: I randomly permute a few possible values for each field, which is just enough to make added rows non-identical to each other. The minor magic comes in the cool DOM functions ROW() and CELL(), and especially in their quick combination in the newRow() function. Just to show off the possibility, I add a type="name" attribute to each <row> here; if you pass a dictionary rather than a null, a set of attributes are created.



Back to top


Making life easier

This introduction was fairly compact. MochiKit and MochiKit.DOM can do a lot more than what I touch on, but I believe this illustrates how a nice wrapper can greatly ease the pain of full W3C DOM calls. You have not seen a whole lot of it here, but MochiKit.DOM is careful to transform the arguments that are actually passed to its functions into something that the particular function can use. So for example, strings naming id attributes are often interchangeable with nodes themselves. And sequences and iterators get unrolled whenever doing so makes things easier.

Another convenience I discovered while writing this article was the really wonderful Firefox plugin called FireBug (see Resources). This is not the article to talk about it, but FireBug provides a rich environment for debugging and experimenting with JavaScript, XML, and Web pages in general. Perhaps the nicest feature in FireBug is its interactive console that lets you try out JavaScript commands (including, for example, MochiKit enhancements to them) in the same way you might try out commands in a Python interactive shell. Of course, for good measure, FireBug throws in a debugger and an inspector while it is at it (including a nice way to view the current DOM, and examine XMLHttpRequest() results to understand Ajax applications).



Resources

Learn

Get products and technologies
  • FireBug: Read about or download this extension.

  • IBM trial software: Build your next development project with trial software available for download directly from developerWorks.


Discuss


About the author

Photo of David Mertz

David Mertz is a great believer in open standards, and is only modestly intimidated by verbosity. David may be reached at mertz@gnosis.cx; his life pored over at http://gnosis.cx/dW/. Suggestions and recommendations on this, past, or future columns are welcomed. Check out David's book Text Processing in Python.




Rate this page


Please take a moment to complete this form to help us better serve you.



YesNoDon't know
 


 


12345
Not
useful
Extremely
useful
 


Back to top