Relatively speaking, Basic Event Model is more independent of browsers but less flexible than those advanced event models. For example, you cannot register multiple event handlers. Though advanced event models - DOM Level 2 Event Model and Internet Explorer Event Model - have more functionality, they can only be run on standards-compliant browsers and Internet Explorer respectively. Using advanced event models to handle events will need more consideration for cross-browser issues.
Using the object detection is the basic start point. For example, when registering the click event, dealing with the difference between addEventListener and attachEvent can be as follow:
if(element.addEventListener) {
element.addEventListener('click', handler, false);
}
else if(element.attachEvent) {
element.attachEvent('onclick', handler);
}
element.addEventListener('click', handler, false);
}
else if(element.attachEvent) {
element.attachEvent('onclick', handler);
}
Internet Explorer doesn't support the capturing phase, so we have to abandon the usage of capturing events. Writing the above code repeatedly when we need to register an event is not only a troublesome matter, but also performance consumption due to repeated object detection. We can do the object detection when a browser loads scripts and create a function to encapsulate the real function used for registering an event. For example:
var bind;
function proxy(element, handler) {
return function() {
return handler.apply(element, arguments);
};
}
if(document.addEventListener) {
bind = function(element, eventType, handler) {
element.addEventListener(eventType, proxy(element, handler), false);
};
}
else if(document.attachEvent) {
bind = function(element, eventType, handler) {
element.attachEvent('on' + eventType, proxy(element, handler));
};
}
function proxy(element, handler) {
return function() {
return handler.apply(element, arguments);
};
}
if(document.addEventListener) {
bind = function(element, eventType, handler) {
element.addEventListener(eventType, proxy(element, handler), false);
};
}
else if(document.attachEvent) {
bind = function(element, eventType, handler) {
element.attachEvent('on' + eventType, proxy(element, handler));
};
}
The above example standardizes the way for registering an event and the event handler's this always refers to the element. We can bind an event as follows:
bind(somBtn, 'click', function() {
var id = this.id;
...
});
var id = this.id;
...
});
Next, we'll consider unifying the way to retrieve the Event instance. If we'd like it be given from the first argument of the handler, we can write as follows:
var bind;
if(document.addEventListener) {
bind = function(element, eventType, handler) {
element.addEventListener(eventType, function(event) {
return handler.call(event.currentTarget, event);
}, false);
};
}
else if(document.attachEvent) {
bind = function(element, eventType, handler) {
element.attachEvent('on' + eventType, function() {
return handler.call(element, window.event);
});
};
}
if(document.addEventListener) {
bind = function(element, eventType, handler) {
element.addEventListener(eventType, function(event) {
return handler.call(event.currentTarget, event);
}, false);
};
}
else if(document.attachEvent) {
bind = function(element, eventType, handler) {
element.attachEvent('on' + eventType, function() {
return handler.call(element, window.event);
});
};
}
We can bind an event and get the Event instance from the first parameter as follows:
bind(somBtn, 'click', function(event) {
var id = this.id;
...
});
var id = this.id;
...
});
If we want to prevent the default action when the event handler returns false, we can rewrite as follows:
var bind;
if(document.addEventListener) {
bind = function(element, eventType, handler) {
element.addEventListener(eventType, function(event) {
var result = handler.call(event.currentTarget, event);
if(result === false) {
event.preventDefault();
}
return result;
}, false);
};
}
else if(document.attachEvent) {
bind = function(element, eventType, handler) {
element.attachEvent('on' + eventType, function() {
var result = handler.call(element, window.event);
if(result === false) {
window.event.returnValue = false;
}
return result;
});
};
}
if(document.addEventListener) {
bind = function(element, eventType, handler) {
element.addEventListener(eventType, function(event) {
var result = handler.call(event.currentTarget, event);
if(result === false) {
event.preventDefault();
}
return result;
}, false);
};
}
else if(document.attachEvent) {
bind = function(element, eventType, handler) {
element.attachEvent('on' + eventType, function() {
var result = handler.call(element, window.event);
if(result === false) {
window.event.returnValue = false;
}
return result;
});
};
}
Properties of an Event instance have cross-browser differences, such as target and srcElement, stopPropagation and cancelBubble, etc. We can create an object to unify those properties from an Event instance. For example:
var bind;
if(document.addEventListener) {
bind = function(element, eventType, handler) {
element.addEventListener(eventType, function(event) {
var evn = {
// Copy properties
target : event.target,
currentTarget : event.currentTarget,
...
// Encapsulate functions
stopPropagation : function() {
event.stopPropagation();
},
...
};
var result = handler.call(event.currentTarget, evn);
if(!result) {
event.preventDefault();
}
return result;
}, false);
};
}
else if(document.attachEvent) {
bind = function(element, eventType, handler) {
element.attachEvent('on' + eventType, function() {
var evn = {
// Copy properties
target : window.event.srcElement,
currentTarget : element,
...
// Encapsulate functions
stopPropagation : function() {
window.event.cancelBubble = true;
},
...
};
var result = handler.call(element, evn);
if(result === false) {
window.event.returnValue = false;
}
return result;
});
};
}
if(document.addEventListener) {
bind = function(element, eventType, handler) {
element.addEventListener(eventType, function(event) {
var evn = {
// Copy properties
target : event.target,
currentTarget : event.currentTarget,
...
// Encapsulate functions
stopPropagation : function() {
event.stopPropagation();
},
...
};
var result = handler.call(event.currentTarget, evn);
if(!result) {
event.preventDefault();
}
return result;
}, false);
};
}
else if(document.attachEvent) {
bind = function(element, eventType, handler) {
element.attachEvent('on' + eventType, function() {
var evn = {
// Copy properties
target : window.event.srcElement,
currentTarget : element,
...
// Encapsulate functions
stopPropagation : function() {
window.event.cancelBubble = true;
},
...
};
var result = handler.call(element, evn);
if(result === false) {
window.event.returnValue = false;
}
return result;
});
};
}
The above examples demonstrate the basic concept for handling cross-browser events. You can take similar consideration while unbinding events. For more information about cross-browser event handling, you may take a look at Cross-Browser Event Handling Using Plain ole JavaScript.