Saturday, May 21, 2011

An alternative workaround for Mobile Safari's innerHTML problem

One major frustration for mobile HTML5 app developers is that innerHTML in Mobile Safari stops working randomly - often at the most inconvenient times. I met the problem as the PhoneGap project scaled up in complexity in the recent months and random rendering bugs appear more and more often in my app.

There's a clear problem description in this 4-year-old blog page:

The code has been in use for several months without issue and has been working fine all this time in all the browsers I’ve tried it in. For some reason in this one particular scenario though, Safari was completely ignoring the attempts to set the innerHTML of the node. Setting the innerHTML and then on the following line attempting to read it was also giving an empty response. For example:

text = "foo";
node.innerHTML = text;
alert( "html="+node.innerHTML ); // Pops up message saying "html="

I tried numerous methods to fix this problem, including setting the property before and/or after adding the DOM node to the document. I also googled it which flagged up a number of related posts but these generally referred to pages that were served as XHTML (ie. pages with an .xhtml extension and MIME type application/xhtml+xml) and were not offering any solution.

The general consensus towards working around this problem is to set a timer to retry setting the innerHTML value again. (e.g. as mentioned in this page) Such a solution is highly inconvenient and is generally inapplicable, however, because it screws up your programming model - you essentially need to turn the simple operation of setting innerHTML into a callback-like asynchronous operation.

A Probable General Solution

I found an alternative solution yesterday when I noticed that, at the moment innerHTML stops working in Mobile Safari, DOM manipulation operations, like..
element.appendChild(document.createTextNode("Baby you're a firework!"));
.. would still work. What this means is, if you're able to translate the set innerHTML operation into purely DOM manipulation operations, you'd be able to circumvent the problem entirely - in theory.

To do this, I'd need to be able to parse the HTML in JavaScript. That's actually easier than most people think - I've worked with such a parser during my CKEditor days, and it was mostly just a bunch of regular expressions. Even better, there's a standalone JavaScript library for doing HTML parsing written by John Resig.

With that in mind, I wrote a simple JavaScript function that turns an HTML string into a DOM fragment - which in mathematical terms, is simply a forest.


Then, as I'm using jQuery Mobile, I can patch it into jQuery's html function:


Done!

1 comments:

Daniel said...

A million thank-yous! This spared me many hours.

For the record, this is how I cope with this problem: http://blog.danieljanus.pl/meet-my-little-friend-createtree