So here’s the situation: you want to syndicate some content using JavaScript to pull in the data (like AdWords or similar programs). The syndication script can’t know anything about the page it’s being used on — so it can’t have a dependency on the existence of particular elements. Yet the host page needs to be able to control where the content is inserted — the syndication script needs to insert the content wherever the <script> tag is.
How do you do it?
Well you probably do what Google does and use document.write. But document.write is not a nice technique, it has some notable issues:
document.writedoes not work in XHTML mode (on XHTML pages served as XML)- content written in with
document.writemay not subsequently appear in the page’s DOM, which means no further access to manipulate it programmatically, and no access to accessibility APIs document.writeis conceptually wrong because it treats nodes as serialized text — which they’re not — they’re nodes
But what’s the alternative? DOM creation techniques need an existing element reference to work with; even innerHTML needs to know where to write the HTML. And in both cases, simply appending to <body> is not an option if you want the new content to appear inline with the existing content.
I was faced with this dilemma a few days ago, until an obvious solution dawned on me — we do in fact have a predictable refence: the <script> element itself!
All we need is a way of identifying the including <script> in the DOM, and then we can use the insertBefore method to append new HTML directly before it.
So, given a syndication script with a fixed ID:
<script type="text/javascript" id="syndication" src="syndication.js"></script>
We can go from oldskool nastiness like this:
document.write('<p id="syndicated-content">Here is some syndicated content.</p>');
To modern loveliness like this:
var newcontent = document.createElement('p');
newcontent.id = 'syndicated-content';
newcontent.appendChild(document.createTextNode('Here is some syndicated content.'));
var scr = document.getElementById('syndication');
scr.parentNode.insertBefore(newcontent, scr);
We could even go a step further and remove the <script> ID, but in that case we would need a concrete method for identifying the specific element. We could do that by knowing its SRC:
var scripts = document.getElementsByTagName('script');
for(var i=0; i<scripts.length; i++)
{
if(scripts[i].src == 'http://www.mydomain.com/syndication.js')
{
//scripts[i] is the one
break;
}
}
And there you have it – a simple but elegant way of inserting content in place, removing the last vestige of need for document.write!
Related posts:
- Techy Treasures #4: What’s inside a dollar function? The $ function is a common feature of all of...
- Google Closure: How not to write JavaScript What if Google released a JavaScript library that sucked, and...
- How to Write a Cookie-less Session Library for JavaScript Craig provides the code for a stand-alone JavaScript session variable...
- UK MoD “How to Stop Leaks” Document is Leaked! The UK Government and the Ministry of Defence faces further...
- HTML 4 Considered Harmful In this post, a frustrated James once again rallies to...







This is one of those forehead-smackingly obvious solutions you wonder why nobody thought of sooner. Wicked stuff, James!
Now if only we could get Google to adopt this in AdSense. It would be far better than the hacks people are currently using to support
document.writein XHTML.July 11th, 2007 at 5:15 pm
Better still, if you enumerate document.getElementsByTagName(’script’), the last one in the array is the script element you’re in.
July 11th, 2007 at 5:17 pm
The id should be more unique. “syndication” is a bad example. Some people like to clutter their site with more than one external script element.
July 11th, 2007 at 5:34 pm
@Stuart: D’oh of course! If the script is non-deferred, then the DOM will stop building at that point until it’s processed, so by definition it will be the last member in the scripts collection. Nice one :)
[EDIT: apparently that's not the case in XHTML mode - according to the page that Kevin linked to, in XHTML mode the DOM is created first and then scripts are processed]
Another idea I had was to pre-process the script itself with PHP, then it would have a reference to its own SRC without you having to know it in advance:
But that of course is less than ideal since it’s not a pure JS solution.
@Stefan: sure, it’s an over-simplified example :)
July 11th, 2007 at 6:02 pm
Yep, as the page I linked to explains, there are plenty of ways to do without
document.write, but none of the obvious ones work in current browsers’ XHTML DOMs. That’s why James’s proposal is so inspired.July 11th, 2007 at 9:02 pm
@brothercake: That will only help you if you’re actively trying to not use document.write() from the start (which, quite frankly, is trivial to do in this day and age). The larger issue is dealing with scripts that use document.write(), causing it not to work in XHTML documents.
As Stuart mentioned, getElementsByTagName(”script”) does work – and it works in XHTML documents, it just has issues in Firefox. You can read my full analysis on the issue, and working solution for Google Adsense (and Google Maps) here – working in all modern browsers that support XHTML served with the correct mimetype:
http://ejohn.org/blog/xhtml-documentwrite-and-adsense/
July 12th, 2007 at 1:40 am
Ha! I knew someone had written this up recently but couldn’t remember who; it was John :-)
July 12th, 2007 at 1:51 am
Hey, I suggest that running code inline is generally bad practice – especially code which accesses or modifies the DOM. Initialising data structures, objects etc seems to be fine – but anything else should be deferred using onload or some similar technique.
I use the following:-
Then, in any included script file or code block I put as the last line:-
Where “
thisInit” is the name of a initialisation function.I refer to this as “registration”. If you wanted to be really elegant, I guess you could wrap it in a class – perhaps using a custom “onload” object something like:-
the body tag then includes the following:-
or something similar, which simply iterates over the array, calling each function thus:-
There are things to consider if you are working with someone else’s approach to onload handlers, where you might need to do the horrile:-
oldOnload = document.body.onload;
document.body.onload = myNewOnload();
in which case you’d simply need to do this once – attaching your nice scaleable solution. I suggest that this is a scaleable solution which works with any number of external libraries or code blocks.
Further, and more specifically to this situation, I’d suggest that tagging each code block so it can look for itself in the DOM is a really good idea – a naming convention which allows you to use a simple regexp to pull out the id of the code block your code is in. I have been using the scripts[scripts.length-1] approach up to now, and wasn’t aware of the xhtml limitation – thanks for that!
badcop666_at_hot_mail_dot_com
July 12th, 2007 at 9:41 am
@jeresig:
I agree that this largely comes down to an advocacy issue. If we can convince the Googles of the world to abandon
document.write()and use clean solutions like the one in this post, it will solve a lot of problems.July 12th, 2007 at 10:28 am
It’s so simple it just might work! Nice.
July 12th, 2007 at 10:07 pm
This might be an issue for some; in HTML 4.01 documents (not XHTML),
ID is not a valid attribute for the SCRIPT element.
July 13th, 2007 at 2:42 am
Is it so hard to have a
<script...>>/script<<div id="syndication"><div>?This way not only does it comply to HTML 4.01 but it also lets one put the “script” part in the page header.
July 13th, 2007 at 7:38 am
(Obviously intended code was
<script ...></script><div id="syndication"></div>)July 13th, 2007 at 7:42 am
@Sunny, sure that’s an option, but I was looking for something that doesn’t place an additional requirement on the host (even a tiny one)
My original purpose for this was content syndication for the upcoming contests and marketplace re-launch, and a way of syndicating that was so simple, all we had to say to people is “put this script on your page”. So I went for the hybrid PHP solution, where the script knows its own URL definitively (including any query parameters), and hence no additional elements are required and the script doesn’t even need an ID.
July 13th, 2007 at 10:38 am
Better yet — stop wasting your time with the effectively dead technology that is xhtml. It was an interesting idea, but lets face it — without IE support, you’re going to be serving html4 anyway. HTML5 will be supported before xhtml 1.0 is.
July 15th, 2007 at 6:38 pm
XHTML in IE is just HTML4 with extra slashes – sure.
But in decent browsers it’s XML. And guess what – it’s the same markup.
XHTML works fine; as a transitional stage from SGML-style markup that’s needlessly complex to parse and error-correct, to XML-style markup that’s simple and easy to parse; and as the XML itself.
July 16th, 2007 at 10:51 am
We never had these problems with ASCII. :P
EOF
July 16th, 2007 at 11:28 am
I do it a bit differently because an ID isn’t legal in a script tag. Also, when using 3rd party scripts, you get into trouble when they have multiple document.write()s per provider. (http://arapehlivanian.com/2006/05/12/documentwrite-fix-for-real-xhtml/)
July 20th, 2007 at 11:13 pm
This is great, but if my text is HTML, it doesn’t render.
How would you apply a css class to the new text?
July 21st, 2007 at 12:41 pm
yeah, i have to go figure out how to apply class and for attributes… guessing theres a namespace problem, i hope there is a way around.
January 21st, 2008 at 6:44 am
@above: nodeName.setAttribute(’for’,'text’)
February 16th, 2008 at 5:11 am
Hi. This is really interesting post. Thank You! I have just subscribed to Your rss!
Best regards
May 25th, 2008 at 7:37 pm
page content addition
function addHTML (html) {
if (document.all)
document.body.insertAdjacentHTML(’beforeEnd’, html);
else if (document.createRange) {
var range = document.createRange();
range.setStartAfter(document.body.lastChild);
var docFrag = range.createContextualFragment(html);
document.body.appendChild(docFrag);
}
else if (document.layers) {
var l = new Layer(window.innerWidth);
l.document.open();
l.document.write(html);
l.document.close();
l.top = document.height;
document.height += l.document.height;
l.visibility = ’show’;
}
}
<input type=”button”
onclick=”addHTML(’‘ + new Date() + ”);”
value=”add current date”
/>
July 8th, 2008 at 7:15 pm
James, great stuff….
However i’m wondering using this option how do i call for example a CSS file for that piece of script.
Thanks
November 14th, 2008 at 7:29 am