
Imagine that there are two blocks on a page, and one is nested into another, as shown in the figure. In the page layout, it looks like this:
<div id="block_outer"> <div id="block_inner"></div> </div>
Now imagine that the onClickOuter event is tied to the #block_outer block, and the onClickInner event, respectively, to the #block_inner block. And answer the question how to make it so that when you click on the #block_inner block, the onClickOuter event is not called? And will it be caused at all? And if so, in what order will the events be triggered? And do you know how the jQuery.live method or similar works in other libraries (events delegation in ExtJS, for example)?
A bit of history
At the dawn of civilization, when dinosaurs were running around the planet, and ancient IT people used rock-hewn smartphones, the browser warfare was in full swing, MS and Netscape shared their opinion about the behavior of events on web pages (fortunately, due to my age with this in those days) When nesting elements on the page (as in the example above), MS proposed the events bubbling model, that is, the order of the events should rise (“gurgle”) up the DOM tree structure. Netscape proposed an opposite model, called event capturing, in which event processing should descend elements (“capture” them) down the DOM tree.
')
W3C tried to combine both options - the
standard allows the programmer to set the behavior of events on the page using the third parameter of the method
addEventListener(type, listener, useCapture)
That is, when clicking, the “descent” phase will first occur, and events associated with the flag useCapture = true will be triggered, then the “ascend” phase will be triggered, and the remaining events will be triggered in ascending order along the DOM tree. By default, events always subscribe to the bubbling phase, that is, with this method of subscribing to the event, useCapture = false:
elementNode.onclick = someMethod;
How do browsers work with it today?
The addEventListener method does not exist in IE below version 9. To do this, use attachEvent, which does not have a third argument, that is, events will always “gurgle” in IE, and much of the information described below for this browser does not make any sense. All other browsers implement addEventListener according to the specification from 2000 without deviations.
Summarizing the above, let's write a small test that will show how you can control the priority of the execution of events:
- HTML structure:
<div id="level1"> <div id="level2"> <div id="level3"> </div> </div> </div>
- Scenario
var EventsFactory = function(logBox){ this.createEvent = function(text){ return function(e){ logBox.append(text + ' '); } } } var factory = new EventsFactory( $('#test') ); $('#level1').addEvent('click', factory.createEvent(1), true); $('#level1').addEvent('click', factory.createEvent(1), false); $('#level2').addEvent('click', factory.createEvent(2), true); $('#level3').addEvent('click', factory.createEvent(3), false);
- Demo
When you click on the block # level3, the numbers will be displayed in the following order:
1 2 3 1
That is, the blocks # level1 and # level2 are subscribed to the capturing phase, and # level3 and # level1 (again signed) to the bubbling phase. The first is the capturing phase descending down through the tree, the first is # level1, then # level2, then the queue of the element itself # level3 comes up, and then, when it is raised via DOM, the queue of the element # level1 again comes up. Internet Explorer will show us:
3 2 1 1
How to stop the execution of the next event?
Any of the attached events can stop traversing the following items:
function someMethod(e) { if (!e) { window.event.cancelBubble = true; } else if (e.stopPropagation) { e.stopPropagation(); } }
The W3C model describes the stopPropagation method for the event object, but Microsoft is different here, so IE needs to refer to the event.cancelBubble field.
Event target
As you know, it is possible to determine the element of the page that initiated the event. The event object has a target field that refers to the initiator element. It is easier to show with an example:
- HTML structure:
<div id="level1"> <div id="level2"> <div id="level3"> </div> </div> </div>
- Scenario
$('#level1').addEvent('click', function(e) {
- Demo
Let me explain what happens here - with any click inside # level1 we check the event target, and if the initiator is the internal block # level3, then we execute some code. Does this implementation remind you anything? This is
how jQuery.live works this
way : if an element does not exist on the page, but it appears in the future, then you can still attach an event to it. During the bubbling phase, any event reaches the document level, which is the common parent for all elements on the page, and it is to it that we can bind events that may or may not trigger the execution of certain functions depending on the event.target.
And then the question arises: if jQuery.live binds events to the bubbling phase at the document level, then previous events can terminate the call chain and break the last event call? Yes, this is true, if one of the events running before this causes event.stopPropagation (), then the call chain will be interrupted. Here is an example:
- HTML structure:
<div id="level1"> <div id="level2"> <div id="level3"> </div> </div> </div>
- Scenario
$('#level1').live('click', function(e){ $('#test').append('#level1 live triggered'); }); $('#level2').bind('click', function(e){ $('#test').append('this will break live event'); if (e.stopPropagation) { e.stopPropagation(); } });
- Demo
When you click on the area # level3, this will break live event will be displayed, that is, the live event will not be executed. Please note that such a hack is possible, it can be a beautiful realization of something, and sometimes it can be difficult (hellishly difficult) perceptible error.
It is also important to note that in the example above, the “e” variable is the jQuery.Event instance. For IE, the event does not have a stopPropagation method, and you need to set the event.cancelBubble = true flag to stop bubbling in IE. But jQuery elegantly solves this problem by replacing this method with its own.
How do different JS libraries work with events?
At this point I’ll make a reservation that there are a lot of libraries that can work with events, but we’ll consider only jQuery, MooTools, Dojo and ExtJS, since the article and the author’s knowledge are, unfortunately, not rubber. Therefore, fans to discuss languages ​​and frameworks will ask to pass by.
- jQuery
can work with events via bind , which always binds events to the bubbling phase, but has the third parameter “preventBubble”, which stops the chain of events for a given handler. There are also wrappers for it like click , change , etc., and ways to delegate events: delegate , live . - MooTools
able to work with events through addEvent , which can handle custom events . AddEvent also does not allow to specify a processing phase. You can work with event delegation using pseude: relay . - Dojo
uses connect (which can also stop the chain of events if the “dontFix” parameter is specified) for binding events or behavior . For delegation, you can use the delegate method. - Extjs
provides, in my opinion, the easiest interface for working with events. For the on method, it is possible to pass parameters as an object, such as, for example, delay, stopPropagation, delegate, or your own arguments.
As we see, all these libraries compromise with cross-browser compatibility, and use bubbling events everywhere, while providing similar functionality for working with events. However, understanding how it works from the inside never hurts :)
Materials