Sunday 20 September 2009

Trouble with this keyword and namespaces

Namespaces, duplicate functions and more IE nightmares

I was working on some code the other night which belonged to a site similar to my own in which numerous scripts and "semi frameworks" were being included. By semi framework I mean an add-on script that includes numerous functions that will almost certainly be replicated elsewhere in the site (DOM manipulation, event handlers etc).

The code involved using an iframe and then running an onload event as soon as the content had loaded so that I could resize the iframe to the correct dimensions for the content within. I had a bit of code like this:

addEvent(document.getElementById('myIframe'),"load",resizeIframe);

// resize iframe content onload
function resizeIframe(){

var dc = S.getIframeDoc(this); // return iframe document
var h = dc.body.scrollHeight; // get height of document within iframe
this.height = h+30+"px"; // set height of iframe+30 to ensure no scrollbars
}

As you can see the function resizeIframe which is called when the iframe loads uses the this keyword to reference itself rather than getting a reference to itself using getElementById which is fine as long as you are using a proper browser that supports the DOM 2 event model. However as IE does not support this model it has a problem with the this keyword in that it references the global window object instead of the iframe.

However this is a well known problem and many a solution has been created to get round this issue in Internet Explorer. On the site in question I use the following function which as you can see handles DOM 2, IE's event model and the older DOM 0 event model. It also correctly handles the this keyword problem by storing a reference in the DOM to the function. Read up on PPKs event handler content for more details.


addEvent = function( obj, type, fn, cp )
{
if(obj){
if(obj.addEventListener){
cp = cp || false;
obj.addEventListener( type, fn, cp );
}else if ( obj.attachEvent ) {
obj[type+fn] = function(){fn.call(obj,window.event);}
obj.attachEvent( 'on'+type, obj[type+fn] );
}else{
var ev='on'+type;
var oldevent = obj[ev];
if (typeof oldevent != 'function'){
obj[ev]=fn;
}else{
obj[ev] = function(){ oldevent();fn();}
}
}
}
}


However when testing the site in Internet Explorer I was getting errors when trying to reference the contentWindow.document in my resizeIframe function saying its null or not an object.

The reason being that the this keyword was referencing the window object and there is no such object property on the global object.

I spent some time scratching my head and looking over the function at hand and then when I put some debug code into the addEvent function and noticed it not appearing it at all the problem suddenly made sense. After a quick search through the other JS files being included on the page in question I came across the following function in a script called sorttable.js used for sorting table contents.
function addEvent(elm, evType, fn, useCapture)
{
if (elm.addEventListener){
elm.addEventListener(evType, fn, useCapture);
return true;
}else if (elm.attachEvent){
var r = elm.attachEvent("on"+evType, fn);
return r;
} else {
var o = elm[evType];
if(typeof(o)=="function"){
elm[evType] = function(){o();fn();}
}else{
elm[evType] = function(){fn();};
}
}
}


As you can see its another addEvent function and one that doesn't handle the this keyword problem in IE.

As this script was being included after my own file it was overwriting the previous addEvent function and therefore was the cause of the error.

Now this self taught lesson reminded me of a recent post I wrote about the amount of duplicate frameworks and semi frameworks being included on websites at the moment. I reckon that on this page in question there was at least 5 different places where a function to add an event listener was being declared:
-My own addEvent function in my standard library strictly.js
-The addEvent function in the sorttable.js script
-JQueries own methods to bind events - different names but doing the same action.
-GooglesAJAX library would surely have its own event handlers.
-AddThis add-on also had a similar event handler.

Therefore 5 scripts all doing the same thing. I explain it more in my earlier post:

http://blog.strictly-software.com/2009/08/large-number-of-duplicate-frameworks.html

where I discuss the possibility of a standard global API for implementing these sort of functions that are nearly always replicated in add-on scripts. This would allow developers to choose the framework to handle this type of work and add-on developers wouldn't have to worry about implementing duplicate code.

Another solution would be if the add-ons would separate their code out into 2 files. The first file would contain the core functionality and then the other file would contain the functions that could/should be handled by the main framework used by the site.

This would allow the site developer to reduce the size of their codebase as they could choose to remove the 2nd file (if they wished) and allow their primary framework to handle the work that a lot of add-ons duplicate such as DOM manipulation and event handling.

Obviously this works only if the add-on is using the same naming convention as the many frameworks and if the frameworks don't even share the same naming convention then this would seem hard to achieve. However we could always use wrapper functions or aliases to point the add-on functions at the desired framework objects e.g for our addEvent function:
// addEvent used by this script
// params supplied
// obj = object, type = event, fn = function

// set alias so that add-on can use sites primary framework e.g jQuery/Prototype
A = addEvent = function(obj,type,fn){
$(obj).bind(type,fn);
}

Which allows the add-on to add event listeners with a call to A or addEvent with either call just being a pointer to the frameworks bind method. The add-on provider could either provide these alias wrappers themselves or just outline the interface required and allow the developer to implement the code.

And of course if the user of the add-on didn't use a framework then they could choose to utilise the 2nd files functions as is even if they were duplicated in 5 other places.

This is all just ideas at the moment but I am developing an add-on at the moment that is going to try this approach.


Why Namespaces are such a good idea

Another solution to the problem of duplicated functions is to always use namespaces to define your functions and objects so that there is little if no chance of two functions having the same name. The writer of the sorttable.js file didn't use a namespace to define his functions but if I had of defined my own addEvent function like so instead:
Strictly.addEvent = function(obj, type, fn){ ...

// call like so
Strictly.addEvent(document.getElementById('myIframe'),"load",Strictly.resizeIframe);


I would not have had any problem working out why my function wasn't working as expected unless one of the other add-ons also had used the namespace Strictly!

Obviously using namepaces may solve the problem of functions being overwritten but it doesn't solve the issue of duplicated functionality.


Object Comparisons in Internet Explorer

Now whilst I was scratching my head in relation to the this problem I looked into creating a little test function that would tell me whether this equated to the global object or not as I was going a bit offtrack and of course a simple this === window should do the trick. However whilst looking into the different methods of object comparision I came across some of the differences between browsers of how object comparison is carried out. I don't know why I thought IE wouldn't be the odd one out as it always is but you might find the results interesting or you may not.

Make sure you view the test page in IE and a standards compliant browser like Firefox for comparison so that you see what I mean:

www.strictly-software.com/thistest.htm

window === window.top is true in Firefox and false in IE.

window == document is false in Firefox and true in IE.

var _w=window;
_w === window.top is true in Firefox and false in IE.

When this relates the the global window object:

this === window.top is true in Firefox and false in IE

this === self is true in Firefox and false in IE

This may all be old news as far as JS developers are concerned but it was news to me so I thought I would post the link anyway.

No comments: