ELO – Encapsulated Load Object – The ultimate way to handle window load events

Updated April 11th

Changed so it will work fine under https as well in Internet Explorer.

If you like this, you might also be interested in DOMAssistant.

Most people who have worked with JavaScript has cursed the time it takes to apply JavaScript to the document (events, for instance) because you’ve waited for the whole document to load. The problem is that we have relied on the onload event to be triggered and that doesn’t happen until all HTML code and every image and other dependency has loaded. In most cases, we want to have our scripts as soon as the DOM has finished loading and not wait for images and their likes.

Therefore, I have with great interest followed the work of Dean Edwards, together with Matthias Miller and John Resig, and the exciting conclusion they came to in window.onload (again).

I really like the gist of it and the implications it brings, but I wanted to make it more flexible for any number of load events. Therefore I created ELO – Encapsulated Load Object.

Description

The idea of ELO is to implement the DOM loading checking principles found out and tested by the gentlemen above, but then be able to call any number of functions you specify.

I usually work with large projects where there are a number of JavaScript files and web developers, and I’m not interesting in fighting with others over just one file where everyone want to add their onload code and functions. Instead, the idea of ELO is to give people the possibility of adding functions, from any file in the web site, to be called as soon as the DOM (i.e. just the HTML code) has loaded.

How to implement it

Basically all you need to do is download the ELO JavaScript file, include it in your web pagea, and then add the functions you want to be called to an array of the ELO object. First, include the file:

<script type="text/javascript" src="js/elo.js"></script>

Then, from the web page or any JavaScript file used, just add your desired functions to the functionsToCallOnload array:

ELO.functionsToCallOnload.push("myFunction()").

Note that the function you want to call should be specified as a string. If you want it to work in IE 5.0, you need to add support for the push method of the Array object:


if(typeof Array.prototype.push != "function"){
    Array.prototype.push = ArrayPush;
    function ArrayPush(value){
        this[this.length] = value;
    }
}

Web browser support

I’ve tested it in these web browsers listed below, but I’m fairly convinced it will work in a lot of others as well:

  • Internet Explorer 5.x+
  • Firefox
  • Safari 1.3+
  • Opera 8.5+

37 Comments

  • Nice work, Robert (again!).

    I'm wondering, since <code>eval</code> is evil, why not push function identifiers in the array and call them like this:

    <code>for (var i=0; i<functionsToCallOnload.length;i++)

    {

    functionsToCallOnload[i]();

    }</code>

    ? (I must be missing something :p)

  • Hm, something screwed up my code, another try:

    <code>

    for (var i=0; i< functionsToCallOnload.length; i++)

    {

    functionsToCallOnload[ i ] ( );

    }

    </code>

  • Robert Nyman says:

    Harmen,

    First, I wouldn't say that <code>eval</code> is evil in any scenario; it's usually not needed, but sometimes has its points.

    Using your technique removes the possibility to use parameters, like <code>myFunction("Show this text")</code>, so therefore I used it to make the script as flexible as possible.

  • Robert,

    you're right, I hadn't thought of that.

    Well, I can only conclude it's yet another great object then! 🙂

  • Rowan Lewis says:

    Everyone seems to hate eval functions, but they're only unsafe when they can be altered by the user (Just like SQL injection, but I guess we'd call it code injection).

    Anyhow, good work, and thanks 🙂

  • Thanks, Robert! This is very timely; I needed to implement something today that would handle multiple events, so it's a good thing you didn't wait another day or two to write this.

  • Peter says:

    Hmm… So what's wrong with addEventListener and attachEvent?

  • They wait for all content to load, including external servers' images, large images, etc. Which makes your page look really slow if it has to wait for all this content before running a script. Once an element's HTML has loaded, there is often a need to manipulate that dom element right away. This script allows that.

  • Thanks for that, Robert. I've read the other post a few months ago and began using it in my own projects, but having it encapsulated in such a beautiful way makes things much easier. I hope more developers will realize that window.onload isn’t the last word on the subject.

  • I looked into this back in April and decided that I wanted to treat this as an event. With that in mind, I modified Dean Edwards' events.js file to add a DOMContentLoaded event. You can see this working at DOMContentLoaded for Browsers, Part II. It allows you to use multiple events and you can pass parameters using anonymous functions.

  • Jakob Heuser says:

    This is wonderful Robert! The best I had managed for the longest time was a weird function using setTimeout() that waited for DOM elements to register.

    At Gaia, we have users with signatures which can come from anywhere on the internet, making window.onload a surefire way to ensure certain JavaScript never happens. This seems to be just the ticket to keeping tha JavaScript in its proper place.

  • Robert Nyman says:

    Thank you everyone, I'm glad that you like it! For me, it instantly became very useful!

    Peter,

    The script actually uses <code>addEventListener</code> to apply the a certain event for Mozilla-based and Opera 9 web browsers, but instead uses the <code>DOMContentLoaded</code> event for them.

    The reason one wants to use something else than the <code>onload</code> event is perfectly described by Eric above.

    Tanny,

    Thanks for sharing an alternate solution.

  • […] oktober 2006 – Lees de reacties Onderwerpen: linkdump […]

  • […] oktober 2006 – Lees de reacties Onderwerpen: linkdump […]

  • Ash Searle says:

    (missing the point entirely but…)

    That ‘push’ implementation is wrong. Here’s one that follows the spec (push multiple things onto the array at once, and return the new length of the array):

    if (typeof Array.prototype.push != "function") {
      Array.prototype.push = function() {
        for (var i = 0; i < arguments.length; i++) {
          this[this.length] = arguments[i];
        }
        return this.length;
      }
    }

    Also, if you used functions instead of strings on the call-stack, you’d be able to use closures for parameterisation, instead of limiting yourself to parameters in the scope of the eval call. Of course, it wouldn’t take much effort to accept both strings and function references.

  • Chris says:

    Hi Robert,

    do you know Simon Willison's addLoadEvent? I use it regularly and like it:

  • Robert Nyman says:

    Chris,

    Yes, I know of it. It makes it easy to apply severable <code>window.onload</code> events without overwriting existing ones, but what it lacks compared to ELO is the possibility to apply events as soon as the DOM has loaded.

  • Chris says:

    OK. I did not understand this. Silly me. 🙂

  • Robert Nyman says:

    Ash,

    I wouldn't necessarily say that the <code>push</code> implementation is wrong, but instead sufficient for its use here. If you want to make it more flexible and accept several parameters, you just have to loop through the <code>arguments</code>, just like in your example.

    I'm not sure I'd agree that the scope for the parameters would be just for the <code>eval</code> call; it depends on what parameters/possible object properties you refer to.

    And yes, you can fairly easily expand the functionality to accept functions as well as strings, and my initial version used only function references. In the end, though, I decided to take this route in this version presented here, because it was sufficient for my needs.

    If you want to enhance it with more support/flexibility for the cases you would use it in, by all means, add the functionality you seem fit. 🙂

  • John Magnus says:

    (Maybe I'm missing something, but…)

    Wouldn't it be better to integrate the different parts of the "@if … if … else if … else …" tighter together? Thus avoiding running browser specific/unnecessary parts of the code when the script allready have identified a browser…

    Possibly something like;

    <code>

    /*@cc_on

    /*@if (@_win32)

        // IE on Win32

    @else @ */

        if (document.addEventListener) {

            // Mozilla/Opera

            }

        else if (navigator.userAgent.search(/WebKit/i) != -1) {

            // WebKit (Safari)

        else {

            // Other browsers

            }

    /*@end @ */

    </code>

  • Robert Nyman says:

    John,

    Good question.

    The first part of the script is a way to accomplish conditional comments in a script file that will only work in IE. And since one wants the <code>window.onload</code> at the bottom for all web browsers as an extra insurance, that leaves only the <code>if</code> clauses for Mozilla/Opera 9 and Safari,

    So yes, if you want to you can use <code>if…else</code> for the Mozilla/Opera 9 and WebKit part, that is an option.

  • John Magnus says:

    Hi again Robert
    I know that the “@cc_on” parts are conditional comments, but [in your script] don’t you end it (@end) before going on to ask if the user-agent is Mozilla or Opera, then lastly asking for WebKit/Safari. Wouldn’t this be asking the user-agent if it’s Mozilla, Opera or Safari even if we’ve identified it to be IE (via the inclusion of the conditional comment) earlier in the script….

    If my coding is working correctly (my limited testing suggests so), the number of if’s asked is kept at a minimum, including having the normal “if … else if …” inside a conditional comment (so it’s completly ignored by IE).

    The last “window.onload” could be kept outside the “if … else if” to trigger in all browsers, though wouldn’t this fire the “ELO event” twice?

    Sorry if I’m not making any sense at all; JavaScript really isn’t my native language, especially when we’re throwing in bits of this strange and sheldom spoken dialect from MS…. 😉

  • Robert Nyman says:

    John,

    Regarding the conditional comments, I’m not a 100% sure. For that I used what was created and tested in Dean’s script. So, you may very well be right, but since Dean’s script has been thoroughly tested by lots of talented people and verified to work, I’m a bit wary about changing that part.

    The window.onload part does indeed trigger the ELO method twice in web browsers that successfully applied the event in any of the ways above in the code. Therefore, the ELO object has a loaded property which in turn prevents multiple execution of already called functions.

  • Edemilson Lima says:

    A problem with this script, I think, is the document.write(), which in mostly cases can't be used inside a DIV if you get its content by an innerHTML property. document.write will mess the document. This is common in an AJAX application, so you need to put it in the main HTML document, outside any DIV.

    I did create my own functions to handle events to use multiple functions (see below). I am not sure this is the most elegant way to do this, but it works fine.

    // global variables

    var onload_events='';

    var onmousemove_events='';

    var onmouseup_events='';

    function onload_push(str) {

    if(str.substr(str.length-1,1)!=';') str+=';';

    onload_events+=str;

    window.onload=function() {

    eval(onload_events);

    }

    }

    function onmousemove_push(str) {

    if(str.substr(str.length-1,1)!=';') str+=';';

    onmousemove_events+=str;

    document.onmousemove=function(evt) {

    eval(onmousemove_events);

    }

    }

    function onmouseup_push(str) {

    if(str.substr(str.length-1,1)!=';') str+=';';

    onmouseup_events+=str;

    document.onmouseup=function(evt) {

    eval(onmouseup_events);

    }

    }

    function onload_init() {

    // flag to look after if the document was loaded

    document.loaded=true;

    }

    // examples

    onload_push('onload_init()');

    onload_push('some_function()');

    onmousemove_push('some_other_function()');

    onmousemove_push(someojb+'.property="value"');

    onmousemove_push(someobj+'.method(evt)');

  • Edemilson Lima says:

    Hmmm… the website ripped many underscores in my code (it was where italics begin and end)… 🙁

  • Robert Nyman says:

    Edemilson,

    The script in this post doesn't use <code>document.write</code> at all.

  • Stefan Van Reeth says:

    Nice work Robert.

    I've seen some variants on this, but this one seems to be the easiest to use. Gonna try to rewrite it if you don't mind, so the ugly conditional statements can go (really, really hate 'em, but that's just me).

    I'll be in touch if I succeed and it's err… presentable :).

  • Anonymous says:

    As promised, here's an alternative way to let the conditional comments (not statements lol) go:

    I've kept the original code between the conditional comments intact, to keep it simple here. It's also possible to add the script by the DOM, but there are a few gotcha's there, and that wasn't the goal I set out for anyway. For those interested, it IS solved by yours truly ;).

    The alternative here requires a bit extra code, but those funcs (and other typecheckers) are missing in javascript anyway. It's one of those things that are in my common.js. Also I've written it out quite elaborate, just to make the workings clearer. Everyone feel free to crunch it as much you like :).

    <code>

    if (isExternal(window.clipboardData))

    {

    document.write( "<script id=__ie_onload defer src=javascript:void(0)></script>" );

    var script = document.getElementById( "__ie_onload" );

    script.onreadystatechange = function() {

    if ( this.readyState == "complete" ) {

    init(); // call the onload handler

    }

    };

    }

    function isExternal(item)

    {

    if (isObject(item))

    {

    return typeof item.constructor !== "function";

    }

    else

    {

    return false;

    }

    }

    function isObject(item)

    {

    if (!(item && typeof item === "object"))

    {

    return isFunction(item);

    }

    else

    {

    return true;

    }

    }

    function isFunction(item)

    {

    return typeof item === "function";

    }

    </code>

    What we do here is searching for an object that is accessible to javascript, but isn't part of it. Like the Packages (from Java) in Mozilla for example. What I found was that IE has quite a few of them, and I picked one that seemed suitable. Instead of wrapping the IE code in those UGLY statements, this way seems much nicer and less 'hack' to me.

    What I like to have now is some feedback from IE users on the Mac. Can I use this test there too? Logically it would work just fine, but one never knows with Explorer :D.

  • Robert Nyman says:

    Stefan,

    Interesting approach! Two things to mention, though:

    First, for security reasons, it seems likely, (at least to me), that maybe IE will block the access to <code>clipboardData</code>.

    Second, I haven't tested, but I'm fairly sure IE on Mac wouldn't let you access <code>clipboardData</code>.

  • Stefan Van Reeth says:

    We never access it here Robert, but just sniff it's presence. Or did I misunderstood what you mean?

    And there is a whole list of "externals" in IE, like window.window. It doesn't have to be the one above, we just have to settle for one that's found in all versions of it. In other browsers that list is limited to the Java stuff (at least Opera and Mozilla family).

    Admitted, looking the presence of window.clipboardData on the Mac is probably not the way to go, but hey, I just picked one as an example…

  • Robert Nyman says:

    Stefan,

    No matter if we're accessing it or not, I doubt that <code>clipboardData</code> or any <code>external</code> property are available in IE on Mac, so just checking if it's there or not will likely fail.

  • Stefan Van Reeth says:

    Well, is there anyone who can test this for us? I know nobody with a Mac. No point discussing something we can't check ourselves ;).

  • Robert Nyman says:

    Stefan,

    Well, I have a Mac, but I don't want to taint it with a nasty IE 5. 🙂

    Besides, I don't put any extra time into supporting IE on Mac anymore, since developing on it stopped a number of years ago and the user base is just too small.

  • Aldrik says:

    Great script Robert! I've made some changes some time ago & have been meaning to share. I call it ELF. 🙂

    Changes: added Konqueror & 64bit IE support (I assume @_win32 doesn't work on 64bit but have not been able to test (vista x64 blue screens upon boot on my hardware)), it's also a bit smaller in file size.

  • Robert Nyman says:

    Aldrik,

    ELF: Ha ha! 🙂

    Thanks for sharing!

  • […] utilizando também o ELO – Encapsulated Load Object para adicionar a função no onload da página, estou disponibilizando os dois scripts para […]

Leave a Reply

Your email address will not be published. Required fields are marked *