2019年11月3日 星期日

[JS] Something About Bubbling And Capturing

Background
During the implementation of a React application, I need to accomplish the following effect

Let's say we have an application with list of items listed, user can interact with the items, and the click event will trigger the calculation of another react component. More precisely, you can think of an app which user clicks to add products to carts, and the other component immediately checks and calculates the total price and show in menu bar. To better illustrate, I borrow the following image from google to present the idea






















When user scroll (either on smartphone or desktop), I want to make the floating menu disappear.


Problem
Following the thread https://medium.com/@pitipatdop/little-neat-trick-to-capture-click-outside-react-component-5604830beb7f, I first add ref to the components of the floating cart component, and add click event listener to the whole document DOM in componentDidMount, then for each user clicking event, use "contains" method, check whether the clicked DOM is the child of the floating cart component.

The idea seems nice, but the contains method always deduced the clicked DOM element as "false", that is not containing even when I clicked to the designated Paybox element. If this is not solved, the scroll to hide functionality will not work.


Findings and Solutions
Taking me an hour, finally the problem is caused by javascript's bubbling and capturing issues. Straightly speaking, the onclick event listener is being responded after the child onclick listener is determined.

The tricky point here is the addEventListener 's third userCapture argument. It is used to determine the order of responding the onclick when both parent and child element is equipped with onclick listener.

In my case, I have a "PayBox" DOM element which is inside the document DOM element, both of them are equipped with click event listener, and the interesting thing is I attach the document listener using


1
2
3
4
5
6
componentDidMount() {
  const {getProductList} = this.props;
  getProductList();
  // Add scroll event to whole app, when scroll, hide the order meal list
  document.addEventListener('click', this.handleDocumentScroll, false);
}

Here, I marked userCapture paramter to false, bubbling effect applies. That means Paybox DOM's onclick listener will be evaluated first and then the document element's listener comes after. The following better illustrates the alignment of elements.












And even more, I put the rendering of the Box element in the render function, which means, whenever there are changes of props within the component, it will have chance to trigger re-render, which in turn re-render the Box element.

Here is the capture of the target element, the parent element, and the results of the contain function.













The interesting thing here is that the parent of the target element is different from the one wrapped by the Box element (noted with the ***-root-290*** and ***-root-291***).

The reason behind is that, when user clicks, bubbling effects apply, js determines the event listener of the inner DOM first (i.e.: the box element), and because there are some changes of props in that onclick function, which in turn triggers the re-render of the box element, and that results in the renewal of the class name associated.

The document click event listener is then being determined afterwards, the event.target's DOM still keeps the reference of the last element (the ***-root-290*** class name's element), so the child of the outdated element is not included in the updated box element, that explains why the dropdown menu is not popping up anytime.

The solution is yet simple, just change the userCapture parameter from false to true. Capturing applies, and the document event listener now determined prior to the child's one, making the contain functions determined the desired result.

References
https://blog.othree.net/log/2007/02/06/third-argument-of-addeventlistener/
https://www.w3schools.com/jquery/tryit.asp?filename=tryjquery_event_stoppropagation
https://www.w3schools.com/jquery/event_stoppropagation.asp
https://stackoverflow.com/questions/34522931/example-for-bubbling-and-capturing-in-react-js

沒有留言:

張貼留言