Use event bubbling and event delegation to optimize code - add event problems to child elements

Keywords: Javascript Front-end Interview

Pure js calendar component learning notes

The following records the problems encountered in the process of completing the calendar component in pure js

Problems encountered

1. Drop down menu implementation
  • The display of the drop-down menu and the style that will appear after clicking the option box are written in advance

    • For example, the drop-down menu already exists, but it is hidden. When I click the option box, I add a class name active to it, and then add display:block to display it through this class name
    • Similarly, the style after the option box is clicked and the style after each option in the drop-down menu is selected is to add a class name to them through js to obtain the style written in advance.
    • As usual, I will add styles to each dynamically changing element through js, which looks messy. It is better to write the implementation of element styles in css
  • Now describe the problem

    <div class="select vacationSelect">
        <!-- Content area -->
        <div class="selected">
            <span class="content">Holiday arrangements</span>
            <i class="icon"></i>
        </div>
        <!-- Drop down box -->
        <div class="selectBox">
            <ul>
                <li>1970 year</li>
                <li>1970 year</li>
                <li>1970 year</li>
                <li>1970 year</li>
                <li>1970 year</li>
                <li>1970 year</li>
                <li>1970 year</li>
                <li>1970 year</li>
            </ul>
        </div>
    </div>
    
    • As shown in the code, I added the click event to the parent element named select. After clicking, the parent element will have another classname - > active. Then when the drop-down menu is displayed, I want to click li. li will be selected, that is, the text color of li will change.

    • Similarly, I also change the style of li by adding. active to li

    • But I found that the active I added to li was added to the parent element select

      About adding class names and bubbling events
      Problem description
      • Here is the error code I wrote
      for (let i = 0; i < selects.length; i++) {
          selects[i].addEventListener('click',function(event){
              selects[i].className =  selects[i].className + ' active';
          })
      }
      
      for (let i = 0; i < lis.length; i++) {
          lis[i].addEventListener('click',function(event){
              for (let i = 0; i < lis.length; i++) {
                  lis[i].className = ""
              }
              this.className = "active";
          })
      }
      
      • Because I didn't know much about the method of using classList.add() to add class names

      • Therefore, this stupid method is used. Because the select already has the class name to be added, the class name is added by splicing (a big pit)

      • Later, I found that every time I click li, an active will be added to the parent element. Later, I changed the way to add the class name to

        for (let i = 0; i < selects.length; i++) {
            selects[i].addEventListener('click',function(event){
                //selects[i].className =  selects[i].className + ' active';
                selects[i].classList.add('active');
            })
        }
        
      • Then the above problem is solved, including even if I use this.className = "active"; To add a class name to select, he didn't have the above problem, and I was confused again

      • Until I learned about the bubble of events

        Event Bubbling
        • There are three nested boxes as shown in the figure

        • There are several situations:

          • Click events have been added to all three boxes
            • Clicking any child element will trigger the click event of itself and the parent element at the same time
          • Only the parent element has a click event added:
            • Clicking a child element will also trigger the click event of the parent element
          • Only child elements have click events added:
            • Click your own response, but click the parent element without response
        • Summary: when you want to perform operations on child elements, you will check whether the child elements have corresponding event handling functions. If not, you will find your parent element layer by layer to see if the parent element has any. If so, you will perform the operations of the parent element. If you have both yourself and the parent element, execute them all

      • Back to the question

        • First, when using add() to add a class name, if the class name already exists, it will not be added; this.className = "active"; Is to empty the original class name and add it. So even if I don't prevent the event from bubbling when I click li, it won't always add active when the parent element select is triggered. At this time, the event bubble still exists, but it is invisible.

        • Earlier, when I added printing for the click events of the parent element and the child element, when I click the child element, I also printed the click parent element in the parent element. I thought that because li is also part of the parent element select, clicking the click of the parent element of li will also trigger. In fact, this is also because of the bubble of events.

          for (let i = 0; i < select.length; i++) {
              select[i].addEventListener('click',function(){
                  this.classList.add('active');
                  console.log("Parent element clicked");
              });
          
          }
          
          for (let i = 0; i < lis.length; i++) {
              lis[i].addEventListener('click',function(){
                  for (let i = 0; i < lis.length; i++) {
                      lis[i].className = ""
                  }
                  this.className = "active";
                  console.log("Child element clicked");
              })
          }
          
        • Because the event bubbles, add the original selections [i]. Classname = selections [i]. Classname + 'active'; There has always been a selection [i]. Classname that will change, so every time you click li, the parent element will continue to add active

        • In general, changing the method of adding class names directly or adding event.stopPropagation() to child elements to prevent event bubbling can solve my problem.

        • However, according to my requirements, it is not appropriate for me to add click events to the parent element and add click events to each child element through circular traversal. Event delegation can be used at this time

        Event delegation

        What is event delegation

        • An event is originally bound to an element, but it is bound to the parent (or ancestor) element of the element. The execution effect is triggered by using the event bubble principle

        Advantages of event delegation

        • Improve web page performance.
        • Events added through event delegates are still valid for later generated elements.

        Follow the above question

        • Let's first understand that when an event bubble occurs, there will be some information during the transmission of the event

          attributedescribe
          currentTargetReference of the current capture node
          pageXAbscissa of the current document at the event occurrence point
          pageYOrdinate of the current document at the event occurrence point
          targetThe node reference that generated the event
          typeCurrent event type: such as click
          whichFor keyboard and mouse events, this property determines which key or button you pressed
        • Use event bubbling and event delegation to optimize the above code

          • Idea:
            1. Add a click event to the parent element select
            2. In the click event of the parent element, judge whether the currently clicked element is what we want through event.target.tagName
            3. If yes, do what you want instead of going back
          • Final code
window.onload = function(){
    // Get element
    const selects = document.querySelectorAll('.select');

    // When the mouse clicks the arrow in the drop-down box, the arrow changes to an upward arrow
    // Click the drop-down box select to add a ClassName
    selects.forEach((item,index)=>{
        var cl = item.classList;
        item.addEventListener('click',function(e){
            // 1. Judge whether you have class name active
            // Click the drop-down box to expand and click the drop-down box again to retract
            if(cl.contains('active')){
                // 1.1 remove if any
                cl.remove('active');
            }else{
                // 1.2 if not, check whether there are other selectors. If there are other selectors, clear their active
                // The implementation has only one drop-down box that is expanded.
                removeClass();
                // Add your own class name active
                cl.add('active');
            }

            // Judge whether li is clicked by e.target.tagName
            if(e.target.tagName != 'LI') return;
            // If yes, add the class name active to li and clear the active of other unselected li
            // Because the obtained lis is a pseudo array, use the expansion operator to convert it into an array
            const lis = [...item.querySelectorAll('ul li')];
            // open is the returned class name containing the active element
            var open = lis.find(li => li.classList.contains('active'));
            open && open.classList.remove('active');
            e.target.classList.add('active');
        })
    })

    function removeClass(){
        const selects = [...document.querySelectorAll('.select')];
        var open = selects.find(select => select.classList.contains('active'));
        open && open.classList.remove('active');
    }
}
  • Effect achieved

supplement

About getting elements
  • const selects = document.querySelectorAll('.select');
    const lis = selects.querySelectorAll('ul li');
    
    • It is wrong to select li in the above way. The selections we get is a pseudo array containing multiple elements

    • Select li from a pseudo array. Who knows which one you want to choose

    • The right thing to do is

      • When these two elements are in a loop
      const selects = document.querySelectorAll('.select');
      
      selects.forEach((item,index)=>{
          const lis = item.querySelectorAll('ul li');
      })
      
    • It is special in this environment. Because we have many same selections and they all have ul li, it is better to obtain the li under different selections by looping through the selection

Posted by cretam on Wed, 27 Oct 2021 01:09:36 -0700