View Full Version : Binding encapsulated anonymous functions without repetition
brothercake
11-12-2004, 03:17 AM
Normally, if you want to bind encapsulated event listeners to an object you have to test for support and then call the same function in different ways, for example:
if(typeof document.addEventListener != 'undefined')
{
document.addEventListener('click', someFunction, false);
}
else if(document.attachEvent != 'undefined')
{
document.attachEvent('onclick', someFunction);
}
But there's code repetition there - okay not very much, because it's just a function call ... but what if you wanted to use an anonymous function ..? Well you can't - the code repetition would be unacceptible.
Except that I've though of a way :) It's really obvious actually .. but I'm posting this in the hope that others will go "wow, that's blindingly useful" as I did when I thought of it :thumbsup:
Here it is - it takes advantage of square-bracket notation to use a string reference to the supported method:
//identify supported method of adding encapsulated event listeners
etype = (typeof document.addEventListener != 'undefined') ? 'addEventListener' : (typeof document.attachEvent != 'undefined') ? 'attachEvent' : 'none';
//set event name prefix
eprefix = (etype == 'attachEvent' ? 'on' : '');
//if encapsulated event listening is not supported, don't continue
if(etype == 'none') { return; }
so anonymous functions are built like this:
element[etype](eprefix + 'event', function()
{
... code ...
}, false);
Even though attachEvent doesn't require a third argument, it's ignored, so this syntax works for all.
Basscyst
11-12-2004, 05:32 PM
**Wonders what exactly it was that just flew over his head**
Um. . .what should I google for to understand this or what is this used for? :confused: :p
Basscyst
brothercake
11-12-2004, 09:14 PM
Square-bracket notation is the key to it - that gives you the ability to do variable replacement on any property name. So this:
document.getElementById
is equivalent to this:
document['getElementById']
is equivalent to this:
var collection = 'getElementById';
document[collection]
The benefit of adding encapsulated listeners is so that they don't interfere. You can bind, for example, an onclick event to an element without interfering with any anonymous or attribute onclick handlers it already has.
Garadon
11-12-2004, 10:05 PM
looks nice if I where ever to use it which would be a first :). only thing I don't get is the etype==none return. why you do that?, I have checked and IE and FF faults on returns outside a function
brothercake
11-12-2004, 11:57 PM
Oh well yeah ... you would only do that if you're inside a function. I pasted that a bit out of context .. like meta code. I'm actually using it within an object constructor, like this:
//identify supported method of adding encapsulated event listeners
this.etype = (typeof document.addEventListener != 'undefined') ? 'addEventListener' : (typeof document.attachEvent != 'undefined') ? 'attachEvent' : 'none';
//set event name prefix
this.eprefix = (this.etype == 'attachEvent' ? 'on' : '');
//if encapsulated event listening is not supported, don't continue
if(this.etype == 'none') { return; }
//bind onchange handler to select element
//(previously referenced as this.select)
this.select[this.etype](this.eprefix + 'change', function(e)
{
... code ...
}, false);
glenngv
11-18-2004, 05:30 AM
Square-bracket notation is the key to it - that gives you the ability to do variable replacement on any property name. So this:
document.getElementById
is equivalent to this:
document['getElementById']
is equivalent to this:
var collection = 'getElementById';
document[collection]
The benefit of adding encapsulated listeners is so that they don't interfere. You can bind, for example, an onclick event to an element without interfering with any anonymous or attribute onclick handlers it already has.
Basscyst, link on square bracket notation can be found in my sig.
Passin Thru
11-27-2004, 05:18 PM
Wouldn't it be easier to fix up Moz with an attachEvent method for elements ?
if(!window.attachEvent && window.addEventListener)
HTMLElement.prototype.attachEvent = function(e,fn)
{
this.addEventListener(e.substring(2),fn,false);
}
I don't know the root prototype to go for to get the window to play along (Object ?).
The sad thing about it is that this involves teaching Moz how to understand non-standard code - when it should be the other way around. I can't face the idea of having attach a behavior to all elements in IE.
brothercake
11-28-2004, 04:20 AM
Interesting ... is HTMLElement prototyping support in Safari and Opera 7, so we still get a full range of modern browsers with this approach?
Passin Thru
11-28-2004, 12:30 PM
I'm stuck with IE6 and Opera 7 at my location - and nothing I can do about it.
If Safari is based on Mozilla (?) then hopefully it will support such additions to element prototypes.
Opera is strange. It does expose element constructors, and their prototypes in as much as it says that they are there, but I can't get any added methods to work. But..
Opera seems to have a policy of supporting IE features (seeing as half the world seems to code unwittingly purely for IE), so it actually supports attachEvent.
Thus, so far as I can test, the approach works.
attachEvent (the normal version) is pants anyway. It doesn't preserve the calling object context - no useful 'this' to play with.
The code above doesn't test, or do anything for browsers that don't support either type of event assignment like brothercake's does.
Here's a Sunday morning attempt at a global function that will, if necessary, continually wrap any current assignment up with the new in a third function.
Who knows. It might even work.
// for pretending the browser
// only supports obj.onwhatever syntax
CLASSIC = false
// Can't call it 'addEventListener'
// '&& !CLASSIC' is just for tests
function addEvent(obj,e,fn)
{
if(obj.addEventListener && !CLASSIC)
obj.addEventListener(e,fn,false)
else if(obj.attachEvent && !CLASSIC)
obj.attachEvent('on'+e,fn)
// only supports 'classic'
else
{
var eName = 'on'+e;
obj[eName] = obj[eName]
? encapsulate(obj[eName],fn)
: fn;
}
}
// Done outside addEvent
// to avoid closure problems
function encapsulate(fn1, fn2)
{
return function(eMoz){fn1(eMoz);fn2(eMoz);};
}
//---------------------------------------------------------------------//
// 2 innocent little test functions
function fn1(e){ alert('fn1: '+ (e||event).type)}
function fn2(e){ alert('fn2: '+ (e||event).type)}
// Someone's already assigned
// a classic handler
window.onload = function(eMoz){ fn1(eMoz);}
// now add a new one
addEvent(window,'load',fn2)
brothercake
11-29-2004, 10:33 PM
Well this is it - catering for all modern browsers was the point of the code I originally posted, even though it may not be the most elegant solution, it's totally robust.
Safari isn't based on Gecko, it uses the KHTML/KJS engines from Konqueror. If you don't have a Mac that may be difficult to test ... but Opera you can download and run for free.
Passin Thru
12-02-2004, 08:42 PM
..yes. I have Opera.
I haven't tried on Konqueror, but if it supports prototype additions, then that method is robust and elegant.
brothercake
12-02-2004, 09:01 PM
Fair enough .. but the complexity there is far greater than what I originally posted.
btw - regarding Opera supporting attachEvent ... is that related to spoof settings? iirc much of Opera's implementation of IE proprietary stuff only works when set to identify as MSIE, in which case it wouldn't be safe to use.
waolly
02-05-2005, 08:21 PM
Very nifty event binding script!
There's only one issue I can see which has to do with browser independance.
It has to do with Opera and using the "onload" event. Since Opera supports both attachEvent and addEventListener, the result of the first check would depend purely on which one you check first. So you can decide which Opera will use.
If you choose attachEvent the problem will be immediate - Opera doesn't ignore the third argument (,,false) like IE does.
If you choose addEventListener then all is well except for this case: you can't bind an "onload" event to the window object in Opera using addEventListener (you can with attachEvent but that doesn't work because of it not ignoring the thrid argument) - weird thing is is that you CAN bind it to the document object but then you will lose compatability in IE and FF since they require "onload" to be binded to the window object.
I can't see any way around without duplicating code (e.g. try catch statements in FF with two arguments for addEventListener)
I would be interested to hear if you guys have any thoughts on this.
brothercake
02-06-2005, 02:17 PM
Yeah the load even in Opera comes from "document" instead of "window" (which is probably more correct, considering that "window" doesn't exist in the DOM). What you have to do is test for window.addEventListener first, document.addEventListener second, window.attachEvent third [, and window.onload at the end if required]
Although Opera supports attachEvent, don't use it - Opera's support for IE proprietary stuff is for the sake of compatibility with existing sites, and should not be consciously relied upon, imo. (I would avoid the use of try {} catch {} in scripting as well - I tend think of it as a debug tool, and sloppy to allow into final code if it can be avoided).
Anyway - for a load event handler you'd end up with something like this. It's not as neatly encapsulated as the earlier example, but you'd only need one instance of it:
//onload function
function generic()
{
alert('Generic onload function');
};
//setup onload function
if(typeof window.addEventListener != 'undefined')
{
//.. gecko, safari, konqueror and standard
window.addEventListener('load', generic, false);
}
else if(typeof document.addEventListener != 'undefined')
{
//.. opera 7
document.addEventListener('load', generic, false);
}
else if(typeof window.attachEvent != 'undefined')
{
//.. win/ie
window.attachEvent('onload', generic);
}
//** remove this condition to degrade older browsers
else
{
//.. mac/ie5 and anything else that gets this far
//if there's an existing onload function
if(typeof window.onload == 'function')
{
//store it
var existing = onload;
//add new onload handler
window.onload = function()
{
//call existing onload function
existing();
//call generic onload function
generic();
};
}
else
{
//setup onload function
window.onload = generic;
}
}
vBulletin® v3.8.2, Copyright ©2000-2010, Jelsoft Enterprises Ltd.