View RSS Feed

molendijk

Some Remarks about Ajax-includes using document.write

Rating: 10 votes, 4.20 average.
Some Remarks about Ajax-includes using document.write

I. Why document.write?
Including external content with the help of document.write brings in external html AND external code (js, css). This is a clear advantage of document.write over other inclusion-methods, like innerHTML (does not execute scripts) and appendChild (does not automatically import all the external js and/or css when IE, Chrome or Opera are used). To see this, create a file external.html with some js in it, then try to include it using the following. You'll see that document.write makes the external code (code of the external file) work in all browsers (but see II), that innerHTML refuses to execute the external code in any browser, and that appendChild doesn't execute it in Chrome, IE and Opera:
Code:
<head>

<script type="text/javascript">
function HttpRequest(url){
var pageRequest = false //variable to hold ajax object
/*@cc_on
@if (@_jscript_version >= 5)
try {
pageRequest = new ActiveXObject("Msxml2.XMLHTTP")
}
catch (e){
try {
pageRequest = new ActiveXObject("Microsoft.XMLHTTP")
}
catch (e2){
pageRequest = false
}
}
@end
@*/

if (!pageRequest && typeof XMLHttpRequest != 'undefined')
pageRequest = new XMLHttpRequest()

if (pageRequest){ //if pageRequest is not false
pageRequest.open('GET', url, false) //get page synchronously
pageRequest.send(null)

//doing this for appendChild
var newdiv = document.createElement("div");
newdiv.innerHTML = pageRequest.responseText;
if(/*@cc_on!@*/false)
{newdiv.innerHTML.text= newdiv.innerHTML;}

document.write(pageRequest.responseText);
//document.body.appendChild(newdiv);
//document.body.innerHTML+=pageRequest.responseText;
}
}

</script>

</head>
<body > <script type="text/javascript">HttpRequest("external.html")</script>
So if we use Ajax to insert a HTML-menu into our page, (document.)writing the responseText seems to be a good option (see IV below, however, for some remarks on the 'evil nature' of document.write).

II. Problems with document.write
But using document.write across browsers is not without problems. There's a difference indeed between IE and non-IE as far as executing the imported scripts is concerned. In IE, external scripts are executed after internal scripts. So if we have
Code:
<script type="text/javascript">document.write('a')</script>
<script type="text/javascript" src="b.js"></script>
<script type="text/javascript">document.write('c')</script>
where b.js is supposed to (document.)write 'b', we get 'acb' in IE. But in non-IE, we get 'abc', because non-IE browsers simply execute scripts in the order in which they are presented, without distinguishing between internal and external scripts. Now, the non-IE way of executing scripts in includes that are done with document.write turns out to be non-problematic, whereas the IE way does produce unwanted results, see this. So we must force IE to behave like non-IE.

III. An easy solution
This can be done rather easily: all the scripts of the external file we want to include must be (made) either internal or external. Obviously, the second option must be preferred. This means that, if we want to include a file using document.write - whether we do it with Ajax or with the help of another technique - we must first make sure that all the scripts of the file_to_be_included are (made) external.

IV. But document.write is evil, isn't it?
I argued here that the use of a string (responseText, in this case) for representing not just text, but also code, is bad practice. When I wrote that page (about Segmented Includes) I wasn't aware of the fact that the problems I described there were due to the way IE handles internal and external scripts having document.write (see II-III above). Those problems seem to disappear when we do as indicated in III. This being said, I must admit that using strings (like the ones produced by document.write) for representing code will always be bad practice. But as longs as bad practice does not produce bad results, as in the cases described here, we can use it (reluctantly, maybe).

V. But document.write cannot be used for dynamic updates, right?
Document.write cannot be used for including external content after the page has loaded. So we can do <script type="text/javascript">document.write('something')</script> but not <script type="text/javascript"> function do_something(){document.write('something')}</script> and then call function do_something() with an onload, an onclick, an onmouseover, or whatever: in stead of adding something to the page, we would wipe out its entire content and simply replace it with something. So, if we are using document.write, we can't dynamically add something to our document (let alone put it at a certain position) after the page has loaded. But we can fake it, see this.

VI. Conclusive remark
So if you're happy with the (fake) solution given in V for dynamically updating your page with the help of document.write, use it. It has the clear advantage of automatically importing external js and css, and isn't perhaps that bad, if we take into account what has been said in III.

Addendum:
There are lots of Ajax-include scripts on the Internet. But many of them cannot be used locally (on the hard disk) when IE (8) is used. The one given above can be used locally. The script provided here by DynamicDrive cannot ('access denied'). Curiously, the following script, found at a page that favoribly refers to the DD-script, can be used locally (using IE8):
Code:
<script type="text/javascript">
/* found at http://www.javascriptkit.com/dhtmltutors/ajaxincludes.shtml */
function HttpRequest(url){
var pageRequest = false //variable to hold ajax object
/*@cc_on
@if (@_jscript_version >= 5)
try {
pageRequest = new ActiveXObject("Msxml2.XMLHTTP")
}
catch (e){
try {
pageRequest = new ActiveXObject("Microsoft.XMLHTTP")
}
catch (e2){
pageRequest = false
}
}
@end
@*/

if (!pageRequest && typeof XMLHttpRequest != 'undefined')
pageRequest = new XMLHttpRequest()

if (pageRequest){ //if pageRequest is not false
pageRequest.open('GET', url, false) //get page synchronously
pageRequest.send(null)
embedpage(pageRequest)
}
}

function embedpage(request){
//if viewing page offline or the document was successfully retrieved online (status code=2000)
if (window.location.href.indexOf("http")==-1 || request.status==200)
document.write(request.responseText);

}

</script>
DEMO HERE.
===
Arie.

Submit "Some Remarks about Ajax-includes using document.write" to del.icio.us Submit "Some Remarks about Ajax-includes using document.write" to StumbleUpon Submit "Some Remarks about Ajax-includes using document.write" to Google Submit "Some Remarks about Ajax-includes using document.write" to Digg

Updated 02-27-2010 at 06:32 PM by molendijk

Tags: None Add / Edit Tags
Categories
JavaScript & Ajax

Comments

  1. jscheuer1's Avatar
    Ideally the responseXML, not the responseText should be used with AJAX. However, IE handles this poorly if at all. One may convert the responseText to an XML document of sorts, but you still are dealing with a string.

    It's not document.write that is the real problem here, the innerHTML property has many of the same issues. Neither method would be required if the responseXML could reliably be used in IE. It is reliance upon the responseText that forces us to use string methods. The responseText is a string.

    If one is going to make one's scripts external anyway, one may simply create a DOM script tag, name the external script as its src attribute, and append the tag to the document. Scripts may be executed concurrent with/upon AJAX import in several ways. The one you mention is perhaps the least desirable. Another method is the jQuery .live() method. Yet another is to use event listeners/attachments in such a way as to ensure that imported content will react as desired without having to invoke a new initialization of the script each time new content for it arrives.
  2. molendijk's Avatar
    If one is going to make one's scripts external anyway, one may simply create a DOM script tag, name the external script as its src attribute, and append the tag to the document.
    That would be (almost) the same thing as (i) making the scripts external; (ii) removing them from the file we want to insert into the encompassing page (to avoid conflicts) and then (iii) putting them directly in the (encompassing) page. So that would mean that some extra steps should be made, since I only do (i). Right? (or wrong?).
    ===
    Arie.
  3. jscheuer1's Avatar
    I don't think so. My suggestion is:

    1. Make the script external (what you are doing anyway).
    2. Add the script to the page using the DOM.
  4. molendijk's Avatar
    I'm afraid I didn't explain very well what I had in mind.
    If we have something like this:
    Code:
    <head>
    <script type="text/javascript">
    function HttpRequest(url){
    var pageRequest = false //variable to hold ajax object
    /*@cc_on
    @if (@_jscript_version >= 5)
    try {
    pageRequest = new ActiveXObject("Msxml2.XMLHTTP")
    }
    catch (e){
    try {
    pageRequest = new ActiveXObject("Microsoft.XMLHTTP")
    }
    catch (e2){
    pageRequest = false
    }
    }
    @end
    @*/
    
    if (!pageRequest && typeof XMLHttpRequest != 'undefined')
    pageRequest = new XMLHttpRequest()
    
    if (pageRequest){ //if pageRequest is not false
    pageRequest.open('GET', url, false) //get page synchronously
    pageRequest.send(null)
    
    document.write(pageRequest.responseText);
    }
    }
    </script>
    
    </head>
    
    <body>
    
    <a href="javascript:void(0)" onclick = "document.getElementById('external').style.display = 'block'">external page</a> 
    
    <div id="external"  style="display:none;"><script type = "text/javascript">HttpRequest("external.html")</script></div>
    
    </body>
    then clicking on the link (faking an update of the page) gives us the text of the external page PLUS its code. So there's no need to add the script to the (main) page using the DOM.
    (In spite of this, if the external file is a HTML-menu depending partially on javascript, then it may not work well in IE if the menu contains a mixture of internal and external scripts. The workaround is to make all scripts (in the menu) external).
    We would only have to add the script to the main page if we used another inclusion method, like innerHTML or appendChild.
    ===
    Arie