5

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.

cornergraf
  • 562
  • 8
  • 22
  • 1
    Binding events on current or future elements using the `.on()` Method should not cost you in performance, cause there's no event until your element is the initiator of such event. What is sluggish? the toggling? – Roko C. Buljan Mar 12 '15 at 07:45
  • Try using css selectors for better performance. Also when you are using jquery then you should know `jsperf` to check which selectors are better to use. – Jai Mar 12 '15 at 07:46
  • @Jai *"Try using css selectors for better performance"* Can you explain this? – Roko C. Buljan Mar 12 '15 at 07:47
  • 1
    Consider paginating those "hundreds of results". And there is no magical recipe for improving performance. There are lots of factors involved. – Ram Mar 12 '15 at 07:49
  • @RokoC.Buljan As OP mentioned about jquery, so it might be possible OP might have used some custom selectors which jquery provides like `:last, [id^="someid"]` etc. which i guess might be there. – Jai Mar 12 '15 at 07:50
  • i think the best option is angularjs, for many tables you can work dynamically with angularjs, and for mobils with html5 support it work very fast, and the best is angularjs integrate javascript html5 and css. – marjes Mar 12 '15 at 07:54
  • @RokoC.Buljan: My star rating widget gives a preview of the rating when the mouse is over the rating widget. So if the mouse is on the first star, the first star is highlighted, on the second star the first two stars are highlighted etc. My tablet (Samsung Note 10.1) has a stylus that lets me hover over elements, but the above mentioned highlighting takes a long time to respond there. On my PC it works just fine. – cornergraf Mar 12 '15 at 08:11
  • @Vohuman: pagination is probably not an option in this case, as the entire point of the site is to build the result set with repeated searches, giving the user a clear overview of the current state. Pagination would make this harder, although I might be able to paginate some parts of the site. – cornergraf Mar 12 '15 at 08:15

1 Answers1

13

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>
Aurelio
  • 24,702
  • 9
  • 60
  • 63