Subtle Differences Using jQuery Event Handlers
At first glance, these event handlers look almost the same. You probably won’t even find a functional difference in most cases. But trust me, they are hours apart in functionality.
$("#order-details tr").on('click', function(evt) {/*do something*/});
VS
$("#order-details").on('click', ‘tr’, function(evt) {/*do something*/});
Let me tell you my story.
I was working on a large project with a lot of data tables. We used a third-party library (https://datatables.net) to provide most of the functionality we needed: select, order, and process data. One day I ran into an issue where I needed to imitate Shift+Click on items in the table so ranges could be selected on devices that do not have keyboards (e.g., tablets).
Imitating Shift+Click was relatively simple. I added a click event to the table rows that would emit another event to set the metaKey property.
I captured the actual click event like this:
$("#order-details").on('click', ‘tr’, function(evt) {/*do something*/});
Well, I ended up having two events fire each time. My event would fire first. Next, the event built into datatables.net would fire and overwrite my selection. I tried unsuccessfully to stop this from happening by utilizing these functions:
- evt.preventDefault()
- evt.stopPropagation()
- evt.stopImmediatePropagation()
- return false
I never could stop the event, and I could not find a reason why. My co-worker, Andrew, pointed me to the Framework Listeners option in the Chrome Developer Tools.
By using this option, I determined that my click event wasn’t attached to the <tr> like I had thought. It was attached on the #order-details element and was only firing if the element that was clicked was a <tr> inside #order-details. The DataTables library attaches their click eventlisteners on the #order-details element. Their event is nested deeper in the document structure so I was unable to cancel the event before it reached their listener.
I found that the right way to approach this is to attach the event directly to the <tr>, like this:
$("#order-details tr").on('click', function(evt) {/*do something*/});
The <tr> element is nested deeper in the document structure than the DataTables element. That means I am able to prevent the event from bubbling by using evt.preventDefault and evt.stopPropagation.
So what’s the moral of the story? Always make sure you know where your click event is attached. As with most code, there can be very subtle differences that produce not-so-subtle outcomes.