robert hahn

a darn good web developer

February 02, 2007

How to Use window.onload the Right Way

While working on a project, I encountered a rather nasty problem. It had been a bit of an adventure to solve it, so I thought I’d share.

The site I was working on needed to pull in a few JavaScript files, and I was writing an additional one just for this project. The new one was kind of special, because I needed to run some code to populate a dropdown list once the page was finished loading. Many of you have probably had to do this, and wrote code like the following:

window.onload = function () {
    // do stuff here
}

This works great, until you need to do this in two different files for different reasons. It’s obvious why: the last JS file to use that structure basically overwrites any previous assignment to window.onload. Not good.

I knew what I wanted the solution to look like: I wanted to test window.onload, and if it wasn’t undefined, then get it, and combine it with whatever list of items I wanted. My first attempt looked something like this:

var temp_f;
if( window.onload ) {
    temp_f = window.onload;
}

window.onload = function () {
    if( temp_f ) {
        temp_f();
    }
    // do other stuff here
}

And my thinking was, on every file that needed to do something with the window.onload, we put in that chunk of code, and it’ll all be good.

Except, it wasn’t. I created a mock test, loaded it up in Firefox, and bam! JavaScript error: “Too much recursion”. What was going on? After awhile of poking the code with pointy sticks, I learned that what I wanted, and what I got were two different things. When I assigned window.onload to temp_f, I wanted

// do other stuff here

but I got

function () {
    if( temp_f ) {
        temp_f();
    }
    // do other stuff here
}

Ok, so that explained the recursion. temp_f was already defined, and in the body of the definition was a call to temp_f(). Duh.

I tried to come up with some alternative solutions, and with the help of a friend, eventually came up with 3 possible solutions. I hated all of them, because I didn’t like the tradeoffs I had to make. Here they are:

Solution 1: Register your onload tasks

In this technique, I’d mandate that for each page that requires work to be done with window.onload, you assign your tasks to the last element of an array. It’s easy enough:

if( !taskList ) {
    taskList = new Array();
}

taskList[taskList.length] = myfunction;

The omission of the parens is deliberate here… keep reading:

window.onload = function () {
    for(var i=0; i< taskList.length; i++) {
        taskList[i]();
    }
}

Well, actually, I’d use an iterator instead of a for loop, but you get the idea. This chunk of code would appear on all pages, window.onload would still get clobbered, but that’s ok, because it’s running the same code on all pages.

That was my favourite non-ideal solution.

Solution 2: Define a global loader

For this solution, we’d clear out all window.onload calls from our scripts, and then create a new ‘loader’ JS. It basically looks like the following:

window.onload = function () {
    if( myFn1 ) 
        myFn1();
    if( myFn2 ) 
        myFn2();
    // and so on
}

Basically, the thinking here is that if we can only use window.onload once, then do so, and maintain a giant loader script that gets loaded last.

What I didn’t like about this solution is that the place where I call a function is completely removed from the place where I defined it. It’s easy to forget about the loader page during maintenance.

Solution 3: eval() anyone?

“If only I could do it the first way, but only get at my functions, not the wrapper code,” I thought. Well, that’s possible too. with Function.valueOf(), I could get the source code for any defined function. I could then pick it apart with Regexp’s, find the code I wanted to run, and eval() that code in my window.onload function. Shudder

I did not like this idea at all because of the eval() call. Eric Lippert said it best when he said “People, eval starts a compiler.”

There’s got to be a simpler, better way.

The Final Solution: It’s about the functions

Ok, first, a disclaimer. I hate to drop names, because I feel like doing so is a cheap grab for popularity. But I’m going to do it, just this once, ok?

Eric Lippert and I are friends. Have been for years. Got him on my MSN list and everything. In fact, he used to be my wife’s boyfriend back in the day. Small world.

I pinged Eric on MSN. “Eric, I am sorely vexed,” I said, and proceeded to spell out the problem to him. “I know there’s got to be a better way than this, but I’m not seeing it.”

As is typical for Eric, he proceeds to explain what the problem is that I was having. “Because Netscape cleverly gave us a single cast event model,” Eric replied, “this is a problem.” What you need is a multi-cast delegate.

No kidding.

But then comes the good part: “I think we can do something similar in JScript without involving an explicit array. Let me think about it for a minute.”

I knew this was going to be good. With some small revisions, here’s his solution.

function makeDoubleDelegate(function1, function2) {
    return function() {
        if (function1)
            function1();
        if (function2)
            function2();
    }
}

window.onload = makeDoubleDelegate(window.onload, myNewFunction );

Let’s walk through this. The makeDoubleDelegate() function would be put in the first JS file, ready for invokation any time. What it does is accept two functions as arguments, checks to see if they exist, and if so, return an anonymous function containing valid, existing functions.

The first time that window.onload is called, window.onload itself is undefined, so what gets returned is an anonymous function with only myNewFunction() inside. On subsequent window.onload assignments, window.onload does exist, so whatever it holds will be wrapped up with the new function. And so the list grows.

If this kind of thing isn’t a compelling reason to learn how to progam in a functional style, I don’t know what is. Time to move the SICP book to the top of the pile!

decorative image of trees

Copyright © 2009
Robert Hahn.
All Rights Reserved unless otherwise indicated.