jQuery and Strophe: Made for Each Other

March 13, 2009

Strophe is a beautiful library for building XMPP applications in JavaScript. It has a lot of nice features to help you build stanzas and respond to various event stanzas. It is built on top of the DOM, and unfortunately, the DOM is not always fun to work with. Luckily, we have an excellent DOM slicer and dicer that we can use alongside Strophe, the jQuery library.

:EXTENDED:

The Hard Way

Let’s take a look at what the code looks like for the traditional DOM method of manipulating incoming XMPP stanzas.

Our first example stanza handler is for receiving service discovery information, or in XMPP parlance, disco#info stanzas. Our code will pull out the feature information from the stanza, and pass that along to someone else.

function onDiscoInfoResult(stanza)
{
    var result = []
    var features = stanza.getElementsByTagName("feature");
    for (var i = 0; i < features.length; i++) {
       result.push(features[i].getAttribute("var"));
    }

    doSomethingWithFeatures(result);
}

</code>

The disco code isn’t too bad, but the DOM gets a lot worse the more complicated the manipulations.

Next, let’s look at Strophe’s echobot example.

function onMessage(msg) {
    var to = msg.getAttribute('to');
    var from = msg.getAttribute('from');
    var type = msg.getAttribute('type');
    var elems = msg.getElementsByTagName('body');

    if (type == "chat" && elems.length > 0) {
        var body = elems[0];

        log('ECHOBOT: I got a message from ' + from + ': ' + 
            Strophe.getText(body));
    
        var reply = $msg({to: from, from: to, type: 'chat'}).cnode(body);
        connection.send(reply.tree());

        log('ECHOBOT: I sent ' + from + ': ' + Strophe.getText(body));
    }

    // we must return true to keep the handler alive.  
    // returning false would remove it after it finishes.
    return true;
}

</code>

This still isn’t terrible, but we can see already that there are a lot of calls to long functions and dealing with the results of getElementsByTagName. Looping has to be done manually. If we need a specific element of the hierarchy, then digging it out of the DOM is pretty painful.

The Easy Way

jQuery makes DOM manipulations really, really simple. You can dig out very precise pieces of the DOM, manipulate them in aggregate, and it even takes care of much of the pain of looping for you.

Here are the same two examples using jQuery.

function onDiscoInfoResult(stanza)
{
    var result = $(stanza).find('feature').map(function () {
        return $(this).attr("var");
    }).get();

    doSomethingWithFeatures(result);
}

</code>

In the disco info example, jQuery’s map() function has made it really easy to grab a bunch of things at once, do something to them (in this case, pulling out a specific attribute value), and then throw the results into an array.

Notice that jQuery is happy to take our XML stanzas as input, just by passing them into the $() function. It then works just as normal, except with our single stanza as its document instead of the HTML page.

Here’s the echobot example:

function onMessage(msg) {
    $(msg).find("message[type='chat'][from]:has(body)")
        .each(function () {
            var body = $(this).find("body:first").text();
            var from = $(this).attr("from");

            log('ECHOBOT: I got a message from ' + from + ': ' + body);

            var reply = $msg({to: from, type: "chat"})
                .c("body")
                .t(body);
            connection.send(reply.tree());

            log('ECHOBOT: I sent ' + from + ': ' + body);
        });
}

</code>

The jQuery code is shorter and clearer because it doesn’t have to do any of the busy work of dealing with DOM. Using jQuery’s powerful selectors, we have:

This code is actually more robust than the other version because we never bothered to make sure there was a from attribute originally.

jQuery is not Just the Inspiration

jQuery’s design has certainly influenced Strophe, most notably with the builder syntax. It is not just an inspiration, however, as its powerful selectors and looping make it perfect for using to slice and dice incoming XMPP stanzas.

I’m sure it was never intended for such a purpose, but it is a testament to jQuery’s good design that it just works, even in domains that are unexpected.

I have to say that working with XMPP stanzas in JavaScript is by far my preferred environment due to jQuery’s powerful selectors and Strophe’s really convenient builders. I hope that this will eventually find its way into Python, Ruby, Erlang, and other languages that do heavy XMPP lifting.

jQuery and Strophe: Made for Each Other - March 13, 2009 - Jack Moffitt