XHTML document.write()
Support
Update 6/5: Made modifications to HTMLParser
to support more usages,
and reverted the changes to it that turned the local variables into member properties since it wasn't necessary.
Update 6/4: Added discussion about supported usages.
Google's AJAX APIs provide some incredible tools which equip web developers to do some amazing things. I've lately been dazzled by the power and accuracy of their
AJAX Language API and also of the performance and convenience afforded
by their AJAX Libraries API. The only issue I have with their APIs is that the fundamental
google.load()
function uses document.write()
to output the necessary script
elements to the DOM,
which doesn't even appear to be necessary since google.setOnLoadCallback()
executes after the scripts are loaded without using document.write()
(but instead appended document with DOM methods). And the reason why using document.write()
is bad, of course, is that it is not available when in XHTML.
Likewise, Google's AdSense program provides a great way for web authors to make get some compensation for their hard work.
But it too relies on document.write()
to output the necessary iframe
element to display the
advertisement. This has been well noted, and a workaround has been
developed which utilizes the an object
element. However, there is another solution which enables AdSense to work in XHTML
without any HTML workaround, and which allows web authors to use the Google Ajax APIs in XHTML pages: simply define and implement
document.write()
yourself, as this script does.
When I set out to do this, somehow I completely missed a solution
developed by John Resig (one of my biggest JavaScript heroes).
My solution, however, has a couple advantages as I see it. First, his solution uses some regular expression hacks to attempt to make the HTML markup well-formed enough
for the browser's XML parser, but as he notes, it is not very robust. Secondly, John's solution relies on innerHTML
which causes it to completely
fail in Safari 2 (although this implementation also fails for an unknown reason). I'm trying a different approach. Instead of using innerHTML
, this implementation of document.write()
parses the string argument of HTML markup into DOM nodes;
if the DOM has not been completely loaded yet, it appends these DOM nodes to the
document immediately after the requesting script
element; otherwise, it appends the parsed nodes to the end of the body
.
I've incorporated John Resig's own HTML Parser (via Erik Arvidsson), but
I've made a couple key modifications to make it play nice with document.write()
. I turned HTMLParser
into a
class with member properties in order to save the end state of the parser after all of the buffer has been processed.
To this class I added a parse(moreHTML)
method which allows additional markup to be passed into the parser for handling so that it can continue parsing from where it
had finished from the previous buffer. And by removing the last parseEndTag()
cleanup call
(for document.write()
is anything but clean), it then became possible for multiple document.write()
calls to be made with arguments consisting of chopped up HTML fragments like just a start tag or end tag, which is exactly what
AdSense does and is a common usage of the method.
Now for some examples, which of course will work when the document is served as either application/xhtml+xml
and text/html
, as in the case of MSIE. First is the canonical AdSense script
elements which output an advertisement:
And to demonstrate handling arbitrary HTML elements with text and comment nodes:
And to illustrate the use of the Google AJAX APIs, the phrase “Hello world!” (loaded from an
external script,
whose calling script
element is itself output by document.write()
)
is translated into Spanish via Google's AJAX Language API: Loading...
The Language API is loaded via google.load('language', 1)
and its completed load-state is detected by google.setOnLoadCallback()
.
The completed load-state of the sourceText
variable in the external script is then detected with this anonymous polling function:
(function(){
if(window.sourceText){
//ready to work!
}
else
setTimeout(arguments.callee, 10);
})();
This document.write()
implementation is known to work at least in Firefox 2/3, Opera 9.26, and Safari 3.
It will work in Internet Explorer, of course, since the document must be served as text/html
to be viewed and so it will already have document.write()
.
For some unknown reason, this currently does not work in Safari 2:
the error “TypeError - Undefined value” is raised on the line of HTML where a script
element loads xhtml-document-write.js
.
Any help would be much appreciated. As a workaround in the mean time, simply serve documents as text/html
for Safari 2 browsers.
Supported usages: There are three common usages of document.write()
in the wild of HTML,
and the first two are currently supported:
-
Outputting a well-formed HTML code fragment:
document.write('<p>Hello <i>World</i>!</p>');
This usage is fully supported by this implementation.
-
Outputting a well-formed HTML code fragment spread out over multiple sequential function calls:
document.write('<p>'); document.write('Hello <i>World</i>!'); document.write('</p>');
This usage is also supported
-
script
elements with function calls outputting HTML fragments interspersed by arbitrary HTML elements:<script type="text/javascript">document.write('<b>');</script> Hello <i>World</i>! <script type="text/javascript">document.write('</b>');</script>
This is not supported. Instead of outputting “Hello World!”, this implementation would output one empty
b
element, followed by just “Hello World!” This usage is more difficult to support although I have an idea of how to do it, but I may not end up implementing it unless there is demand for it.
One restriction, of course, to the use of this implementation is that the entire document must be well-formed XHTML
without regard for the markup output by the calls to document.write()
; thus you cannot do something like this
(via Ian Hixie):
<foo>
<script type="text/javascript" xmlns="http://www.w3.org/xhtml/1999"/><[!CDATA[
document.write('<bar>');
]]></script>
</bar>
</foo>
Download (or link to) the script source
or the minified version (via Dojo's ShrinkSafe)
(except ShrinkSafe currently breaks it).
Please comment with any suggestions or feedback!
This emerged while working for my employer Shepherd Interactive.