Capture all the events (javascript) - javascript

I want to be able to capture all events that are both created and dispatched and fire some callback when that happens.
Also, I would like to be able to fire a callback anytime an event is paired with an event listener.
Problems include: dynamically added elements, events whose propagation or bubbling is prevented, and custom events that are generated dynamically. I imagine there would need to be a prototyping of dispatchEvent or something, but I am unsure. Is this even possible?

Some event basics:
Events are dispatched "on" a DOM object (usually an element) that is the event target.
Events can firstly propagate down to child elements in a capture phase. This phase is rarely used since it wasn't supported by some widely used browsers until recently.
Events can secondly propagate up to parent elements in a bubbling phase. This phase is commonly used.
Some events don't propagate, they have neither a capture or bubble phase (e.g. focus, blur and submit events). Some events that propagate in some browsers don't propagate in others.
DOM elements that respond to events have an event handler. It can be set to listen for particular events and call a listener function when that event reaches the element, either during capture, bubbling or if the element is an event target.
Listeners can cancel propagation, e.g. a click event on a span inside a link can cancel propagation so the link doesn't get the click
Given the above, it is a practical impossibility to "capture all events" using the Events API. It would require establishing a listener for every event type on every element and be impossible to capture custom events because you have to know about them to set an appropriate listener.
I imagine there would need to be a prototyping of dispatchEvent or something
dispatchEvent is a method of an Event instance, it's not specified to be a constructor (there is no requirement for it to have an internal [[Construct]] method) so not practical to use as such. Browsers aren't required to implement prototype inheritance for host objects (though most do), and the implementation details of host objects and methods are largely hidden, so this is not an option.
You might try extending the Event API, but you really should not mess with host objects.
It seems that you are concerned about dynamically added elements. There is a strategy called "event delegation", where you work out the events you need to listen for, then setup listeners as close to the event targets as you can on an element that doesn't change (e.g. a table element if you are dynamically adding and removing table rows, or a container div for other elements) for the specific event types you need to respond to.
You can also have the functions that are modifying the DOM dispatch custom events to add listeners or whatever.

If you really want to do this, then you can override addEventListener to keep track of events being registered and fired.
var myEventManager = (function() {
var old = EventTarget.prototype.addEventListener,
listeners = [],
events = [];
EventTarget.prototype.addEventListener = function(type, listener) {
function new_listener(listener) {
return function(e) {
events.push(e); // remember event
return listener.call(this, e); // call original listener
};
}
listeners.push([type, listener]); // remember call
return old.call(this, type, new_listener(listener)); // call original
};
return {
get_events: function() { return events; },
get_listeners: function() {return listeners; }
};
}());
However, there are uncountable reasons not to do this, not least the fact that you will quickly run out of memory as you record thousands of events such as mouse moves. This will also not capture event listeners set in ways such as elt.onclick. Nor of course will it catch listeners set up via the old IE attachEvent API. Most importantly, it will not help with you that events that are generated and listened for internally, such as a mouse click on a check box. (A complete solution would also require handling removeEventListener.)
You can also override createEvent and dispatch in similar fashion, but again, that will capture only events that are explicitly created or dispatched in the JS code.
If you really want to do what you seem to be wanting to, I guess you need to fork Chrome.

Related

Check for event listener collisions with Javascript [duplicate]

There are two scripts in a document:
// my_script.js goes first
document.onclick = function() {
alert("document clicked");
};
// other_script.js comes after
// this overrides the onclick of my script,
// and alert will NOT be fired
document.onclick = function() {
return false;
};
To make sure my click event does not get overridden by other script, I switched to addEventListener.
// my_script.js goes first
document.addEventListener("click", function() {
alert("document clicked");
}, false);
// other_script.js comes after
document.addEventListener("click", function() {
return false;
}, false);
Now I got another question. Since return false in the second code is defined after alert, how come it does not prevent alert from being called?
What if I want my script to get total control of click event (like return false all the time disregarding events defined in other scripts)?
What if I want my script to get total control of click event (like return false all the time disregarding events defined in other scripts)?
If you can register your handler first, before they do, you can do that, provided the browser you're using correctly implements DOM3 events (which it probably does unless it's IE8 or earlier).
There are (at least) four things involved here:
Preventing the default.
Stopping propagation to ancestor elements.
Stopping other handlers on the same element from being called.
The order in which handlers are called.
In order:
1. Preventing the default
This is what return false from a DOM0 handler does. (Details: The Story on Return False.) The equivalent in DOM2 and DOM3 is preventDefault:
document.addEventListener("click", function(e) {
e.preventDefault();
}, false);
Preventing the default may not be all that relevant to what you're doing, but since you were using return false in your DOM0 handler, and that prevents the default, I'm including it here for completeness.
2. Stopping propagation to ancestor elements
DOM0 handlers have no way to do this. DOM2 ones do, via stopPropagation:
document.addEventListener("click", function(e) {
e.stopPropagation();
}, false);
But stopPropagation doesn't stop other handlers on that same element getting called. From the spec:
The stopPropagation method is used prevent further propagation of an event during event flow. If this method is called by any EventListener the event will cease propagating through the tree. The event will complete dispatch to all listeners on the current EventTarget before event flow stops.
(My emphasis.)
3. Stopping other handlers on the same element from being called
Naturally, this didn't come up for DOM0, because there couldn't be other handlers for the same event on the same element. :-)
As far as I'm aware, there's no way to do this in DOM2, but DOM3 gives us stopImmediatePropagation:
document.addEventListener("click", function(e) {
e.stopImmediatePropagation();
}, false);
Some libraries offer this feature (even on non-DOM3 systems like IE8) for handlers hooked up via the library, see below.
4. The order in which handlers are called
Again, not something that related to DOM0, because there couldn't be other handlers.
In DOM2, the specification explicitly says that the order in which the handlers attached to an element are called is not guaranteed; but DOM3 changes that, saying that handlers are called in the order in which they're registered.
First, from DOM2 Section 1.2.1:
Although all EventListeners on the EventTarget are guaranteed to be triggered by any event which is received by that EventTarget, no specification is made as to the order in which they will receive the event with regards to the other EventListeners on the EventTarget.
But this is superceded by DOM3 Section 3.1:
Next, the implementation must determine the current target's candidate event listeners. This must be the list of all event listeners that have been registered on the current target in their order of registration.
(My emphasis.)
Some libraries guarantee the order, provided you hook up the events with the library.
It's also worth noting that in Microsoft's predecessor to DOM2 (e.g., attachEvent), it was the opposite of DOM3's order: The handlers were called in reverse order of registration.
So taking #3 and #4 together, if you can register your handler first, it will get called first, and you can use stopImmediatePropagation to prevent other handlers getting called. Provided the browser implements DOM3 correctly.
All of this (including the fact that IE8 and earlier don't even implement DOM2 events, much less DOM3) is one reason people use libraries like jQuery, some of which do guarantee the order (as long as everything is hooking up their handlers via the library in question) and offer ways to stop even other handlers on the same element getting called. (With jQuery, for instance, the order is the order in which they were attached, and you can use stopImmediatePropagation to stop calls to other handlers. But I'm not trying to sell jQuery here, just explaining that some libs offer more functionality than the basic DOM stuff.)

jQuery best way to apply eventhandlers

I'm working on a grid with 20 columns and 100+ rows. Every column has an input field. Now I want to put eventHandlers on the input fields like change and keyup, focus, and much more. So there can be 20*100 events = 2000!
What is the best way to do this? I've read something about eventHandlers and memory problems.
This is how I think I should do it:
$("#grid input").each(function() {
$(this).keyup(function() {
//
});
$(this).change(function() {
//
});
});
Or is this the best way to do it?
$("#grid").keyup(function() {
//
});
You're looking for event delegation.
$("#grid").on("change", "input", function() {
// Handle the event here, `this` refers to the input where it occurred
});
That attaches one handler (on #grid), which jQuery then fires only if the event passed through an element matching the second selector. It calls the handler as though the handler were attached to the actual input. Even events like focus that don't natively bubble are supported through jQuery's mechanations.
More in the on documentation.
I'd suggest using Event Delegation, like so:
$("#grid").on("keyup", "input", function() {
...
});
Rather than adding a listener to each and every input, you're only adding one to #grid.
As per this great answer: What is DOM event delegation?
Another benefit to event delegation is that the total memory footprint used by event listeners goes down (since the number of event bindings go down). It may not make much of a difference to small pages that unload often (i.e. user's navigate to different pages often). But for long-lived applications it can be significant.
There are some really difficult-to-track-down situations when elements removed from the DOM still claim memory (i.e. they leak), and often this leaked memory is tied to an event binding. With event delegation you're free to destroy child elements without risk of forgetting to "unbind" their event listeners (since the listener is on the ancestor). These types of memory leaks can then be contained (if not eliminated, which is freaking hard to do sometimes. IE I'm looking at you).

Native addEventListener with selector like .on() in jQuery

Does anyone know how jQuery's .on() method can be implemented in native JS? The addEventListener method does not take a child/selector element as a way to filter, and I don't think I have the proper bubbling/capturing knowledge to completely understand what is happening in there. I did consult the source in event.js, where it appears that eventually addEventListener does get used just as it normally does, but I'm not sure I quite grok the source.
If the native method does not provide a mechanism for taking advantage of bubbling and capturing, then does the jQuery .on() function really even have any benefit, or does it just make it look that way? I was under the impression that
.on('parent', '.child', fn(){});
is more efficient than attaching an event to all children individually, but from my interpretation of the source, it's difficult to tell if jQuery is somehow managing this in a way to leads to performance improvement, or if it's just for readability.
Is there a standard methodology for implementing events on a parent that take advantage of bubbling/capture phases for it's child elements, rather than having to attach an event to each individual child?
To perform event delegation natively:
parent.addEventListener('click', function(e) {
if(e.target.classList.contains('myclass')) {
// this code will be executed only when elements with class
// 'myclass' are clicked on
}
});
The efficiency you are referring to has to do with how many event handlers you add. Imagine a table with 100 rows. It is much more efficient to attach a single event handler to the table element then 'delegate' to each row than attach 100 event handlers, 1 to each row.
The reason event delegation works is because a click event actually fires on both the child and the parent (because you're clicking over a region within the parent). The above code snippet fires on the parent's click event, but only executes when the condition returns true for the event target, thus simulating a directly attached event handler.
Bubbling/capturing is a related issue, but you only need to worry about it if the order of multiple event handlers firing matters. I recommend reading further on event order if you are interested in understanding bubbling vs capturing.
The most common benefit of event delegation is that it handles new elements that are added to the DOM after the event handler is attached. Take the above example of a table of 100 rows with click handlers. If we use direct event handler attachment (100 event handlers), then new rows that are added will need event handlers added manually. If we use delegated events, then new rows will automatically 'have' the event handler, because it's technically been added to the parent which will pick up all future events. Read What is DOM Event Delegation, as Felix Kling suggested, for more information.
Adding to the accepted answer: since often the actual event target will be nested within the element you want to bind the listener to it would be better to query the parents (Element.closest() includes the element itself). Also this works with complex CSS selectors instead of a single class only.
<button><span>button with</span><span>multiple click targets</span></button>
function addListener(el, type, callbackFn, selector) {
el.addEventListener(type, e => {
const target = e.target.closest(selector);
if (target) callbackFn.call(target, e);
}, true);
}
addListener(document, "click", e => console.log("clickediclick"), "button");
The answer by #Raine Revere, while concise, does not handle all cases. For example, if .child element contains children of its own, then click on the grandchildren will not trigger the handler. Also, jQuery sets the this execution context to the matched element.
The following snippet handles it correctly.
function on(event, elem, selector, handler) {
elem.addEventListener(event, ev => {
const target = ev.target.closest(selector);
if (target) {
handler.apply(target, arguments)
}
})
}
on('click', document.querySelector('.parent'), '.child', function () { console.log('Clicked ' + this.tagName);});
<div class="parent">
<button class="child">Click Me <i class="icon icon-something">→</i></button>
</div>

Javascript cancel POST from another piece of code using one single function

Ok, in my code I have for example, this:
$('.follow').click(function(){
var myself = $(this);
var data = {
id: this.getAttribute('data-id')
}
$.post('/users/setFriend', data, function(msg){
myself.text(msg);
myself.attr('data-status-friends', (myself.attr('data-status-friends').toLowerCase() == 'follow') ? 'following' : 'follow');
});
})
However, i put a class of 'auth' on certain elements that if the user is logged out, run this bit of JS:
$('.auth').click(function(e){
e.preventDefault();
alert('not logged in');
});
This works for the majority of elements, but with the above POST, it seems to still action the POST. How can I definitively cancel the events fired by other bits of code if .auth is clicked?
I don't think we should talk about propagation or defaultAction preventing here. The point is that you create two different series of event handlers: one attached to the .follow elements, another - to the .auth elements. Of course, if an element has two classes, clicking it will trigger both handlers automatically - and they both will be attached to this element (hence no propagation).
The most simple solution here, I think, is to remove click handler from an element when you assign .auth class to it.
Or, alternatively, you can check $(this).hasClass('auth') condition within the .follow handler function - and return false immediately if that's the case.
Rather than stopping the events, I think a better approach would be either to simply not put events into elements which aren't supposed to be doable without logging in, or track the auth state in your JS code with a variable and check that before doing actions. The latter would probably be a better approach to use if you are building a client-side MVC style application.
You could have two issues going on here with different sets of solutions.
The listeners are attached to different elements in reverse of what they should be
The listeners are being attached to the same element
Different Elements
Your handlers are out of order, swap them so that e.stopPropogation() is on the inner(child) element and the $.post() call is on the outter(parent) element.
Same Element
If the listeners are on the same element, neither e.stopPropogation() nor e.preventDefault() will do what you wish as the event listeners will still fire on the same element.
stopPropogation()
Description: Prevents the event from bubbling up the DOM tree,
preventing any parent handlers from being notified of the event.
Propagation according to the DOM Level 2 Spec will still execute all listeners on the same element but not events attached to the parent:
If the capturing EventListener wishes to prevent further processing of
the event from occurring it may call the stopProgagation method of the
Event interface. This will prevent further dispatch of the event,
although additional EventListeners registered at the same hierarchy
level will still receive the event.
preventDefault()
Description: If this method is called, the default action of the event
will not be triggered.
Default actions are are based on which element is being acted upon (W3C). For a link this would be the redirect to the href="" value, input element to focus it, etc. This is not what you desire as you are most likely not the 'default behavior'.
One option is to attach the handler that calls $.post to an element that is higher on the DOM.
$('.follow').parent().click(function(e){e.stopPropogation()})
You might have to alter your target HTML so that you have an inner(child) and outter(parent) element to attach your events to. The goal being to have the $.post handler as the outer(parent) and the inner(child) handler cancel the event.
Another option is to add a check to see if the other class is present on your element.
$('.follow').click(function(){
var myself = $(this);
if(!$(this).hasClass('.auth')){
var data = {
id: this.getAttribute('data-id')
}
$.post('/users/setFriend', data, function(msg){
myself.text(msg);
myself.attr('data-status-friends', (myself.attr('data-status- friends').toLowerCase() == 'follow') ? 'following' : 'follow');
});
}
});

jQuery programmatically trigger events

What all events can be triggered programmatically using jQuery? Also is there any important differences to be remembered when one is doing event triggering using jQuery Vs a natural way of it being triggered?
Every event can be programmatically fired, just use the callback-less version of it.
Example:
$('#button').click(function() { alert('event hanlder'); });
$('#button').click(); // generate the event
About your second question, there should be no difference between the native and jQuery event handlers.
One thing that is neat though is that jQuery binds this to the element that received the event, inside the callback (this doesn't happen in native event handlers):
$('#button').click(function() { alert(this); }); // here 'this' == document.getElementById('button');
Warning: the element referenced by this is not "jQuery augmented". If you want to traverse or modify it with jQuery goodness you'll have to do something like var $this = $(this);
You should know the differences between trigger and triggerHandler in jQuery.
trigger
trigger attempts to replicate the natural event as best as it can. The event handler for the event being triggered get's executed, but the default browser actions will not always be replicated exactly. For example $('a#link).trigger('click'); will execute the javascript function bound to the links click event handler, but will not redirect the browser to the href of the anchor, like a normal click would. EX: http://jsfiddle.net/AxFkD/
All the short forms of the trigger call behave exactly like trigger IE. click(), mouseup(), keydown(), etc
triggerHandler
triggerHandler prevents bubbling up ( EX. http://jsfiddle.net/LmqsS/ ), it avoids default browser behaviour and just executes the events callback, and it returns the return value of the event handler instead of a jQUery object for chaining.
You should also be aware that trigger affects all elements matched by a selector, but triggerHandler only affects the first one EX: http://jsfiddle.net/jvnyS/
You can trigger any event programmatically. But most of the events cannot be simulated as the natural event using programmatic triggers.
//to trigger a click event on a button
$("buttonSelector").trigger("click");
First, for obvious reasons, you cannot trigger the ready event.
That said, events raised by trigger() behave the same way as if they were triggered by the user. In particular, the event handlers are called in the same order.
The only difference I know of is that triggered events did not bubble up the DOM tree in older versions of jQuery (that behavior was fixed in version 1.3).

Categories