Events bubbling and events capturing

    intro
    Imagine that there are two blocks on the page, and one is nested in the other, as shown in the figure. In the page layout, it looks like this:

    Now imagine that the onClickOuter event is attached to the #block_outer block, and the onClickInner event, respectively, to the #block_inner block. And answer the question, how to make sure that when you click on the #block_inner block, the onClickOuter event is not raised? And will it be called at all? And if so, in what order will 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 ran around the planet, and antique IT workers used stone-carved smartphones, there was a browser war in full swing, the opinions of MS and Netscape on the behavior of events on web pages were divided (fortunately, I did not have to face due to age with that back in those days). When nesting elements on a page (as in the example above), MS proposed an event bubbling model, that is, the order of events should rise (“gurgle”) up the structure of the DOM tree. Netscape proposed the opposite model, called event capturing, in which event processing should go down the elements (“capture” them) down the DOM tree.

    compare


    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 you click, the “descent” phase will first occur, and events associated with the useCapture = true flag will be triggered, then the “ascent” phase will be triggered, and other events will be triggered in the ascending order along the DOM tree. By default, events always subscribe to the bubbling phase, that is, with this method of subscribing to the useCapture = false event:
       elementNode.onclick = someMethod;
    

    general


    How do browsers work today?


    The addEventListener method does not exist in IE below version 9. For this, attachEvent is used, which does not have a third argument, that is, events will always “gurgle” in IE, and much of what is described below for this browser makes no sense. All other browsers implement addEventListener according to the specification from 2000 without deviations.

    To summarize the above, let's write a short test that will show how you can control the priority of events:
    • HTML structure:
    • Scenario
         // using jQuery;
         jQuery.fn.addEvent = function(type, listener, useCapture) {
            var self = this.get(0);
            if (self.addEventListener) {
               self.addEventListener(type, listener, useCapture);
            } else if (self.attachEvent) {
               self.attachEvent('on' + type, listener);
            }
         }   

         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 (once signed) to the bubbling phase. The capturing phase is called first, with a descent down the tree, the first one is # level1, then # level2, then the line of the # level3 element itself is suitable, and then, when raising along the DOM, the line of the # level1 element is again suitable. Internet Explorer will show us:
       3 2 1 1
    


    How to stop the next event?


    Any of the bound events may stop traversing the following elements:
    function someMethod(e) {
       if (!e) {
          window.event.cancelBubble = true;
       } else if (e.stopPropagation) {
          e.stopPropagation();
       }
    }

    The W3C model describes the stopPropagation method of an event object, but Microsoft is different here too, so for IE you need to refer to the event.cancelBubble field.

    Event target


    As you know, it is possible to determine the page element that triggered the event. The event object has a target field that refers to the initiating element. This is easier to show with an example:
    • HTML structure:
    • Scenario
         $('#level1').addEvent('click', function(e) {
            // MSIE "features"
            var target = e.target ? e.target : e.srcElement;
            if ( $(target).is('#level3') ) {
               $('#test').append('#level3 clicked');
            }
         }, false);
      
    • Demo

    Let me explain what’s happening 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 of nothing? This is how jQuery.live works : if an element does not exist on the page, but it appears in the future, you can still bind 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 we can attach events to it that may or may not trigger certain functions depending on event.target.

    And here the question arises: if jQuery.live binds events to the bubbling phase at the document level, then previous events can stop the call chain and disrupt the call of the last event? Yes, this is so, if one of the events that are executed before this calls event.stopPropagation (), the call chain will be interrupted. Here is an example:
    • HTML structure:
    • 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 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 implementation of something, and sometimes it can be a difficult (hellishly difficult) perceptible mistake.
    It is also important to note that in the example above, the variable “e” is a jQuery.Event instance. For IE, the event does not have a stopPropagation method, and you must set the flag event.cancelBubble = true to stop bubbling in IE. But jQuery elegantly solves this problem, replacing this method with your own.

    How do various JS libraries work with events?


    At this point, I make a reservation that there are a lot of libraries that can work with events, but we will only consider jQuery, MooTools, Dojo and ExtJS, since the article and the author’s knowledge, unfortunately, are not rubber. Therefore, fans to discuss languages ​​and frameworks, I ask you to pass by.
    • jQuery
      can handle events through bind , which always binds events to the bubbling phase, but has a third parameter, “preventBubble,” which stops the chain of events for this handler. There are also wrappers for it such as click , change , etc., and ways to delegate events: delegate , live .
    • MooTools
      can handle events through addEvent , which can handle custom events . AddEvent also does not allow you to specify the processing phase. You can work with event delegation using pseude: relay .
    • Dojo
      uses connect to bind events (which can also stop the event chain if the dontFix parameter is specified) or behavior . For delegation, you can use the delegate method .
    • ExtJS
      provides, in my opinion, the simplest interface for working with events. It is possible for the on method to pass parameters in the form of an object, such as, for example, delay, stopPropagation, delegate, or its own arguments.

    As we can see, all these libraries compromise with cross-browser compatibility, and everywhere use events bubbling, while providing similar functionality for working with events. However, understanding how it works from the inside will never hurt :)

    Materials


    Also popular now: