Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How to Get a Cross-Browser Reference to an HTML Object

0.00/5 (No votes)
29 May 2005 1  
The article describes a method to retrieve HTML object references in a cross-browser manner. It also discusses how to write object oriented JavaScript.

Introduction

This article demonstrates the use of one of the JavaScript classes of my DHTML API. You can see the API in action at:

Both the sites and the documentation are in Turkish however :(

If you consider using the API, the code is distributed under a 'Creative Commons Attribution-NonCommercial-Share Alike 2.0' license. That is to say its non-commercial use is free, if you give me credit and provide me with feedback or updated code (email: volkan.ozcelik@gmail.com; see terms of use for details).

Like any developer, I'd be glad when you give credit to me after using my code/know-how. However what I mostly want is to share knowledge, help others improve, and listen to others' comments so that I can improve myself.

I'm planning to make an English version of the site as well. But I have other issues of higher priority.

Purpose of this article

In this article we will be creating an object that will return a cross-browser reference to the elements in an HTML page.

We will be writing JavaScript in an object oriented manner. We will see examples on function prototyping. As we will see, JavaScript functions are not mere pieces of procedure. In JavaScript, to be more technical - in ECMAScript, functions can be considered as objects. They can have their own methods, member variables etc..

The skeleton

Let us begin with a prototype model for our object. We will name it CBObject, an abbreviation for cross-browser object.

function CBObject(elmID) {
    ...
}

CBObject.prototype.exists = function() {
    ...
};

CBObject.prototype.getObject = function() {
    ...
};

CBObject.prototype.getValidator = function() {
    ...
};

CBObject.prototype.getID = function() {
    ...
};

The CBObject(elmID) can be considered as the constructor. It takes the ID of the object on the HTML page (elmID) as a string and seeks the page to check whether there exists such an object.

  • Method exists returns true if such an object is found, false otherwise.
  • Method getObject returns a reference to that object.
  • Don't worry about the getValidator, it returns a ref to a Validator object which I will explain shortly.
  • And getID returns the ID of the object as you can guess.

Swapping the code

Now let us swap things around:

var g_blnCBObjectProtoCalled = false;
function CBObject(elmID) {
    if(!g_blnCBObjectProtoCalled) {
        g_blnCBObjectProtoCalled = true;
        var _this = CBObject.prototype;

        _this.prototype.exists = function() {
            ...
        };

        _this.getObject = function() {
            ...
        };

        _this.getValidator = function() {
            ...
        };

        _this.getID = function() {
            ...
        };
    }//if prototype not called

}//CBObject

Modifying the code this way is my design preference. In my humble opinion, it has two advantages.

  • First, the code much more resembles a class.
  • Second, if the CBObject is never constructed with new, the prototypes will not be attached to the object since they appear in the function body in contrast to the previous code where they were outside the function body.

We use the variable g_blnCBObjectProtoCalled to ensure that the prototypes are attached once and only once (i.e., when the object is constructed for the first time).

The validator

Validator object is the utility class for validating objects. Here is the complete list of methods it includes:

isDefined(x) // returns true if x is not undefined.

    
isEmpty(x)   // returns true if x is an empty string.


isEmail(x)   // does a regexp check to see if x represents a valid e-mail.


isInteger(x) // returns true if x represents an integer.


isFloat(x)   // returns true if x can be parsed as float.


isString(x)  // returns true if x is an instance of a String.


isDate(intYear, intMonth, intDay) // returns true if the parameters represent

                                  // a valid date.


getStatusCode() // returns 0 if the former validation is OK.

                // returns a non-zero number otherwise.

You can find the entire code in the source zip. For our discussion, we will be interested in two specific methods:

function Validator() {

    ... truncated ...

    _this.isDefined = function(x) {
        if(typeof(x) == "undefined") {return false;}
        return true;
    };

    _this.isString = function(x) {
        if(!this.isDefined(x)) {return false;}
        if(typeof(x) == "string") {return true;}
        return false;
    };

    ... truncated ...
}

String prototype helpers

The Validator class uses several String prototypes. I'm giving them for the sake of completeness.

var _spt = String.prototype;

_spt.trim = function(blnIgnoreCarriage, blnIgnoreInnerWhiteSpace) {
    var temp = this.replace(/^\s*/,"");
    temp = temp.replace(/\s*$/,"");

    blnIgnoreCarriage = blnIgnoreCarriage ? true : false;
    blnIgnoreInnerWhiteSpace = blnIgnoreInnerWhiteSpace ? true : false;

    if(blnIgnoreCarriage && blnIgnoreInnerWhiteSpace) {;}
    else if(blnIgnoreCarriage&&!blnIgnoreInnerWhiteSpace) {
        temp = temp.replace(/\t+/g," ");
        temp = temp.replace(/ +/g," ");
    }
    else if(!blnIgnoreCarriage && blnIgnoreInnerWhiteSpace) {
        temp=temp.replace(/(\n\r)+/g,"");
    }
    else if(!blnIgnoreCarriage && !blnIgnoreInnerWhiteSpace) {
        temp=temp.replace(/\s+/g," ");
    }

    if(temp==" ") { temp=""; }

    return temp;
};

_spt.match = function(strRegExp, strOption) {
    var regEx = new RegExp(strRegExp, strOption ? strOption : "g");
    return this.match(regEx);
};

_spt.remove = function(strRegExp, strOption) {
    var temp = this;
    var regEx = new RegExp(strRegExp, strOption ? strOption : "g");
    temp = temp.replace(regEx, "");
    return temp;
};

_spt.removeTags = function() {
    var regEx = /<[\/]?([a-zA-Z0-9]+)[^>^<]*>/ig; /*any tag*/
    return this.replace(regEx,"");
}

_spt.test = function(strRegExp, strOption) {
    var regEx = new RegExp(strRegExp, strOption ? strOption : "g");
    return regEx.test(this);
};

How to find the object reference

In the light of the above descriptions, we are now ready to focus our attention on CBObject.

Let us begin by initializing the member variables of CBObject:

function CBObject(elmID) {
    if(!g_blnCBObjectProtoCalled) {

        ... truncated ...    
    }

    this._val = new Validator();
    this._obj = this._getObject(elmID);
}

A notational convenience I use is the underscore. For methods that are not supposed to be used outside the object, I prefix them with an underscore. That is, method names that begin with an underscore (_) are intended to be used only within the object. (We can say that they are private methods of the object, and variable names that begin with underscore are private members.)

In the code above, the variables _val and _obj, and the method _getObject are intended to be private to CBObject.

The variance _val is a reference to an internal Validator object. And _obj is a reference to an element with ID elmID that exists within the HTML body.

And here comes the heart of the construct that does most of the work:

_this._getObject = function(elmID) {
    var v = this.getValidator();

    if(!v.isString(elmID)) {return elmID;}

    if(document.getElementById) {elmID = document.getElementById(elmID);}
    else if(document.all) {elmID = document.all[elmID];}
    else if(document.layers) {elmID = this._getLayer(elmID);}
    else if(document.forms) {
        if(document.forms[elmID]) {elmID = document.forms[elmID];}
        else {
            for(var i=0; i<document.forms.length; i++) {
                if(document.forms[i][elmID]) {
                    elmID = document.forms[i][elmID];
                    break;
                }
            }
        }
    }
    else {elmID = null;}

    return elmID;
};

Method getValidator simply returns the member Validator instance _val that is mentioned a few paragraphs above.

Let me try to explain what _getObject does step by step:

  • If elmID is not a string then assume that it is an object reference and return it immediately.
  • Else try to get the reference with W3C DOM function getElementById in a cross browser manner. Latest versions of most browsers support getElementById.
  • Else try to get it the IE way by using document.all which only IE understands.
  • If you still fail to get the object reference, try iterating through the document's layers, which only archaic versions of Netscape (i.e. NS4-) understand. Here _getLayer function scans all the layers to see whether elmID exists in one of them.

Since layers can contain other layers, the _getLayer method should be called recursively. Here is how it is done:

_this._getLayer = function(elmID,objRoot) {
    var i = 0;
    var objLayer = null;
    var objFound = null;

    if(!objRoot) {objRoot = window;}

    for(i=0; i<objRoot.document.layers.length; i++) {
        objLayer = objRoot.document.layers[i];

        if(objLayer.id==elmID) {return objLayer;}

        if(objLayer.document.layers.length) { 
            objFound = this._getLayer(elmID,objLayer);
        }

        if(objFound) {return objFound;}
    }
    
    return null;
};

Although _getLayer method will never be used, I think it is worth examining how the recursion is implemented.

If none of the above steps find the object, then the object can be a form element. So for a last hope, we iterate the forms collection. If we still fail to find anything, we return null.

Test what we have done

The TestCBObject.html bundled with the source zip includes a simple test case.

Here is what is in the Head of the document:

<script type="text/javascript" src="String.js"></script>
<script type="text/javascript" src="Validator.js"></script>
<script type="text/javascript" src="CBObject.js"></script>
<script type="text/javascript">
function checkForObjects() {
    var MyLayer = new CBObject("MyLayer");
    var FormInput1 = new CBObject("FormInput1");
    var FormInput2 = new CBObject("FormInput2");
    var NonExistent = new CBObject("NonExistent");

    if(MyLayer.exists()) {
        MyLayer.getObject().style.backgroundColor = "#cc0000";
    }
    else {
        alert("MyLayer does not exist");
    }

    if(FormInput1.exists()) {
        FormInput1.getObject().style.backgroundColor = "#cc0000";
    }
    else {
        alert("FormInput1 does not exist");
    }

    if(FormInput2.exists()) {
        FormInput2.getObject().style.backgroundColor = "#cc0000";
    }
    else {
        alert("FormInput2 does not exist");
    }

    if(NonExistent.exists()) {
        NonExistent.getObject().style.backgroundColor = "#cc0000";
    }
    else {
        alert("NonExistent does not exist");
    }
}
</script>

And here is the body:

<div id="MyLayer">My Layer</div>
<form id="MyFirstForm" method="post" action="#" onsubmit="return false;">
       <fieldset>
        <legend>first form</legend>
        <input name="FormInput1" />
    </fieldset>
</form>

<form id="MySecondForm" method="post" action="#" onsubmit="return false;">
    <fieldset>
        <legend>second form</legend>
        <input name="FormInput2" />
    </fieldset>
</form>

<form id="ActionForm">
    <div>
    <input type="button" value="click me" onclick="checkForObjects();" />
    </div>
</form>

Once you click the button, checkForObjects() will be executed. It will check several objects for existence, if it finds one it will change the background color of that object to red. If it fails to find, it will alert that it couldn't.

Conclusion

In this article, we tried to retrieve a cross-browser reference to an HTML DOM object, in the meantime we looked at how prototype-based, object oriented JavaScript code can be implemented. I hope, during the discussion I didn't jump here and there a lot.

Writing cross-browser JavaScript can be quite challenging if you span a wide range of target browsers and platforms. There are still users in this world who are using browsers of pre-historic times (NS3 for instance). For sure, they have their reasons to do so.

Note to the reader

To be honest, writing this cross-browser code made the API far too complicated and bloated.

Since almost any version 4+ browser (NS7+, IE5.5+, Mozilla1.3+, Opera7.5+, Safari X+ ..) supports W3C DOM, I decided to remove pre-DOM cross-browser support from the API, and decided to add new widgets that utilize DOM.

I realized that supporting Flintstone-browsers to the best extent resulted in a switch-case mambo-jambo and a spaghetti code where even I found myself lost!

Therefore, I decided not to write code for browsers which do not understand DOM. May be it's a radical change but the time for it has already come.

See how the above messy _getObject is simplified if we choose that course of action:

_this._getObject = function(elmID) {
    if(!new Validator().isString(elmID)) {return elmID;}
    else {return document.getElementById(elmID);}
};

25 lines of code (including _getLayer stuff) with "if", "else-if" and "for" junk simply reduces to two lines! It is simple, it is fast, it is consistent, it is understandable and followable, and it is the standard way of doing it! To sum up, keep your code as little jumpy as possible. I don't say "write code for IEv7+ only". I just say, don't support old browsers just to show off.

You may think that a structure that supports all version2+ browsers throughout the globe as the most elegant coding manifest of all times. But I simply consider it as a pile of garbage. (I'd appreciate any thoughts on my decision as well, TIA.)

History

  • 2005-03-05
    • Article created.
  • 2005-03-07
    • Method _getLayer is altered a bit. Article changed accordingly.
  • 2005-03-22
    • "Note to the Reader" part added.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here