Thursday, April 16, 2009

Binding to the BODY onLoad event in a portlet

One of the hassles that many developers come across when developing Javascript for their portlets is the lack of access to the BODY onLoad event. You can't simply override this using javascript as this might break functionality in the portal theme, or in other portlets on your page. (Ofcourse, this predicament is not limited to portlet development!)

My old trick was to use the below addToBodyOnload() javascript function, that borrowed some code from quirksmode.org:

function addToBodyOnload(oFunction) {
var existingOnload = window.onload;
window.onload = function () { oFunction(); existingOnload(); }
}

addToBodyOnload(newOnloadFunction);


The above makes use of javascript's ability to pass functions around as data and let you 'append' your function to the chain of functions called by the onLoad event by 'wrapping' these function calls. However even this has its limitations.

Sometimes, a previously existing onLoad function may require access to "this" or "event" - implicit objects in event handlers that refer to the object originating the event and data about event. For this scenario, I've tried expanding the above to handle the passing of the event object through the wrapper function. Event is handled a bit differently in different browsers - in some it is implicit, and in others needs to be explicitly passed - wheras the "this" object seems to be implicit always. Take a look at the example below:



You should see that the event and window objects are accessible to both the old 'wrapped' and new onload functions.

One last alternative... Working with WebSphere Portal 6.0 and IBM's JSF-based Ajax framework, I found that the javascript called by the page's onload event handler (via the addBehaviour() method, or hx:behaviour tag) is actually a property of another object that seems to expect values from within this object as parameters. This makes it next to impossible to automatically deduce the correct parameters to pass in from the wrapper function. In cases like this, there's another very simple workaround that is suitable for many occasions - the old setTimeOut function:

var t=setTimeout(newOnloadFunction, 100);

This above will call your function 100ms after this script is executed. Remember that this could be a bit unreliable. Putting a line like the above in your page starts the clock from the point the browser renders that part of the page. On a page that takes a long time to render, this could mean that your script fires before body.onload. There's a chance that DOM elements your script references (for example using getElementById()) may not yet be present on the page in such a scenario. For best results, put this as close to the bottom of your page as you can, and increase the time value as much as you can (although this can look glitchy if your onload event makes visual changes to the page). Also make sure you handle scenarios where the script fails to find DOM objects, perhaps by setting a new timer to go off in another 100ms time.