Building Dynamic HTML the Proper Way
How many times have you found yourself using Microsoft proprietary code in your web pages? As an example I am thinking of the use of the IE only JavaScript properties such as innerHTML and innerText. They are irresistibly simple and easy to use. I have to admit I've used them more than a few times. But always in a strictly IE-only environment. Even then I had feelings of guilt about doing so. It's all very well relying on them in this situation but bad practice like this is never good in the long-run. What we need to do is get used to using the DOM compliant methods instead. That's what I've started doing when working on client applications. An example is the code I used in the demo I published yesterday. When you use the Add Link button and select a link it is written back to the parent window's document using code that should work with all the browsers.
As you might expect the code itself is nowhere near as simple as that you might be used to. If you want to mimic the innerHTML property you need to carefully build the HTML element-by-element before carefully placing it inside the document's object model. This can require a lot of code but it's also rewarding to know you're doing it properly. Here's some example code:
//Function to insert a link in the DOM
function doAddLink ( title, unid ) {
var container = document.getElementById('related-articles');
var div = document.createElement('div');
var lnk = document.createElement('a');
var txt = document.createTextNode(title);
lnk.setAttribute('href', 'all/' + unid + '?OpenDocument');
lnk.setAttribute('target', '_blank');
lnk.appendChild(txt);
div.setAttribute('id',unid);
div.appendChild(lnk);
container.appendChild(div);
}
//Function to remove a specific link
function doRemoveLink ( unid ) {
var div = document.getElementById(unid);
div.parentNode.removeChild(div);
}
It might look complicated but the idea is simple. Create all the elements you need to build the link. Set the attributes of each, nest them according to the rules of the DOM and insert the result in to the document. To remove the link you simply get a handle on the node and remove it from its parent node.
Note: The latest versions of Mozilla-based browsers have bowed to public pressure and included support for these innerHTML type properties. Still it's a good idea to use the DOM compliant method if you want to support as many browsers as possible. You can see browser support for innerHTML et al.
I'm not sure if using exactly setAttribute() method in the example above, got any explanation, but why not use much more cleaner form:
lnk.href = 'all/' + unid + '?OpenDocument';
lnk.target = '_blank';
/* ... */
div.id = unid;
All of these atributes are available as a properties.
Have you tried to create a SELECT input yet using this method?
If so, could you set the text/values using setAttribute ?
Dot notation does not work in all browsers if the attribute does not already exist in the element node, whereas setAttribute() will create an attribute node if one does not exist.
For a <select> you need to create <option> nodes. These will actually contain the values/text as attributes. So you'd create a <select> node on the document, then, for each option:
create the <option> node using createElement()
set the value attribute using setAttribute()
fix it to the <select> node using appendChild()
The text would be handled using createTextNode, and that node would be glued to the <option> using appendChild() as well.
Thanks Stan. Meant to get round to answering that question but forgot all about it. Wasn't 100% sure but I was guessing what you've outlined there would be the way to go, having to create text elements for each of the options...
FYI, here's a JavaScript constructor I wrote to use insertAdjacentHTML() for Netscape browsers. (I have a constructor for innerHTML() at home, so I'll post it when I can find it.) Enjoy!
====================================
if (document.childNodes&&!document.childNodes[0].insertAdjacentHTML){
HTMLElement.prototype.insertAdjacentHTML = function (sWhere, sHTML) {
var df;
var r = this.ownerDocument.createRange();
switch (String(sWhere).toLowerCase()) {
case "beforebegin":
r.setStartBefore(this);
df = r.createContextualFragment(sHTML);
this.parentNode.insertBefore(df, this);
break;
case "afterbegin":
r.selectNodeContents(this);
r.collapse(true);
df = r.createContextualFragment(sHTML);
this.insertBefore(df, this.firstChild);
break;
case "beforeend":
r.selectNodeContents(this);
r.collapse(false);
df = r.createContextualFragment(sHTML);
this.appendChild(df);
break;
case "afterend":
r.setStartAfter(this);
df = r.createContextualFragment(sHTML);
this.parentNode.insertBefore(df, this.nextSibling);
break;
}
}
}
Yes, the DOM is pretty nice to have sometimes :) I really like:
document.getElementsByTagName("<tagname>")
or even better:
document.getElementById("<id>").getElementByTagName("<tagname>")
..to get a nice collection of wanted elements....
However, using the DOM is often a performance killer... in general is much slower than innerHTML.
IE, Mozilla and Safari (among others) all supports innerHTML, so if you plan to generate a lot of html fragments and know the User-Agents that will access the pages, it might we worth to test the different methods...
As part of their IEEmu library WebFX have a complete implementation of all the inner/outer properties & methods here :
{Link}
Including:
innerHTML
innerText
outerHTML
outerText
insertAdjacentHTML
insertAdjacentText
Of course that's if you want to go the MS way :)
Thanks Stan. It was the createTextNode part that I was missing !
Thanks Jake... I was immensly ignorant on this point. Those resouce links are very helpful too.
Cheers!