Sunday, June 8, 2008

jQuery and dynamic HTML in XUL

There is a problem with doing dynamic HTML in Firefox Extensions using jQuery, actually only if you use XUL windows. This is probably applicable to all JavaScript frameworks that are allowing dynamic manipulation of DOM.
Consider the following example:




<?xml version="1.0"?>
<window id="rootWnd" title="Test"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:html="http://www.w3.org/1999/xhtml" >
<script type="application/x-javascript" src="jquery-1.2.3.js"/>
<script type="application/x-javascript">
function test(){
$("#content").append('&lt;p&gt;test&lt;/p&gt;');
alert('done');
}
</script>
<vbox hidden="false" height="0">
<button label="click me" oncommand="test();"/>
<html:div id="content"></html:div>
</vbox>
</window>




There is nothing special about this code, highlighted text JavaScript should add paragraph to content div and show text "test". That is probably very common statement when using jQuery to change some HTML content (and probably doing something more useful). Nothing special about the code as I said but it does not work. If you try this into web browser it will work though however in XUL it won't.
This example follows a tutorial on adding HTML elements to XUL. It is obvious now that $("#content").append('test') won't work. According to the article it has to be: $("#content").append('test'). Unfortunately it does not work again. The problem is in jQuery.1.2.3.js, more precisely in the line 968:
var tags = jQuery.trim( elem ).toLowerCase(), div = context.createElement("div");
Again if we try to put "html:div" it will not work and now the problem is in the Mozilla's document.createElement implementation which will create the element in the default namespace of http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul since it is XUL document. I think this is not how it should be but non the less it is the way it works so solution to the problem would be:
div = context.createElementNS("http://www.w3.org/1999/xhtml", "html:div")
which will create the element in the correct namespace. Unfortunately it will not work again since exception will be thrown in the line 998: div.innerHTML = wrap[1] + elem + wrap[2]; it does not like html:... part I guess.
So is there a solution? Here it comes:

Solution

If we rewrite the XUL document to look like this:




<?xml version="1.0"?>
<xul:window id="rootWnd" title="Test"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns="http://www.w3.org/1999/xhtml" >
<xul:script type="application/x-javascript" src="jquery-1.2.3.js"/>
<xul:script type="application/x-javascript">
function test(){
$("#content").append('&lt;p&gt;test&lt;/p&gt;');
alert('done');
}
</xul:script>
<xul:vbox hidden="false" height="0">
<xul:button label="click me" oncommand="test();"/>
<div id="content"></div>
</xul:vbox>
</xul:window>




What we are doing here is setting the default namespace to be the XHTML namespace instead of XUL. It requires a bit of rewriting the XUL elements but it will presumably enable us to use any JavaScript framework. however there is another bit that has to be changed in the jQuery script:
line 968 should stand as:
div = context.createElementNS("http://www.w3.org/1999/xhtml", "div")
And of course any createElement calls should be amended.

Another problem

At the end to conclude with another unsolved problem. This will all work if you have control over the html contents you want to change, but if you don't, e.g. showing rss feed, then you might end up with a problem. If you check the namespaces in the XUL document you'll see that namespace is of XHTML and actually the document is XML document. Because of that, sometimes you might end up with error similar to:
[Exception... "Component returned failure code: 0x80004003 (NS_ERROR_INVALID_POINTER) [nsIDOMNSHTMLElement.innerHTML]" nsresult: "0x80004003 (NS_ERROR_INVALID_POINTER)"
Which is Firefox's friendly way to say that your XHTML is invalid. Thanks to this article in Russian (here is the English version, courtasy of the Google language tools, mind that translation is a bit awkward but it can be understood if you badly need it) I managed to decypher the error message.
This message can occure because of many things, unclosed tags, text in elements that are not supposed to have text etc. It can pass in regular browser since browser will do its best to parse whatever you give it but if it ain't proper XHTML it won't work in Firefox. If you have any suggestions on how to clean the HTML to get proper XHTML please let me know.

4 comments:

  1. I know this is an old topic but I'm currently trying to do a similar thing - I had the same idea of setting the default namespace to XHTML instead of XUL, but I find the $(document).ready() function does not fire. Nor does the fire. I think I understand why the jQuery function doesn't work but not why the XUL onload event doesn't. Any ideas? Did you come across this problem with your testing?

    ReplyDelete
  2. I used jQuery for a very limited scenario so unfortunately I can't help you with that. I would imagine it is something simple (as with namespace) but it really needs to be debugged. It could be that ready function screws up onload event handling completely, check if it fires when you remove ready() call.

    ReplyDelete
  3. Thanks Mihailo. You made my day :)

    ReplyDelete
  4. I found that using the $(document).bind("ready", function() { ... }); syntax works and is functionally equivalent to $(document).ready(). You then need to trigger the ready event in the XUL window onload attr.

    ReplyDelete