Attaching properties to bubbled event object in Javascript - javascript

In Chrome/Firefox I can attach my custom properties to an event object in one handler and read them in a different handler for the same event even if the event handling is bubbled up.
I cannot do the same in IE. My custom property is lost while event is bubbled up.
Do you know if there's any solution or workaround to this?
The following is an example of that problem:
<div id="div1">
<input type="button" value="Foo" id="button1">
</div>
<script>
function attach(el, event, fn) {
if (el.addEventListener) {
el.addEventListener(event, fn);
} else if (el.attachEvent) {
el.attachEvent('on'+event, fn);
}
}
attach(document.getElementById("button1"), 'click', function (event) {
event.abc = "done";
return true;
});
attach(document.getElementById("div1"), 'click', function (event) {
alert(event.abc);
return true;
});
</script>

According with my test you cannot add property to event object in IE (IE8 tested).
Try next code:
attach(document.getElementById("button1"), 'click', function (ev) {
//ev=ev||event;
//ev.abc = "done";
// next lines show you why you cannot save properties in event object
var xx1=event;
var xx2=event;
alert(xx1===xx2); // // showed *false* in IE8, but expected *true*
return true;
});
I am not sure but maybe, when event object is requested, IE8 always return new object, that contains same properties/values as previous requested.

Related

Is there an event for any kind of interaction (click, mousedown, keydown etc) [duplicate]

I'd like to accomplish the following code using a wildcard (that I don't think exists?)
myObject.element = document.getElementsByClassName('js-myObject');
myObject.element.addEventListener('click', myObject.click);
myObject.element.addEventListener('mouseover', myObject.mouseover);
//etc..
So far, I have the following
myObject.controller = function(e){
if( (e.type in myObject) && (typeof myObject[e.type] ==='function') ){
myObject[e.type](e);
}
};
//but the listeners still have to be assigned as such
myObject.element = document.getElementsByClassName('js-myObject');
myObject.element.addEventListener('click', myObject.controller);
myObject.element.addEventListener('mouseover', myObject.controller);
//etc...
// but I want to do (and it doesn't work)
myObject.element.addEventListener('*', myObject.controller);
Any suggestions on methods other than an array of events and a foreach statement?
Edit, my current solution
I've accepted an answer below (there's no wildcard available, and it's a bad idea to parse potentially hundreds of events)
For those looking for a similar function, I've settled on the following approach, at least for now.
for(var prop in myObject){
if (!myObject.hasOwnProperty(prop){continue;}
if (typeof myObject[prop] !== 'function'){continue;}
myObject.element.addEventListener(prop, myObject[prop]);
}
The upside is a handler for custom events that I don't have to go back and add listeners for. The downside is that I have to ensure this function is called after every myObject.someEvent() is defined. I call it in myObject.init(); which works for me just fine. Note that this solution wouldn't fit my previous specs, because it uses a for/each loop -- but it accomplishes what i really wanted to accomplish and a big thanks to #torazaburo for clearly defining the technical limitations and lack of wisdom in my initial plan.
How to implement addEventListener('*') yourself
For all native events, we can retrieve a list of supported events by iterating over the target.onevent properties and installing our listener for all of them.
for (const key in target) {
if(/^on/.test(key)) {
const eventType = key.substr(2);
target.addEventListener(eventType, listener);
}
}
The only other way that events are emitted which I know of is via EventTarget.dispatchEvent, which every Node and thefore every Element inherits.
To listen for all these manually triggered events, we can proxy the dispatchEvent method globally and install our listener just-in-time for the event whose name we just saw ✨ ^^
const dispatchEvent_original = EventTarget.prototype.dispatchEvent;
EventTarget.prototype.dispatchEvent = function (event) {
if (!alreadyListenedEventTypes.has(event.type)) {
target.addEventListener(event.type, listener, ...otherArguments);
alreadyListenedEventTypes.add(event.type);
}
dispatchEvent_original.apply(this, arguments);
};
🔥 function snippet 🔥
function addEventListenerAll(target, listener, ...otherArguments) {
// install listeners for all natively triggered events
for (const key in target) {
if (/^on/.test(key)) {
const eventType = key.substr(2);
target.addEventListener(eventType, listener, ...otherArguments);
}
}
// dynamically install listeners for all manually triggered events, just-in-time before they're dispatched ;D
const dispatchEvent_original = EventTarget.prototype.dispatchEvent;
function dispatchEvent(event) {
target.addEventListener(event.type, listener, ...otherArguments); // multiple identical listeners are automatically discarded
dispatchEvent_original.apply(this, arguments);
}
EventTarget.prototype.dispatchEvent = dispatchEvent;
if (EventTarget.prototype.dispatchEvent !== dispatchEvent) throw new Error(`Browser is smarter than you think!`);
}
// usage example
function addEventListenerAll(target, listener, ...otherArguments) {
// install listeners for all natively triggered events
for (const key in target) {
if (/^on/.test(key)) {
const eventType = key.substr(2);
target.addEventListener(eventType, listener, ...otherArguments);
}
}
// dynamically install listeners for all manually triggered events, just-in-time before they're dispatched ;D
const dispatchEvent_original = EventTarget.prototype.dispatchEvent;
function dispatchEvent(event) {
target.addEventListener(event.type, listener, ...otherArguments); // multiple identical listeners are automatically discarded
dispatchEvent_original.apply(this, arguments);
}
EventTarget.prototype.dispatchEvent = dispatchEvent;
if (EventTarget.prototype.dispatchEvent !== dispatchEvent) throw new Error(`Browser is smarter than you think!`);
}
// usage example
addEventListenerAll(window, (evt) => {
console.log(evt.type);
});
document.body.click();
document.body.dispatchEvent(new Event('omg!', { bubbles: true }));
// usage example with `useCapture`
// (also receives `bubbles: false` events, but in reverse order)
addEventListenerAll(
window,
(evt) => { console.log(evt.type); },
true
);
document.body.dispatchEvent(new Event('omfggg!', { bubbles: false }));
There's no wild card, but using jQuery you can have 1 long event listener line, rather than multiple.
How do you log all events fired by an element in jQuery?
Like so:
('body').on("click mousedown mouseup focus blur keydown change dblclick mousemove mouseover mouseout mousewheel keydown keyup keypress textInput touchstart touchmove touchend touchcancel resize scroll zoom select change submit reset",function(e){
console.log(e);
});
A little late to the party, but here's how I add all event listeners & log them to the console:
Object.keys(window).forEach(key => {
if(/./.test(key)){
window.addEventListener(key.slice(2), event => {
console.log(key, event)
})
}
})
There is no feature to add a * listener, and I doubt if you want hundreds of events on an element being listened for anyway.
By the way, instead of your roll-your-own architecture for a generic event handler in the form of your myObject.controller, you should use the under-appreciated approach involving the EventListener interface and the handleEvent method it defines.
I'm not a 100% if this is what you're looking for, but I think this is what you're looking for.
the document which is what we usually tag into for our events doesn't contain the events. It's actually the grandparent class.
document -> HTMLDocument -> Document (where you could find the onclick* events)
as you could see we can then do this. just trim the on at the start of the events
Object.keys(document.__proto__.__proto__).reduce((arr, event)=> {
if(event.startsWith('on')) return [...arr, event.substr(2)];
return arr;
}, [])
which would return something like:
["readystatechange", "pointerlockchange", "pointerlockerror", "beforecopy", "beforecut", "beforepaste", "freeze", "resume", "search", "securitypolicyviolation", "visibilitychange", "copy", "cut", "paste", "abort", "blur", "cancel", "canplay", "canplaythrough", "change", "click", "close", "contextmenu", "cuechange", "dblclick", "drag", "dragend", "dragenter", "dragleave", "dragover", "dragstart", "drop", "durationchange", "emptied", "ended", "error", "focus", "formdata", "input", "invalid", "keydown", "keypress", "keyup", "load", "loadeddata", "loadedmetadata", "loadstart", "mousedown", "mouseenter", "mouseleave", "mousemove", "mouseout", "mouseover", "mouseup", "mousewheel", "pause", "play", "playing", "progress", "ratechange", "reset", "resize", "scroll", "seeked", "seeking", "select", "stalled", "submit", "suspend", "timeupdate", "toggle", "volumechange", "waiting", "webkitanimationend", "webkitanimationiteration", "webkitanimationstart", "webkittransitionend", "wheel", "auxclick", "gotpointercapture", "lostpointercapture", "pointerdown", "pointermove", "pointerup", "pointercancel", "pointerover", "pointerout", "pointerenter", "pointerleave", "selectstart", "selectionchange", "animationend", "animationiteration", "animationstart", "transitionend", "fullscreenchange", "fullscreenerror", "webkitfullscreenchange", "webkitfullscreenerror", "pointerrawupdate"]
this is just something quick I came up with and I'm sure there are better answers from smarter folks. you might also want to check out https://developer.mozilla.org/en-US/docs/Web/Events which contains what you just might need.
getElementsByClassName already returns an array (or more specifically, an HTMLCollection), not a single element. Just do it in a loop.
myObject.element = document.getElementsByClassName('js-myObject');
for(var i=0; i<myObject.element.length; i++){
myObject.element[i].addEventListener('click', myObject.controller);
myObject.element[i].addEventListener('mouseover', myObject.controller);
}
Edit... after clarification.. loops and arrays are still useful
var e = ['click', 'hover', 'focus', 'mouseover', 'mouseout'];
myObject.element = document.getElementsByClassName('js-myObject');
for(var i=0; i<myObject.element.length; i++){
for(var n=0; n<e.length; n++)
myObject.element[i].addEventListener(e[n], myObject.controller);
}
To find all event listeners on the page
getEventListeners(document)
Reference
To list them a little nicer
for (const [key, value] of Object.entries(getEventListeners(document))) {
console.log(`${key}`)
}
I will leave it up to you to actually do something with each, but now you can at least see all that exist on the page, you can open DevTools and try it out on this page and see for yourself it does work.
Something like this ?
document.addEventListener('click', function (event) {
if (event.target.closest('.scroll')) {
// Do something...
}
if (event.target.matches('[data-some-attribute]')) {
// Do something else...
}
if (event.target.matches('#my-form')) {
// Do another t hing...
}
}, false)

HTML onchange event sometimes fires twice on input checkbox element

This happens to me in Chrome 79.0.
CodePen replicating the issue:
https://codepen.io/puckowski/pen/eYmEbVz
I have a basic input element which looks like:
<input type="checkbox">
In JavaScript, I define an onchange property like so:
let eventObj = {
onchange: function() { console.log('event fired'); }
}
I then set my input element's onchange property like so:
for (let [k, v] of Object.entries(eventObj)) {
vType = typeof v;
if (vType === 'function') {
inputElem[k] = v; // inputElem is the correct element in the document and not undefined
}
}
Effectively, inputElem is defined by:
inputElem = document.getElementById('foo');
The onchange event is bound only once per the method above.
When I click on the checkbox, sometimes the onchange event fires once, sometimes it fires twice.
This doesn't seem to be an event bubbling issue. If I change the bound function to the following:
function(evt) { console.log('event fired'); evt.stopPropagation(); }
The onchange event will still fire twice occasionally.
Any ideas as to what is going on? Is this perhaps a element focus issue?
Here is what worked fine for me:
function(evt) {
console.log('event fired');
evt.stopImmediatePropagation();
}

How to determine that two HTML events have the same origin (and then determine "event was processed already")

How to determine that two HTML events have the same origin? What I need is to process click event only once (without stopPropogation because stopPropogation of clicks is "not friednly to dropdowns, modals, etc")
Comparision by originalEvent (using jQuery) if (event.originalEvent != processedEvent.originalEvent) works well, but I want to do the same in vanilla js.
Do we have convinient pure js method to do the same?
var processedEvent = null;
$("#myButton").click(
function(event){
console.log("this should be processed once")
processedEvent = event;
}
)
$("#myPanel").click(
function(event){
if (event.originalEvent == processedEvent.originalEvent)
console.log("nice. we are going to ingore event")
else
console.log("error. event processed second time")
processedEvent=null;
}
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="myPanel">
<button id="myButton">Click Me jQuery</button>
</div>
</div>
PreventDefault is a pretty standard way to do this. You call event.preventDefault() in your innermost handler, then your outer one should check if (!event.isDefaultPrevented()) { for jquery, or if (!event.defaultPrevented) { for vanilla js.
You can add custom properties to the event object as flags, like event._customWasHandled = true, then check for those properties in later handlers.
I was blind. In vanilla javascript opposite to jQuery events are the same...
var processedEvent = null;
document.addEventListener('click', function (event) {
if (!event.target.matches('#myButton2')) return;
console.log("this should be processed once")
processedEvent = event;
}, false);
document.addEventListener('click', function (event) {
if (event == processedEvent)
console.log("nice. we are going to ingore event")
else
console.log("error. event processed second time")
processedEvent=null;
}, false);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="myPanel2">
<button id="myButton2">Click Me Vanilla</button>
</div>

preventDefault in function with more parameters?

I have a function that is run when clicking an input in the DOM. I want to stop the element from being checked until my function can approve it. I'm trying to do this using e.preventDefault(); or event.preventDefault(); (is there any difference?!) but I'm not succeeding, what am I doing wrong?
This is my code without the preventDefault-part.
$(document).on("click","[data-item]", function() {
cart.updateCart(this);
});
cart.updateCart = function(target) {
// do stuff and perhaps check the input element
}
I tried this, which is not working:
$(document).on("click","[data-item]", function() {
cart.updateCart(this, event);
});
cart.updateCart = function(target, event) {
event.preventDefault();
console.log(event); // returns MouseEvent -- is this even the correct "event" ?
// do stuff and perhaps check the input element
}
I think I'm not "getting" how this works. Perhaps someone can explain how this works and what I'm doing wrong?
event should be the first parameter passed into your click handler. So your code really should be like this.:
$(document).on("click","[data-item]", function(event) {
cart.updateCart(this, event);
});
cart.updateCart = function(target, event) {
event.preventDefault();
console.log(event); // returns MouseEvent -- is this even the correct "event" ?
// do stuff and perhaps check the input element
}
You can read more about preventing default actions here.

How can I add an event listener for all events in javascript without listing them individually?

I'd like to accomplish the following code using a wildcard (that I don't think exists?)
myObject.element = document.getElementsByClassName('js-myObject');
myObject.element.addEventListener('click', myObject.click);
myObject.element.addEventListener('mouseover', myObject.mouseover);
//etc..
So far, I have the following
myObject.controller = function(e){
if( (e.type in myObject) && (typeof myObject[e.type] ==='function') ){
myObject[e.type](e);
}
};
//but the listeners still have to be assigned as such
myObject.element = document.getElementsByClassName('js-myObject');
myObject.element.addEventListener('click', myObject.controller);
myObject.element.addEventListener('mouseover', myObject.controller);
//etc...
// but I want to do (and it doesn't work)
myObject.element.addEventListener('*', myObject.controller);
Any suggestions on methods other than an array of events and a foreach statement?
Edit, my current solution
I've accepted an answer below (there's no wildcard available, and it's a bad idea to parse potentially hundreds of events)
For those looking for a similar function, I've settled on the following approach, at least for now.
for(var prop in myObject){
if (!myObject.hasOwnProperty(prop){continue;}
if (typeof myObject[prop] !== 'function'){continue;}
myObject.element.addEventListener(prop, myObject[prop]);
}
The upside is a handler for custom events that I don't have to go back and add listeners for. The downside is that I have to ensure this function is called after every myObject.someEvent() is defined. I call it in myObject.init(); which works for me just fine. Note that this solution wouldn't fit my previous specs, because it uses a for/each loop -- but it accomplishes what i really wanted to accomplish and a big thanks to #torazaburo for clearly defining the technical limitations and lack of wisdom in my initial plan.
How to implement addEventListener('*') yourself
For all native events, we can retrieve a list of supported events by iterating over the target.onevent properties and installing our listener for all of them.
for (const key in target) {
if(/^on/.test(key)) {
const eventType = key.substr(2);
target.addEventListener(eventType, listener);
}
}
The only other way that events are emitted which I know of is via EventTarget.dispatchEvent, which every Node and thefore every Element inherits.
To listen for all these manually triggered events, we can proxy the dispatchEvent method globally and install our listener just-in-time for the event whose name we just saw ✨ ^^
const dispatchEvent_original = EventTarget.prototype.dispatchEvent;
EventTarget.prototype.dispatchEvent = function (event) {
if (!alreadyListenedEventTypes.has(event.type)) {
target.addEventListener(event.type, listener, ...otherArguments);
alreadyListenedEventTypes.add(event.type);
}
dispatchEvent_original.apply(this, arguments);
};
🔥 function snippet 🔥
function addEventListenerAll(target, listener, ...otherArguments) {
// install listeners for all natively triggered events
for (const key in target) {
if (/^on/.test(key)) {
const eventType = key.substr(2);
target.addEventListener(eventType, listener, ...otherArguments);
}
}
// dynamically install listeners for all manually triggered events, just-in-time before they're dispatched ;D
const dispatchEvent_original = EventTarget.prototype.dispatchEvent;
function dispatchEvent(event) {
target.addEventListener(event.type, listener, ...otherArguments); // multiple identical listeners are automatically discarded
dispatchEvent_original.apply(this, arguments);
}
EventTarget.prototype.dispatchEvent = dispatchEvent;
if (EventTarget.prototype.dispatchEvent !== dispatchEvent) throw new Error(`Browser is smarter than you think!`);
}
// usage example
function addEventListenerAll(target, listener, ...otherArguments) {
// install listeners for all natively triggered events
for (const key in target) {
if (/^on/.test(key)) {
const eventType = key.substr(2);
target.addEventListener(eventType, listener, ...otherArguments);
}
}
// dynamically install listeners for all manually triggered events, just-in-time before they're dispatched ;D
const dispatchEvent_original = EventTarget.prototype.dispatchEvent;
function dispatchEvent(event) {
target.addEventListener(event.type, listener, ...otherArguments); // multiple identical listeners are automatically discarded
dispatchEvent_original.apply(this, arguments);
}
EventTarget.prototype.dispatchEvent = dispatchEvent;
if (EventTarget.prototype.dispatchEvent !== dispatchEvent) throw new Error(`Browser is smarter than you think!`);
}
// usage example
addEventListenerAll(window, (evt) => {
console.log(evt.type);
});
document.body.click();
document.body.dispatchEvent(new Event('omg!', { bubbles: true }));
// usage example with `useCapture`
// (also receives `bubbles: false` events, but in reverse order)
addEventListenerAll(
window,
(evt) => { console.log(evt.type); },
true
);
document.body.dispatchEvent(new Event('omfggg!', { bubbles: false }));
There's no wild card, but using jQuery you can have 1 long event listener line, rather than multiple.
How do you log all events fired by an element in jQuery?
Like so:
('body').on("click mousedown mouseup focus blur keydown change dblclick mousemove mouseover mouseout mousewheel keydown keyup keypress textInput touchstart touchmove touchend touchcancel resize scroll zoom select change submit reset",function(e){
console.log(e);
});
A little late to the party, but here's how I add all event listeners & log them to the console:
Object.keys(window).forEach(key => {
if(/./.test(key)){
window.addEventListener(key.slice(2), event => {
console.log(key, event)
})
}
})
There is no feature to add a * listener, and I doubt if you want hundreds of events on an element being listened for anyway.
By the way, instead of your roll-your-own architecture for a generic event handler in the form of your myObject.controller, you should use the under-appreciated approach involving the EventListener interface and the handleEvent method it defines.
I'm not a 100% if this is what you're looking for, but I think this is what you're looking for.
the document which is what we usually tag into for our events doesn't contain the events. It's actually the grandparent class.
document -> HTMLDocument -> Document (where you could find the onclick* events)
as you could see we can then do this. just trim the on at the start of the events
Object.keys(document.__proto__.__proto__).reduce((arr, event)=> {
if(event.startsWith('on')) return [...arr, event.substr(2)];
return arr;
}, [])
which would return something like:
["readystatechange", "pointerlockchange", "pointerlockerror", "beforecopy", "beforecut", "beforepaste", "freeze", "resume", "search", "securitypolicyviolation", "visibilitychange", "copy", "cut", "paste", "abort", "blur", "cancel", "canplay", "canplaythrough", "change", "click", "close", "contextmenu", "cuechange", "dblclick", "drag", "dragend", "dragenter", "dragleave", "dragover", "dragstart", "drop", "durationchange", "emptied", "ended", "error", "focus", "formdata", "input", "invalid", "keydown", "keypress", "keyup", "load", "loadeddata", "loadedmetadata", "loadstart", "mousedown", "mouseenter", "mouseleave", "mousemove", "mouseout", "mouseover", "mouseup", "mousewheel", "pause", "play", "playing", "progress", "ratechange", "reset", "resize", "scroll", "seeked", "seeking", "select", "stalled", "submit", "suspend", "timeupdate", "toggle", "volumechange", "waiting", "webkitanimationend", "webkitanimationiteration", "webkitanimationstart", "webkittransitionend", "wheel", "auxclick", "gotpointercapture", "lostpointercapture", "pointerdown", "pointermove", "pointerup", "pointercancel", "pointerover", "pointerout", "pointerenter", "pointerleave", "selectstart", "selectionchange", "animationend", "animationiteration", "animationstart", "transitionend", "fullscreenchange", "fullscreenerror", "webkitfullscreenchange", "webkitfullscreenerror", "pointerrawupdate"]
this is just something quick I came up with and I'm sure there are better answers from smarter folks. you might also want to check out https://developer.mozilla.org/en-US/docs/Web/Events which contains what you just might need.
getElementsByClassName already returns an array (or more specifically, an HTMLCollection), not a single element. Just do it in a loop.
myObject.element = document.getElementsByClassName('js-myObject');
for(var i=0; i<myObject.element.length; i++){
myObject.element[i].addEventListener('click', myObject.controller);
myObject.element[i].addEventListener('mouseover', myObject.controller);
}
Edit... after clarification.. loops and arrays are still useful
var e = ['click', 'hover', 'focus', 'mouseover', 'mouseout'];
myObject.element = document.getElementsByClassName('js-myObject');
for(var i=0; i<myObject.element.length; i++){
for(var n=0; n<e.length; n++)
myObject.element[i].addEventListener(e[n], myObject.controller);
}
To find all event listeners on the page
getEventListeners(document)
Reference
To list them a little nicer
for (const [key, value] of Object.entries(getEventListeners(document))) {
console.log(`${key}`)
}
I will leave it up to you to actually do something with each, but now you can at least see all that exist on the page, you can open DevTools and try it out on this page and see for yourself it does work.
Something like this ?
document.addEventListener('click', function (event) {
if (event.target.closest('.scroll')) {
// Do something...
}
if (event.target.matches('[data-some-attribute]')) {
// Do something else...
}
if (event.target.matches('#my-form')) {
// Do another t hing...
}
}, false)

Categories

Resources