I've been putting together a lightweight event utility for cross-browser event handling, but have come across a rather high hurdle I can't quite jump over. It's concerning the srcElement property in IE9, which just isn't working for me!
Currently, my utility looks something like this (the srcElement is the 3rd code block):
var bonsallNS = new Object();
bonsallNS.events = {
addEvent: function (node, type, func) {
if (typeof attachEvent != "undefined") {
node.attachEvent("on" + type, func);
} else {
node.addEventListener(type, func, false);
}
},
removeEvent: function (node, type, func) {
if (typeof detachEvent != "undefined") {
node.detachEvent("on" + type, func);
} else {
node.removeEventListener(type, func, false);
}
},
target: function (e) {
if (typeof srcElement != "undefined") {
return window.event.srcElement;
} else {
return e.target;
}
},
preventD: function (e) {
if (typeof srcElement != "undefined") {
window.event.returnValue = false;
} else {
e.preventDefault();
}
}
}
Now, using the following test script below works fine in Chrome and Firefox, but returns the evtTarget to be undefined in IE when I try to alert it. I have NO idea why, so any help would be greatly appreciated! See below code:
var gClick = document.getElementById("clickme");
var gCount = 0;
function mssg(e){
var evtTarget = bonsallNS.events.target(e);
alert("it worked, target: " + evtTarget);
gCount++
if (gCount > 2){
bonsallNS.events.removeEvent(gClick, "click", mssg);
}
}
function setUp(){
bonsallNS.events.addEvent(gClick, "click", mssg);
}
bonsallNS.events.addEvent(window, "load", setUp);
Like I say, everything else works except for the event source in IE!!
The variable srcElement will always be undefined unless it's defined in a higher scope. But it will never refer to event.srcElement.
You can solve this problem in two ways: Either check whether e or e.srcElement are not defined, or whether window.event and window.event.srcElement are defined.
if (typeof e === "undefined" || typeof e.srcElement === "undefined") {
// or
if (typeof window.event !== "undefined" &&
typeof window.event.srcElement !== "undefined") {
You can also shorten the whole function to:
target: function (e) {
e = e || window.event;
return e.target || e.srcElement;
}
Related
Given a publish-subscribe pattern using ES6 as follows (extracted from https://davidwalsh.name/pubsub-javascript):
class PubSub {
constructor() {
this.handlers = [];
}
subscribe(event, handler, context) {
if (typeof context === 'undefined') {
context = handler;
}
{
if (this.getHandler(event, handler) == null) {
this.handlers.push({event: event, handler: handler.bind(context), key: Guid()});
}
}
}
unsubscribe(event, handler) {
let filteredHandler = this.getHandler(event, handler);
if (filteredHandler != null) {
let idx = this.handlers.indexOf(filteredHandler);
if (idx > -1) {
this.handlers.splice(idx, 1);
}
}
}
publish(event, args) {
this.handlers.forEach(topic => {
if (topic.event === event) {
topic.handler(args)
}
})
}
getHandler(event, handler) {
if (this.handlers == null || this.handlers.length < 1) {
return null;
}
let filtered = null;
this.handlers.forEach(topic => {
if (topic.event === event && topic.handler === handler) {
filtered = topic;
}
});
return filtered;
}
getNumOfSubsribers() {
if (this.handlers != null && this.handlers.length > 0) {
return this.handlers.length;
}
return 0;
}
}
The subscribe and publish methods work. However, the getHandler and unsubscribe method do not work as expected (getHandler seems returning null). I have tried to search around but could not get a satisfactory solution to this problem (not sure how a function bound to a given context can be filtered out from an array).
What have I done wrong in the code? Kindly advise me on getHandler and also unsubscribe part of the code.
Appreciate some kind help.
That code is odd in a couple of ways.
The reason getHandler doesn't work is that the handler property of the object pushed on handlers is not the function that was passed in; it's the result of calling bind on that function. Formatted properly, this is subscribe:
subscribe(event, handler, context) {
if (typeof context === 'undefined') {
context = handler;
}
{
if (this.getHandler(event, handler) == null) {
this.handlers.push({
event: event,
handler: handler.bind(context), // ** NOTE **
key: Guid()
});
}
}
}
That value will never be equal to the original, by definition.
Instead, it should include the original handler as well so it can check for it later. Let's also get rid of the pointless standalone block:
subscribe(event, handler, context) {
if (typeof context === 'undefined') {
context = handler;
}
if (this.getHandler(event, handler) == null) {
this.handlers.push({
event: event,
handler: handler.bind(context),
originalHandler: handler, // ***
key: Guid()
});
}
}
Now, getHandler can look for matches with originalHandler. While we're there, let's stop looping when we find the handler rather than keeping going, and use the semantically-appropriate Array#find:
getHandler(event, handler) {
if (this.handlers == null || this.handlers.length < 1) {
return null;
}
let filtered = this.handlers.find(topic => topic.event === event && topic.originalHandler === handler);
return filtered;
}
There are other issues with the code (like binding the handler to itself if no context is provided), but a full code review is out of scope; the above is why getHandler doesn't work and thus why unsubscribe doesn't work. With that fix, unsubscribe should also work (though it seems odd to search twice).
I have a shimmed and polyfilled version of angularjs 1.3 working perfectly on ie8. Unfortunately when mootools is included on the page there are quite a few conflicts. I have managed to get a handle on all but one with the following which adds add / remove EventListener and dispatchEvent to Window.prototype, HTMLDocument.prototype and Element.prototype. It checks to see if mootools is loaded and if so it adds them differently.
!window.addEventListener && (function (WindowPrototype, DocumentPrototype, ElementPrototype, addEventListener, removeEventListener, dispatchEvent, registry) {
var addEventListenerFn = function(type, listener) {
var target = this;
registry.unshift([target, type, listener,
function (event) {
event.currentTarget = target;
event.preventDefault = function () {
event.returnValue = false;
};
event.stopPropagation = function () {
event.cancelBubble = true;
};
event.target = event.srcElement || target;
listener.call(target, event);
}]);
// http://msdn.microsoft.com/en-us/library/ie/hh180173%28v=vs.85%29.aspx
if (type === 'load' && this.tagName && this.tagName === 'SCRIPT') {
var reg = registry[0][3];
this.onreadystatechange = function (event) {
if (this.readyState === "loaded" || this.readyState === "complete") {
reg.call(this, {
type: "load"
});
}
}
} else {
this.attachEvent('on' + type, registry[0][3]);
}
};
var removeEventListenerFn = function(type, listener) {
for (var index = 0, register; register = registry[index]; ++index) {
if (register[0] == this && register[1] == type && register[2] == listener) {
if (type === 'load' && this.tagName && this.tagName === 'SCRIPT') {
this.onreadystatechange = null;
}
return this.detachEvent('on' + type, registry.splice(index, 1)[0][3]);
}
}
};
var dispatchEventFn = function(eventObject) {
return this.fireEvent('on' + eventObject.type, eventObject);
};
if(Element.prototype.$constructor && typeof Element.prototype.$constructor === 'function') {
Element.implement(addEventListener, addEventListenerFn);
Element.implement(removeEventListener, removeEventListenerFn);
Element.implement(dispatchEvent, dispatchEventFn);
Window.implement(addEventListener, addEventListenerFn);
Window.implement(removeEventListener, removeEventListenerFn);
Window.implement(dispatchEvent, dispatchEventFn);
} else {
WindowPrototype[addEventListener] = ElementPrototype[addEventListener] = addEventListenerFn;
WindowPrototype[removeEventListener] = ElementPrototype[removeEventListener] = removeEventListenerFn;
WindowPrototype[dispatchEvent] = ElementPrototype[dispatchEvent] = dispatchEventFn;
}
DocumentPrototype[addEventListener] = addEventListenerFn;
DocumentPrototype[removeEventListener] = removeEventListenerFn;
DocumentPrototype[dispatchEvent] = dispatchEventFn;
})(Window.prototype, HTMLDocument.prototype, Element.prototype, 'addEventListener', 'removeEventListener', 'dispatchEvent', []);
This has resolved all my errors bar one. When this function is called in Angular, when mootools is on the page, and element is a form addEventListener is undefined.
addEventListenerFn = function(element, type, fn) {
element.addEventListener(type, fn, false);
}
specifically this function is called from angulars formDirective like so
addEventListenerFn(formElement[0], 'submit', handleFormSubmission);
Any ideas why the form element still dosn't have the addEventListener function available?
Extending native type via Element.prototype in IE8 is considered very unreliable as the prototype is only partially exposed and certain things are not inheriting from it / misbehave.
http://perfectionkills.com/whats-wrong-with-extending-the-dom/
What MooTools does in this case is rather than work around the quirks of all edgecases that don't adhere to the correct proto chain (and because of IE6/7 before that) is to COPY the Element prototypes on the objects of the DOM nodes as you pass it through the $ selector.
This is not ideal, because.
var foo = document.id('foo');
// all known methods from Element.prototype are copied on foo, which now hasOwnProperty for them
Element.prototype.bar = function(){};
foo.bar(); // no own property bar, going up the chain may fail dependent on nodeType
Anyway, that aside - you can fix your particular problem by copying your methods from the Element.prototype to the special HTMLFormElement.prototype - any any other elements constructors you may find to differ.
It's not scalable, you may then get an error in say HTMLInputElement and so forth, where do you draw the line?
I want to do global event handling for reporting JavaScript errors. We have minified JS files in production so I'm attempting get it done with the help of sourcemap.
Unfortunately, uncaught errors (reported by the browser's top-level
error handler, window.onerror) do not currently include column numbers
in any current browser. The HTML5 Spec has been updated to require
this, so this may change in the near future.
Source : https://rollbar.com/docs/guides_sourcemaps/
So now I need to wrap backbone view events in try catch block. There should be generic way of extending Backbone.View. Probably somewhere at delegateEvents function.
Here is how finally I wrapped all jQuery event handlers in try-catch block.
// maintain a reference to the existing function
var oldOn = $.fn.on;
// ...before overwriting the jQuery extension point
$.fn.on = function(types, selector, data, fn, /*INTERNAL*/ one) {
// parameter correction for backward compatibility copied from `on` function of jQuery JavaScript Library v1.9.0
// Types can be a map of types/handlers
if (typeof types === "object") {
// ( types-Object, selector, data )
if (typeof selector !== "string") {
// ( types-Object, data )
data = data || selector;
selector = undefined;
}
for (type in types) {
this.on(type, selector, data, types[type], one);
}
return this;
}
if (data == null && fn == null) {
// ( types, fn )
fn = selector;
data = selector = undefined;
} else if (fn == null) {
if (typeof selector === "string") {
// ( types, selector, fn )
fn = data;
data = undefined;
} else {
// ( types, data, fn )
fn = data;
data = selector;
selector = undefined;
}
}
if (fn === false) {
fn = returnFalse;
} else if (!fn) {
return this;
}
// ENDS - parameter correction for backward compatibility copied from `on` function of jQuery JavaScript Library v1.9.0
if (fn) {
var origFn = fn;
var wrappedFn = function() {
try {
origFn.apply(this, arguments);
} catch (e) {
//handle the error here.
}
};
fn = wrappedFn;
}
return oldOn.apply(this, [types, selector, data, fn, /*INTERNAL*/ one]);
};
UPDATE:
There was a bug that jQuery-UI droppable divs were sticking to mouse pointer like glue ;) instead of getting dropped and it was traced to this code as culprit.
So please make sure you wrap this extension with condition if(!(arguments.length === 4 && arguments[1] === null)) {}
Like this.
// maintain a reference to the existing function
var oldOn = $.fn.on;
// ...before overwriting the jQuery extension point
$.fn.on = function(types, selector, data, fn, /*INTERNAL*/ one) {
// We can ignore .bind() calls - they are passed from jquery-ui or other outdated components
if(!(arguments.length === 4 && arguments[1] === null)) {
//rest of the above code here.
}
}
If I run this code on IE8 or lower, I get this error: Object doesn't support this property or method
var hasFlash = ((typeof navigator.plugins != "undefined" && typeof navigator.plugins["Shockwave Flash"] == "object") || (window.ActiveXObject && (new ActiveXObject("ShockwaveFlash.ShockwaveFlash")) != false));
Maybe the new ActiveXObject part is failing, because ActiveXObject is (in your current setup) not anything that the new operator can be applied to -- or 'ShockwaveFlash.ShockwaveFlash' isn't a valid input and therefore an exception is thrown.
You can however easily rewrite your code to address that problem:
var hasFlash = (function() {
if (typeof navigator.plugins != "undefined" && typeof navigator.plugins["Shockwave Flash"] == "object") {
return true;
} else if (typeof window.ActiveXObject != "undefined") {
try {
new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
return true;
} catch (e) { }
}
return false;
})();
I have a tiny function I use to only allow numeric input. It works great in IE, but I cannot get it to work in FireFox or Chrome. I have this js file loaded in my script tag of my HTML page.
var numberOnly = function(evt) {
var theEvent = evt || window.event;
var key = theEvent.keyCode || theEvent.which;
key = String.fromCharCode( key );
var regex = /[0-9]|\./;
if( !regex.test(key) ) {
theEvent.returnValue = false;
}
};
var wireElemToEvent = function(elemId, event, func){
var elem = document.getElementById(elemId);
if (typeof window.event !== 'undefined') {
elem.attachEvent("on" + event, func);
} else {
elem.addEventListener(event, func, true);
}
};
var wireEvents = function(){
wireElemToEvent("tbxQuantity", "keypress", numberOnly);
wireElemToEvent("tbxPhone", "keypress", numberOnly);
wireElemToEvent("tbxZip", "keypress", numberOnly);
};
window.onload = wireEvents;
Chrome tells me
file:///C:/Documents%20and%20Settings/xxx/Desktop/numbersonly/res/js/numbersonly.js:17Uncaught TypeError: Object #<an HTMLInputElement> has no method 'attachEvent'
Any idea what I am doing wrong?
In wireElemToEvent You may want to check that elem is not null after you initialize it. Also, it would be better to check the existence of elem.attachEvent and elem.addEventListener rather than whether window.event is defined.
Here is the function I use to attach events cross browser:
function eventListen( t, fn, o ) {
o = o || window;
var e = t+fn;
if ( o.attachEvent ) {
o['e'+e] = fn;
o[e] = function(){
o['e'+e]( window.event );
};
o.attachEvent( 'on'+t, o[e] );
}else{
o.addEventListener( t, fn, false );
}
}
And to use it:
eventListen('message', function(e){
var msg = JSON.parse(e.data);
...
});
I've had the same problem and, because I am a novice, was looking around for a day for a solution.
Apparently (typeof window.event !== 'undefined') doesn't stop Safari/Chrome from getting in that if statement. So it actually initializes the attachEvent and doesn't know what to do with it.
Solution:
var wireElemToEvent = function(elemId, event, func){
var elem = document.getElementById(elemId);
if (elem.attachEvent) {
elem.attachEvent("on" + event, func);
}
else { // if (elem.addEventListener) interchangeably
elem.addEventListener(event, func, true);
}
};
One good way to make cross-browser scripting easier is to use jQuery. Plus, there's no reason to reinvent the wheel. Why not try this jQuery plugin: jQuery ยป numeric