I am currently building a site that allows searching for elements, with results added to a big table (think hundreds of results). Each element in the result table can be manipulated in various ways (custom javascript 0-5 star rating, toggling a fold-out panel for additional options etc.).
If I use the site on my android tablet, performance of the javascript part is very sluggish, so I am wondering how I can improve performance.
One option I have considered is to not bind any event listeners on the result rows except for a single mouse-enter event, and then binding the actual event listeners only when the mouse is over a given element.
Any other ideas to improve performance would be greatly appreciated.
Most of my javascript code is jquery based, so if you have any jquery specific optimizations I would appreciate that also.
You might look into javaScript event delegation. There are many answers on SO (an example here) and many good articles ( here or here ).
Basically the idea you had is actually a good solution. So, instead of binding - let's say - one hundred rows with their own event handlers, you bind only their common parent which will fire an event when any of its child will receive a mouse input.
Roughly speaking instead of this:
$('tr').on("click", function() {});
You will do this:
$('table').on('click', 'tr', function() {});
This is obviously a very simplified example, but it should be enough to build a good pattern upon.
As a side note, it's a very interesting thing (well, at least for me...) to inspect the event that is fired doing something like:
$('table').on('click', 'tr', function(evt) {
console.log(evt);
});
And see how much information an event carries, and how much useful information you get out of the box with a simple click or mouse enter.
VanillaJs
Of course the same result can be achieved without any library.
A simple implementation using Vanilla JS can be taken from David Walsh's article linked at the beginning of the answer, it goes roughly like this:
// Get the element, add a click listener...
document.getElementById("#myTable").addEventListener("click", function(e) {
// e.target is the clicked element.
// Check if it was a <tr>...
if(e.target && e.target.nodeName == "TR") {
// Element has been found, do something with it
}
});
If you try this code though chances are that the actual target.element
is the <td>
, and not the <tr>
we want.
This means we have to be a bit more clever and walk the DOM upwards to see if the clicked element (<td>
) is contained in the one we really want (<tr>
).
A rough implementation to get you started would look like this:
function walk(startEl, stopEl, targetNodeName) {
if (!startEl || startEl === stopEl || startEl.nodeName === 'BODY') {
return false;
}
if (startEl.nodeName === targetNodeName) {
// Found it, return the element
return startEl;
}
// Keep on looking...
return walk(startEl.parentNode, stopEl, targetNodeName);
}
const container = document.getElementById('myTable');
container.addEventListener('click', (e) => {
const clickedElm = walk(e.target, container, 'TR');
console.log(clickedElm);
if (clickedElm) {
clickedElm.classList.add('clicked');
}
})
See this fiddle or the snippet below:
function walk(startEl, stopEl, targetNodeName) {
if (!startEl || startEl === stopEl || startEl.nodeName === 'BODY') {
return false;
}
if (startEl.nodeName === targetNodeName) {
return startEl;
}
return walk(startEl.parentNode, stopEl, targetNodeName);
}
const elem = document.getElementById('myTable');
elem.addEventListener('click', (e) => {
const clickedElm = walk(e.target, elem, 'TR');
console.log(clickedElm);
if (clickedElm) {
clickedElm.classList.add('clicked');
}
})
table {
width: 100%;
}
tr {
background: #ddd;
}
.clicked {
background: teal;
}
<table id="myTable">
<tr>
<td>one</td>
</tr>
<tr>
<td>two</td>
</tr>
<tr>
<td>three</td>
</tr>
<tr>
<td>four</td>
</tr>
<tr>
<td>five</td>
</tr>
<tr>
<td>six</td>
</tr>
</table>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With