Introduction

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.

Ranges

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.

innerHTML

This property is natively supported in Mozilla so there is no need to emulate it. Both setting and getting the property works.

innerText

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 &lt;, > with &gt; and & with &amp;).

Setter

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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
});

Getter

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();
});

outerHTML

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.

Getter

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 + ">";
});

Getter

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);
});

outerText

This property is very similar to innerText, in fact for the getter we reuse the function in the getter for innerHTML.

Getter

Same as for innerText.

Setter

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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
});

insertAdjacentHTML

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.

Implementation

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;
   }   
};

insertAdjacentText

This is the text version of the method insertAdjacentHTML and once again we just replace the extended characters and reuse the HTML counterpart method.

Implementation

HTMLElement.prototype.insertAdjacentText = function (sWhere, sText) {
   sText = sText.replace(/\&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
   this.insertAdjacentHTML(sWhere, sText);
};

Usage

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>

Demo

To see this is action follow this link.

Issues

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
???

Author: Erik Arvidsson