In IE4 the only way to create elements where to insert a html string that contained the markup needed for the element. This method is pretty limited but in conjunction with the W3C DOM it is really useful. It is even so powerful that the Mozilla engineers agreed to add the IE proprietary property, innerHTML, after a lot of demands from the developer community. The problem is that innerHTML can hardly survive on its own. IE4 had other methods and properties to complement that but the Mozilla team decided not to include these.
Most of the logic in the emulation of the innerHTML model is done using the
W3C
DOM level 2 - Ranges, and a Mozilla proprietary method on ranges called
createContextualFragment. This method takes an HTML string and returns the
DocumentFragment that is needed to insert that HTML in the place of the range. I've
covered the ranges a little in my initial article covering
innerHTML for Mozilla. Even if innerHTML
now is natively supported in Mozilla it might be a good place to start.
This property is natively supported in Mozilla so there is no need to emulate it. Both setting and getting the property works.
This property allows non HTML to be inserted and then converted to entity references and BR elements. When getting the value new lines are included where the lines break are and entity references returned their textual value. The new line feauture is not available in this emulation but the markup is replaced with the needed entity references (< with <, > with > and & with &).
The setter just replace the special characters with the entity references and it uses
the innerHTML property to insert the tex.
HTMLElement.prototype.__defineSetter__("innerText", function (sText) {
this.innerHTML = sText.replace(/\&/g, "&").replace(/</g, "<").replace(/>/g, ">");
});
The getter uses the toString method of the range interface so what we
need to do is to make the range cover the nodes that we want to get the text of.
This is done with the selectNodeContents method:
HTMLElement.prototype.__defineGetter__("innerText", function () {
var r = this.ownerDocument.createRange();
r.selectNodeContents(this);
return r.toString();
});
This is a property that one can discuss whether allowing it to be set is good or not. When set it replaces the element with a new document fragment and that means that the old element is removed/destroyed as well. Getting the property on the other hand is really useful and it allows you to get a dump of the HTML that the element represent.
The idea here is to generete the string needed for the start tag by looping through all the attributes to get their name and value. Then we add the innerHTML string and finally add the end tag (if needed). The tags that are always empty are declared in a simple hash set structure (if I forgot any that you need, please add them).
var _emptyTags = {
"IMG": true,
"BR": true,
"INPUT": true,
"META": true,
"LINK": true,
"PARAM": true,
"HR": true
};
HTMLElement.prototype.__defineGetter__("outerHTML", function () {
var attrs = this.attributes;
var str = "<" + this.tagName;
for (var i = 0; i < attrs.length; i++)
str += " " + attrs[i].name + "=\"" + attrs[i].value + "\"";
if (_emptyTags[this.tagName])
return str + ">";
return str + ">" + this.innerHTML + "</" + this.tagName + ">";
});
For the setter we create a range and use createContextualFragment to get
a document fragment that we replace the current element with.
HTMLElement.prototype.__defineSetter__("outerHTML", function (sHTML) {
var r = this.ownerDocument.createRange();
r.setStartBefore(this);
var df = r.createContextualFragment(sHTML);
this.parentNode.replaceChild(df, this);
});
This property is very similar to innerText, in fact for the getter we
reuse the function in the getter for innerHTML.
Same as for innerText.
The code for this is almost the same as for innerText but we use
outerHTML instead of innerHTML.
HTMLElement.prototype.__defineSetter__("outerText", function (sText) {
this.outerHTML = sText.replace(/\&/g, "&").replace(/</g, "<").replace(/>/g, ">");
});
This method allows you to insert HTML into you document without destroying any old
elements or reparsing any old HTML code. This method is the loyal companion of the
innerHTML property and without it the innerHTML model is really crippled.
This method takes two arguments and the first one describes the location relative to the element and the second is the HTML to insert.
Once again we use a range and the method createContextualFragment to
create a DocumentFragment that is later inserted at the desired location.
There are some range handling done here but it is fairly straight-forward if you have
used ranges a few times.
HTMLElement.prototype.insertAdjacentHTML = function (sWhere, sHTML) {
var df; // : DocumentFragment
var r = this.ownerDocument.createRange();
switch (String(sWhere).toLowerCase()) { // convert to string and unify case
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;
}
};
This is the text version of the method insertAdjacentHTML and once
again we just replace the extended characters and reuse the HTML counterpart method.
HTMLElement.prototype.insertAdjacentText = function (sWhere, sText) {
sText = sText.replace(/\&/g, "&").replace(/</g, "<").replace(/>/g, ">");
this.insertAdjacentHTML(sWhere, sText);
};
To extend the HTMLElement object with the IE HTML Model you need to include the ie emu
file as well as call the emulateHTMLModel function.
<script type="text/javascript" src="ieemu.js"></script>
<script type="text/javascript">
if (moz) {
emulateHTMLModel();
}
</script>
To see this is action follow this link.
Mozilla has a bug that makes the ownerDocument return null before the
node has been inserted into a document
(Bug 27382). (The specs
clearly state that it should be set to the document used to create the node.) This
prevents all method and properties except innerHTML to work until the
element has been added to the document.
The implementation of the text properties and methods (innerText,
outerText and insertAdjacantText) are not identical to the IE
versions but close enough. Be cautious when you use these and make sure that no
problems arise.
Introduction
The power of JS
Event Listeners
Classic Event Handlers
Event Object
InnerHTML Model
Element Model
Document All Model
Current Style Model
???