Adding Methods to Native HTML Elements



We recently developed a function that returned all of the sub-elements of an element that used a specific CSS class. If you remember, one of the parameters passed was ElementRoot(the element to search from).

The built in method getElementsByTagName also works in a similar fashion, retrieving all sub-elements of a specified root element. But the syntax is somewhat different-instead of passing ElementRoot in as a parameter-getElementsByTagName is called as a method of the root element itself like this: Element.getElementsByTagName(TagName). How can we adjust our getElementsByClass to use a similar syntax?



In Firefox, this turns out to be ridiculously easy. If you retrieve an element from a page (using getElementsByTagName(), getElementById() or some other method) and you alert it, you'll see that Firefox says its of HTMLElement type. Firefox doesn't treat this type any differently than any other JavaScript object-which means you can simply add methods to the HTMLElement prototype to add your method to all HTML elements on any page. Our reworked getElementsByClass() is below:


HTMLElement.prototype.getElementsByClass = function (ElementType, ClassName)
{
var ElementArray = new Array();
var UnfilteredElements = new Array();

UnfilteredElements = this.getElementsByTagName(ElementType);
for (var i = 0; i < UnfilteredElements.length; i++)
{
if (UnfilteredElements[i].className == ClassName)
ElementArray[ElementArray.length] = UnfilteredElements[i];
}
return (ElementArray);
};


The problem here is that this doesn't work in most other browsers (notably Internet Explorer). Although we can't add methods to the prototype of HTMLElement in IE, we can add methods to individual elements. There's two ways to conquer this problem then-we can loop through all of the elements in the page (by going through the document.all array) and assign methods to each element, or we can override the existing DOM methods that retrieve elements and append our methods to each element as it is retrieved.

It doesn't seem too likely that scripts would access every element on a page(or even most of the elements) so for performance reasons, let's look at the second option.

First off, we'll store all of the methods that we want to add in an array and also add each to the HTMLElement.prototype if we're using Firefox. See below.


function ivie()
{
HTMLMethodArray = new Array();

/* Add Methods to HTMLElement */
this.addHTMLMethod = function(MethodName, Method)
{
HTMLMethodArray[MethodName] = Method;
var agt = navigator.userAgent.toLowerCase();
var is_safari = agt.indexOf('safari')!=-1;
if (!document.all && !is_safari)
HTMLElement.prototype[MethodName] = Method;
}

this.addHTMLMethod("getElementsByClass", function (ElementType, ClassName)
{
var ElementArray = new Array();
var UnfilteredElements = new Array();

UnfilteredElements = this.getElementsByTagName(ElementType);
for (var i = 0; i < UnfilteredElements.length; i++)
{
if (UnfilteredElements[i].className == ClassName)
ElementArray[ElementArray.length] = UnfilteredElements[i];
}
return (ElementArray);
});

/* Add Methods to HTML Element Instances (For Browsers Not Supporting HTMLElement) */
this.applyHTMLMethods = function(Element)
{
for (var i in HTMLMethodArray)
{
if (!Element[i])
Element[i] = HTMLMethodArray[i];
}
}
}

Ivie = new ivie();


In the above, we're creating an object called ivie that will hold our methods to add extra functionality to the DOM. In ivie, we declare a method to store the methods we want to add to the DOM, and then call it with the getElementsByClass() function. We also create a method for applying our new methods to an element.

Now, for the next step-overriding existing DOM methods.


/* Save References For Override */
document.ivGetElementById = document.getElementById;
document.ivGetElementsByTagName = document.getElementsByTagName;
document.ivCreateElement = document.createElement;

document.createElement = function(Tag)
{
var Element;

Element = document.ivCreateElement(Tag);

if (Element != null)
Ivie.applyHTMLMethods(Element);
return (Element);
}

document.getElementById = function(Id)
{
var Element;

Element = document.ivGetElementById(Id);
if (Element != null)
Ivie.applyHTMLMethods(Element);
return (Element);
}

document.getElementsByTagName = function(Tag)
{
var Elements;
var i;

Elements = document.ivGetElementsByTagName(Tag);
for (i = 0; i < Elements.length; i++)
Ivie.applyHTMLMethods(Elements[i]);
return (Elements);
}


We override some common DOM methods, but first create variables to hold references to the originals. You'll see that these variable references are saved to the document scope-some browsers will choke if you try to assign these methods to a different scope(I originally tried to place them as methods of the ivie object).

Each of the overridden methods then calls its original counterpart, but before returning its retrieved element (or array of elements as the case may be), it calls applyHTMLMethods to apply the extra methods that we stored earlier.

A few notes on this code-although we instantiate ivie with Ivie = new ivie()-this probably could have been done by making calls to the ivie object directly if the addHTMLMethod() and applyHTMLMethod methods had been added to ivie's prototype.

Secondly, although this works fine-this doesn't protect against direct element references (such as document.body-or any of the DOM properties such as firstChild etc...)

To fix this, instead of using DOM properties, equivalent methods could be created that would accomplish the same task as our overridden methods above(such as Element.firstChild()). This isn't as clean as we would like, but it may be the best solution without actually looping through every element on a page and making the assignments on an element by element basis.

If anyone has any questions, let me know!

1 comment :

Lidiya said...

This code will work in IE 10...