JavaScript Event prototype in IE8 - javascript

I'm trying to add a method to the Event prototype. In order to call/set preventDefault() or, in IE-speak returnValue = false and -if desired- stopPropagation() / cancelBubble = true;. I thought the code below would have sufficed.
Event = Event || window.Event;
//^^ makes the fiddle work on IE8 ^^
if(!(Event.prototype.stopEvent))
{
Event.prototype.stopEvent = function(propagate)
{
"use strict";
propagate = (propagate ? true : false);
if (this.preventDefault)
{
this.preventDefault();
if (propagate === false)
{
this.stopPropagation();
}
}
else
{
this.returnValue = false;
this.cancelBubble = !propagate;
}
return this;
};
}
Which seems to work, as you can see here. This fiddle shows OK in IE8, firefox and chrome. Though, when I add this to my script, IE8 breaks on the first line, saying 'Event is undefined'. Leaving out "use strict"; makes no difference at all.
Reluctantly, I tried this, too:
if (typeof Event === 'undefined')
{
var Event = window.Event || window.event;//FFS IE :-(
}
But to no avail: Error: 'Event.prototype' is null or not an object, so I got 1 line further. The thing is, the entire prototype method is a copy paste from my script, but what am I overlooking here? Any idea's/suggestions? Thanks
PS: I like Pure JavaScript, so please, don't suggest jQuery, prototypejs, dojo,... as a solution. I've just gotten rid of jQuery. (I like jQuery, but there is no need for it in this case)
Update
Things have taken a turn for the worse, I'm afraid. I found this MSDN reference. The entire page deals with DOM Element prototypes. It's pretty fair to say they are available and usable in IE8 (to some extent). On this page, this code caught my eye:
Event.prototype.stopPropagation = function ()
{
this.cancelBubble = true;
};
Event.prototype.preventDefault = function ()
{
this.returnValue = false;
};
It can be found ~3/4ths of the page down, in the section titled "Powerful Scenarios". This is, to my mind exactly the same thing as I want to do, but what's more: if I try this code via jsfiddle, it doesn't even work, whereas my jsfiddle (with my code) did work on IE8. This is just the last few lines of a snippet, but as far as I can work out, these few lines of code should work just fine. I've altered them as follows:
Event.prototype.stopPropagation = function ()
{
if (this.stopPropagation)
{
return this.stopPropagation();
}
this.cancelBubble = true;
};
Event.prototype.preventDefault = function ()
{
if (this.preventDefault)
{
return this.preventDefault();
}
this.returnValue = false;
};

I recently had (another) brainwave, and I think I found a better way of augmenting the event prototype. Strictly speaking, the Event prototype is not accessible in IE (<9), but it is, I found out accessible if you work your way back from an instance (well, the instance: window.event). So here's a snippet that works in all major browsers (and IE8 - not 7):
(function()
{
function ol(e)
{//we have an event object
e = e || window.event;
if (!e.stopEvent)
{
if (Object && Object.getPrototypeOf)
{//get the prototype
e = Object.getPrototypeOf(e);
}
else
{//getting a prototype in IE8 is a bit of a faff, this expression works on most objects, though
//it's part of my custom .getPrototypeOf method for IE
e = this[e.constructor.toString().match(/(function|object)\s+([A-Z][^\s(\]]+)/)[2]].prototype;
}
e.stopEvent = function(bubble)
{//augment it (e references the prototype now
bubble = bubble || false;
if (this.preventDefault)
{
this.preventDefault();
if (!bubble)
{
this.stopPropagation();
}
return this;
}
this.returnValue = false;
this.cancelBubble = !bubble;
return this;
};
}
alert(e.stopEvent ? 'ok' : 'nok');//tested, it alerts ok
if (this.addEventListener)
{
this.removeEventListener('load',ol,false);
return;
}
document.attachEvent('onkeypress',function(e)
{
e = e || window.event;
if (e.stopEvent)
{//another event, each time alerts ok
alert('OK!');
}
});
this.detachEvent('onload',ol);
}
if (this.addEventListener)
{
this.addEventListener('load',ol,false);
}
else
{
this.attachEvent('onload',ol);
}
})();
That way, the header doctype doesn't matter all that much: I've tested it using the <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">, and it works in FF, chrome and IE 8, no problems whatsoever. Using <!DOCTYPE html> to be safe, though
Hope this helps someone...

Its Standards versus Quirks mode. The JSFiddle page has a DOCTYPE declaration, albeit an incredibly simple one, <!DOCTYPE html>, that kicks the render into Standards mode. Chances are your web page does not have a DOCTYPE which leaves the render in Quirks mode. After adding that simple DOCTYPE to a page I built from your fiddle, it worked for me.

Related

Internet Explorer 11 issues: addEventListener not working

I am trying to add listener event for mouse down on a list of elements. The code works for chrome, but not for IE
document.getElementsByClassName('select2-result-selectable').forEach(function(item){
item.addEventListener('mousedown', function(e) { console.log( "User clicked on 'foo.'" );
e.preventDefault();});
})
This works on chrome, but not on IE 11.
I tried the following code as well.
document.getElementsByClassName('select2-result-selectable').forEach(function(item){
if (item.addEventListener){
item.addEventListener('mousedown',function(e){ console.log(e); e.preventDefault();
console.log( "User clicked on 'foo.'" );})
} else if (item.attachEvent){
item.attachEvent('mousedown',function(e){ console.log(e); e.preventDefault();
console.log( "User clicked on 'foo.'" );})
}
})
But this was again futile, it works for chrome, but not on IE. Any suggestions?
I suspect you'll find that it's not addEventListener that's the problem, although your second code block would have needed onmousedown rather than just mousedown in the attachEvent call (Microsoft used the "on" prefix on event names). But IE11 has addEventListener anyway, it would only be missing if IE11 were hobbling itself (which you can fix by adding the X-UA-Compatible header to your page in head):
<meta http-equiv="X-UA-Compatible" content="IE=edge">
...and turning off "compatibility view" for Intranet sites if necessary.
But, I think the problem is that you're trying to use forEach on an HTMLCollection. The return value of getElementsByClassName isn't an array, it's an HTMLCollection. The spec doesn't require HTMLCollection to have forEach (Chrome adds it as an extension). forEach is defined for NodeList (the type returned by querySelectorAll), but not HTMLCollection, and that addition is relatively new and not supported in IE.
So to use forEach, you'd do:
Array.prototype.forEach.call(document.getElementsByClassName('select2-result-selectable', function(item) {
// ...
});
Alternatively, you can polyfill forEach on HTMLCollection easily, though, as shown in my answer here. Here's a loop doing both NodeList (if necessary) and HTMLCollection (if necessary), and polyfilling forEach (if necessary) and (for browsers that have it) Symbol.iterator (IE11 doesn't, though, you may choose to leave that code off although it's harmless to leave it):
var ctors = [typeof NodeList !== "undefined" && NodeList, typeof HTMLCollection !== "undefined" && HTMLCollection];
for (var n = 0; n < ctors.length; ++n) {
var ctor = ctors[n];
if (ctor && ctor.prototype && !ctor.prototype.forEach) {
// (Yes, there's really no need for `Object.defineProperty` when doing the `forEach`)
ctor.prototype.forEach = Array.prototype.forEach;
if (typeof Symbol !== "undefined" && Symbol.iterator && !ctor.prototype[Symbol.iterator]) {
Object.defineProperty(ctor.prototype, Symbol.iterator, {
value: Array.prototype[Symbol.itereator],
writable: true,
configurable: true
});
}
}
}
Then your original code using forEach on HTMLCollection would work.
Your problem is using .foreach. It isn't supported in IE11
Change it for a regular for loop.
var myclasses = document.getElementsByClassName('select2-result-selectable')
for (var i = 0; i < myclasses.length; i++) {
array[i].addEventListener('mousedown', function(e) { console.log( "User clicked on 'foo.'");
}
Or you can use other language processors like babel to fix it and build your sites, depending on what your stack looks like.
This is untested as I don't have access to ie11 at the moment
you can have a utility like below
function addHandler (element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
}
that can work on all browsers,
In this case you should use like
addHandler(item, "mousedown", function() {....} )

Which Cross-Browser-EventListener is the right one (pure Javascript)

I need a CrossBrowser-EventListener in pure Javascript.
On the internet I found the following 2 versions.
Which one is the right / better one?
Could someone please explain the SECOND version?
(especially this paragraph is not clear to me: event_function.call(html_element);
Version 1:
function AddEvent(html_element, event_name, event_function)
{
if(html_element.attachEvent) //Internet Explorer
html_element.attachEvent("on" + event_name, event_function);
else if(html_element.addEventListener) // Everything else
html_element.addEventListener(event_name, event_function, false);
}
Version 2:
function AddEvent(html_element, event_name, event_function)
{
if(html_element.attachEvent) //Internet Explorer
html_element.attachEvent("on" + event_name, function() { event_function.call(html_element); }); //<-- This I don't understand
else if(html_element.addEventListener) // Everything else
html_element.addEventListener(event_name, event_function, false);
}
Preface: All modern browsers support addEventListener, even IE9+, with the caveat that IE9-IE11 will hobble themselves by default via (in)Compatibility Mode on intranet sets and (I think) in some other contexts. You can tell IE9-IE11 not to hobble themselves by sending the X-UA-Compatible header from the server, or including it as a meta tag at the beginning of head. (This answer claims you actually have to send it from the server, but I believe it's incorrect; it's just that if you put the meta tag further down, IE may ignore it.) So unless you need to support IE8, you probably don't need a cross-browser alternative anymore.
Neither of them does a thorough job of normalizing what the event handler deals with.
The differences you need to handle are:
The value of this when calling the handler
Where the event object comes from
The methods available on the event object
The part you don't understand:
html_element.attachEvent("on" + event_name, function() { event_function.call(html_element); }); //<-- This I don't understand
...is trying to handle the first of those, the value of this within the callback. Function#call calls a function allowing you to set a specific value for this to have during the call. So event_function.call(html_element) calls event_function with this equal to html_element.
Some time back, for this answer, I wrote this which does a fairly thorough job:
var hookEvent = (function() {
var div;
// The function we use on standard-compliant browsers
function standardHookEvent(element, eventName, handler) {
element.addEventListener(eventName, handler, false);
return element;
}
// The function we use on browsers with the previous Microsoft-specific mechanism
function oldIEHookEvent(element, eventName, handler) {
element.attachEvent("on" + eventName, function(e) {
e = e || window.event;
e.preventDefault = oldIEPreventDefault;
e.stopPropagation = oldIEStopPropagation;
handler.call(element, e);
});
return element;
}
// Polyfill for preventDefault on old IE
function oldIEPreventDefault() {
this.returnValue = false;
}
// Polyfill for stopPropagation on old IE
function oldIEStopPropagation() {
this.cancelBubble = true;
}
// Return the appropriate function; we don't rely on document.body
// here just in case someone wants to use this within the head
div = document.createElement('div');
if (div.addEventListener) {
div = undefined;
return standardHookEvent;
}
if (div.attachEvent) {
div = undefined;
return oldIEHookEvent;
}
throw "Neither modern event mechanism (addEventListener nor attachEvent) is supported by this browser.";
})();
Then you'd use it like this in your example:
hookEvent(document.getElementById("hd_vertical"), "click", function(e) {
// ...
});

How to find what is causing a preventDefault which overrides normal click behavior

I've inherited some large piece of code. Somewhere inside a way too generalised e.preventDefault() is prohibiting the normal behavior of an anchor click.
I thought about running profiler in Chrome webtools to see what is happening when clicking on a particular link, hoping to trace it back to the culprit statement. However I haven't had much luck
how can I trace back (if possible) a statement that is overriding normal click behavior, when clicking a link in Chrome webtools?
(I am using jQuery)
You should be able to override Event.prototype.preventDefault and add a debugger statement as its first line.
Run the following via the console.
var oldEPD = Event.prototype.preventDefault;
Event.prototype.preventDefault = function() {
debugger;
oldEPD.call(this);
};
Based on techfoobar answer, here's modern and more advanced version that is quite useful to debug event-related problems. Note it expects you to be using a modern env JS like Webpack/Babel but you can certainly make the same work with older JS syntax.
It's basically the same except the log message is more user-friendly. I try to compute a "meaningful selector" that will help you debug the problem:
click.stopPropagation() on section#nav-bar > a.Tappable-inactive.group-link.nav-bar-item.my-main-team > div.nav-bar-item-content > svg
// Logs all calls to preventDefault / stopPropagation in an user-friendly way
if ( process.env.NODE_ENV !== "production" ) {
(function monkeyPatchEventMethods() {
const logEventMethodCall = (event,methodName) => {
const MinimumMeaninfulSelectors = 3; // how much meaningful items we want in log message
const target = event.target;
const selector = (function computeSelector() {
const parentSelectors = [];
let node = target;
let minimumSelectors = 0;
do {
const meaningfulSelector = node.id ?
`#${node.id}` : node.classList.length > 0 ?
`.${Array.prototype.join.call(node.classList, '.')}` : undefined;
if ( meaningfulSelector ) minimumSelectors++;
const nodeSelector = `${node.tagName.toLowerCase()}${meaningfulSelector ? meaningfulSelector : ''}`;
parentSelectors.unshift(nodeSelector);
node = node.parentNode;
} while (node && node !== document && minimumSelectors < MinimumMeaninfulSelectors);
return parentSelectors.join(" > ");
})();
console.debug(`${event.type}.${methodName}() on ${selector}`,event);
};
const preventDefault = Event.prototype.preventDefault;
Event.prototype.preventDefault = function() {
logEventMethodCall(this,'preventDefault');
preventDefault.call(this);
};
const stopPropagation = Event.prototype.stopPropagation;
Event.prototype.stopPropagation = function() {
logEventMethodCall(this,'stopPropagation');
stopPropagation.call(this);
};
})();
}
https://gist.github.com/slorber/b1c0ffef56abd449c05476b5c609a36e
In addition to the various preventDefault() answers here, you can also see if in your HTML code, you're returning false at your link's OnClick event-handler, like that:
If you do, just remove it (It's true by default):
Maybe search your code on e.preventDefault and adding a breakpoint to that line. You can read the call stack when the breakpoint is triggered and you can possibly see what code overrides the click.

Cross-browser Getter and Setter

This works in modern Chrome/Firefox/Opera but fails in IE8. Haven't tried it in IE9. How can I make this cross-browser compatible, including IE7+? (Fiddle here.)
var foo = {
get test(){ return 'Works'; }
};
// foo.test should be 'Works'
I've seen some usage with __defineGetter__ but that threw an 'unrecognized method' error in IE8.
Here is the workaround for IE6/7/8. I performed the test and it works very well!
Update: The link is broken, you can see the code of from my testing here:
// Super amazing, cross browser property function, based on http://thewikies.com/
function addProperty(obj, name, onGet, onSet) {
// wrapper functions
var
oldValue = obj[name],
getFn = function () {
return onGet.apply(obj, [oldValue]);
},
setFn = function (newValue) {
return oldValue = onSet.apply(obj, [newValue]);
};
// Modern browsers, IE9+, and IE8 (must be a DOM object),
if (Object.defineProperty) {
Object.defineProperty(obj, name, {
get: getFn,
set: setFn
});
// Older Mozilla
} else if (obj.__defineGetter__) {
obj.__defineGetter__(name, getFn);
obj.__defineSetter__(name, setFn);
// IE6-7
// must be a real DOM object (to have attachEvent) and must be attached to document (for onpropertychange to fire)
} else {
var onPropertyChange = function (e) {
if (event.propertyName == name) {
// temporarily remove the event so it doesn't fire again and create a loop
obj.detachEvent("onpropertychange", onPropertyChange);
// get the changed value, run it through the set function
var newValue = setFn(obj[name]);
// restore the get function
obj[name] = getFn;
obj[name].toString = getFn;
// restore the event
obj.attachEvent("onpropertychange", onPropertyChange);
}
};
obj[name] = getFn;
obj[name].toString = getFn;
obj.attachEvent("onpropertychange", onPropertyChange);
}
}
I don't believe you can.
In IE8 and lower, property access is mere property access. There's no way to run function code without explicitly invoking the function.
I think in IE8 you may be able to with DOM elements, but I don't believe it works for regular native objects.
There is a "definePropery" method that will essentially allow you to create accessor methods (getters/setters) on Objects without the need to invoke a function call like setProp() / getProp().
The syntax is a little weird but I've been able to get this to work on Firefox, Chrome, Safari and IE9.
Say I have JavaScript Object called "Person".
function Person()
{
// set a default value //
this.__name = 'John';
// add getter & setter methods //
Object.defineProperty(this, "name", {
get: function() {
// additional getter logic
return this.__name;
},
set: function(val) {
this.__name = val;
// additional setter logic
}
});
}
var p = new Person();
console.log(p.name); // 'John'
p.name = 'Stephen';
console.log(p.name); // 'Stephen'
More info on Mozilla's site here.
You cannot, the syntax is not supported in browsers that did not implement it. Its going to be quite a while before you'll be able to use that syntax without having CBC problems. Be grateful IE6 is pretty much dead in North America.

prototype to javascript

What is the equivalent javascript code for this prototype code?
var Confirm = Class.create();
Confirm.prototype = {
initialize: function(element, message) {
this.message = message;
Event.observe($(element), 'click', this.doConfirm.bindAsEventListener(this));
},
doConfirm: function(e) {
if(! confirm(this.message))
e.stop();
}
}
Roughly speaking:
var Confirm = (function()
function Confirm(element, message) {
var self = this;
this.message = message;
hookEvent(element, "click", function(event) {
self.doConfirm(event);
});
}
Confirm.prototype.doConfirm = Confirm$doConfirm;
function Confirm$doConfirm(e) {
if (!confirm(this.message)) {
if (e.stopPropagation) {
e.stopPropagation();
}
else {
e.cancelBubble = true;
}
if (e.preventDefault) {
e.preventDefault();
}
else {
e.returnValue = false;
}
}
}
return Confirm;
})();
(You can shorten that slightly if you don't mind using anonymous functions; I prefer to help my tools help me by giving functions names.)
In the above, hookEvent is a utility function you'll have to provide that either calls addEventListener or attachEvent (to support IE8 and earlier) as appropriate, something like this:
function hookEvent(element, eventName, handler) {
// Very quick-and-dirty, recommend using a proper library,
// this is just for the purposes of the example.
if (typeof element.addEventListener !== "undefined") {
element.addEventListener(eventName, handler, false);
}
else if (typeof element.attachEvent !== "undefined") {
element.attachEvent("on" + eventName, function(event) {
return handler(event || window.event);
});
}
else {
throw "Browser not supported.";
}
}
Note how much more work is required for cross-browser compatibility. You don't have to use Prototype, but I do strongly recommend you use another decent library even if not Prototype, like jQuery, YUI, Closure, or any of several others. You'll save a lot of effort working around cross-browser differences and dealing with edge cases that come up by leveraging the significant work done by others in this area.
If your goal is to move off Prototype rather than moving off libraries entirely, here's that same thing using jQuery for instance:
var Confirm = (function()
function Confirm(element, message) {
this.message = message;
$(element).click($.proxy(this.doConfirm, this));
}
Confirm.prototype.doConfirm = Confirm$doConfirm;
function Confirm$doConfirm(e) {
if (!confirm(this.message)) {
return false;
}
}
return Confirm;
})();
That uses $().click for hookEvent, $.proxy to avoid creating an explicit closure (still creates one, just does it for you behind the scenes), and the fact that in jQuery event handlers, return false is the same as both stopping propagation and preventing the default action (just like Prototype's stop). You could also use stopPropagation and preventDefault without worrying about browser differences; jQuery handles it for you. Most libraries will.
If you move off Prototype but still want something akin to its Class feature, here's one you can drop into your code. My goal in that blog post wasn't to replace Prototype's Class (at the time I was using Prototype), but rather to fix what I found was Prototype's hugely inefficient way of handling supercalls. But in doing that, well, a full implementation that can replace Class got created. I really need to update the terminology in it, because of course it's not about classes at all (JavaScript doesn't have classes), it's just about some handy plumbing sugar for JavaScript's prototypical inheritance.
(Inb4 Raynos arrives with his pd craziness.)
function Confirm(element, message) {
this.message = message;
element.addEventListener("click", this.doConfirm.bind(this), false);
}
Confirm.prototype.doConfirm = function (e) {
if (!confirm(this.message)) {
e.preventDefault();
e.stopPropagation();
}
};
Depends on how abstract you want it to be. In the simplest case:
<button onclick="return confirm('Are you sure?')">something</button>

Categories

Resources