Client Side Server Side Includes

I spent some time over Xmas reflecting on my past web adventures. My first ever website, a guide to local drinking establishments, was hosted on a cast-off server at Edinburgh University in late 1993, and the particular server and site are now long gone (although the server has been replaced). Similarly, my original mid-nineties home page, replete with animated GIFs and ripped off Homer Simpson images has also be consigned to the recycle bin of web history (thankfully). However, the first website I worked on 'professionally' is still online: iwant2bhealthy.com is a thousand page static HTML monolith which we maintained with Dreamweaver 3.

The site did take advantage of some server processing, it used Server Side Includes (SSI) to embed particular common items such as the main navigation and footer. SSI isn't so common these days when nearly every cheap host offers some sort of server side scripting language, so often it isn't turned on my default. The result is that the iwant2bhealthy.com website is missing its main navigation and footer on most pages, all that's left is some markup like this:

<!--#include virtual="/Library/mainmenu.shtml" -->

Or this:

<!--#include virtual="/Library/footer.shtml" -->

I had a hankering to experience the website in all its turn of the century glory and it seemed to me that I ought to be able write a bookmarklet to grab jQuery, grab the comments, fetch the includes with AJAX and insert them in place of the comments.

It turns out that the first tricky thing is grabbing the comment elements. The jQuery selection engine purposely ignores comments so none of those handy little methods are much use. There's not much option but to loop through the document and select based on nodeType (8 for a comment element), fortunately someone on the web had already done most of the hard work so I was able to adapt his code:

parseSSI : function(el) {
    var nodes = el.childNodes;
    var l = nodes.length;
    while (l--) {
        current = nodes[l];
        if (current.nodeType == 8) {
            //do stuff here
        } else if (current.nodeType != 3 && current.childElementCount > 0) {
            this.parseSSI(current);
        }
    }
}

The function loops through all the child nodes of a supplied element and, if they're a comment, does some processing. If the node isn't a comment we check that it's also not a text node and then call the function recursively if there are any child nodes.

Of course it's entirely possible that there are comments in the web page that have nothing to do with server side includes, so some sort of check is probably in order. Following James' example I used a regular expression:

re : /#include virtual=\"(.*)\"/i

My next step was to convert those comment elements into something that could be more easily manipulated by jQuery, so I decided to convert them to links:

var match = this.re.exec(current.data);
if (match != null) {
    var a = document.createElement('a');
    a.href = match[1];
    current.parentNode.replaceChild(a, current);

Now we're back in the nice, succinct land of jQuery - fetch the URL with Ajax and replace the relevant link element in the callback:

$.ajax({ url: match[1],
         success: function(data, textStatus, XMLHttpRequest){
            $('[href=' + this.url + ']').replaceWith(data);
        }});

Try out the final code here.