JavaScript: Accessing nodes, objects and properties–the existential problem

Just finished attending the 2007 Widget Summit. (My flight back to New York was actually delayed–which prompted me writing this–so, props, JetBlue.)

I have to say: the summit was lots of fun. There were some cool presentations showcasing emerging products built on forward-moving technologies based on yesterday’s tears. Who could ask for anything more. (That’s not really a question.)

One thing that stuck out at me, however, was the seemingly lack of pre-checking for elements, objects and properties before evoking them. (Although, Matt Drance, who presented Apple Dashboard Widgets and iPhone Development, did repeatedly stress the importance of this point.)

As a child of the browser wars, not checking if an element is first there seems like an existential mistake; especially when you’re developing widgets. Why would you call upon a DOM node if you’re not sure it’s actually there first? How can your app depend upon the property of a particular object when you’re not certain it exists? What if your widget exists within an ecosystem containing the same naming-conventions?

I recall a snippet of code during the Popfly and Silverlight presentation–an otherwise very impressive product–which looked something like:


component.getElementById("yourIdPlease").innerHTML="something";

Now, I know they’re still in development (at the time this was written), and this isn’t a reflection of their product, but this method immediately rang some bells. How do we know element “yourIdPlease” is there? Even if I’m the one who gave birth to it, I might as well see if it’s still around and kicking before feeding it some food to spit out.

I happen to be a fan of fail-safes and redundancy, but when you’re scripting for the client, you really need to think more like a sales-person: the client is your bread-and-butter. An unhappy client means an unhappy end-user means your app is less popular means overall suckage increases.

Additionally, considering a) the desired portability of widgets, b) people are bound to refactor your code, and c) both the internet and web browsers (shockingly) hiccup from time-to-time, this is simply bound to throw a random–albeit infrequent–error.

Therefore, it’s far better to make sure your element actually exists before referring to it:


if(component.getElementById("yourIdPlease")) {
    component.getElementById("yourIdPlease").innerHTML="something";
}

Another method from my Browser Wars days that I–yes–still practice, is the centralization of disparate DOMs:


var DOM=new Object();
DOM.getElementById=function(id) {
    if(document.getElementById && document.getElementById(id)) {
        return document.getElementById(id);
    }
    if(document.layers && document.layers[id]) {
        return document.layers[id];
    }
    if(document.all && document.all[id]) {
        return document.all[id];
    }
    return null;
}

I know some of you are cringing seeing document.layers and document.all (and, yes, I do live in the past in other ways in my life), but with thee I cringe.

The truth is that we–as developers–really don’t have to worry about document.layers and document.all these days. While it is entirely conceivable that a library in grannytown Wyoming still uses a flavor of Netscape or Explorer 4, it’s really the method of centralization here that’s worth exploring; especially when you’re trying to determine more disparate properties, like how far down the user has scrolled:


var Browser=new Object();
Browser.getScrollY=function() {
    if(window.pageYOffset) {
        return pageYOffset;
    }
    if(document.body.scrollTop) {
        return document.body.scrollTop;
    }
    if(document.documentElement.scrollTop) {
        return document.documentElement.scrollTop;
    }
    return null;
}

Or, find the x/y coordinates of a given element:


DOM.getPosX=function(id) {
    posX=0;
    el=DOM.getElementById(id);
    if(el && el.offsetParent) {
        while(el.offsetParent) {
            posX+=el.offsetLeft;
            if(!el.offsetParent) {
                break;
            }
        el=el.offsetParent;
        }
    }
    else if(el && el.x) {
        posX+=el.x;
    }
    return parseInt(posX);
}

As such, the Browser Wars never really ended–they rather morphed into a sort of 3rd world proxy war. Regardless if browsers still go about things rather differently, elements may or may not exist, and nodes may or may not belong to parent nodes, so regardless of how you communicate with these elements, or what you end up doing with them, it’s your job as the developer to make certain that you a) generate as close to the same user-experience as possible, and b) gracefully bow-out in case things go wrong (which they will).

So, it’s better to check if elements exist first. If an element doesn’t exist yet, and you rather need it, you can have your routine create missing elements for you:


DOM.getElementById=function(id, tag) {
    if(document.getElementById && document.getElementById(id)) {
        return document.getElementById(id);
    }
    if(document.layers && document.layers[id]) {
        return document.layers[id];
    }
    if(document.all && document.all[id]) {
        return document.all[id];
    }
    if(document.createElement) {
        tag=(tag)? tag : 'div';
        el=document.createElement(tag);
        el.setAttribute('id', id);
        document.getElementsByTagName('body')[0].appendChild(el);
        return el;
    }
    return null;
}

That way, you’re always guranteed an element.