From the dawn of history browsers have supported event delegation.
We will use event delegation to run all our web application just with one event !

Javascript Event Delegation

If you click on an element, the event will bubble all the way up to the document in search of event handlers to execute.

Event delegation allows you to avoid adding event listeners to specific nodes, instead, the event listener is added to one parent.

That event listener analyzes bubbled events to find a match on child elements.

Let's asume we have a normal list, and see an example of how events are used:

<ul id="parentList">
  <li><img src='image-1.jpg'></li>
  <li><img src='image-2.jpg'></li>
  <li><img src='image-3.jpg'></li>
  ...
  <li><img src='image-98.jpg'></li>
  <li><img src='image-99.jpg'></li>
  <li><img src='image-100.jpg'></li>
</ul>

Let's also asume that something needs to happen when each child element is clicked. You could add a separate event listener to each individual LI element, but what if LI elements are frequently added and removed from the list or the whole element is updated via asyncronous calls?

Adding and removing event listeners would be complicated, especially if addition and removal code is happening in multiple places within your app.


A good solution

A good solution is to add an event listener to the parent UL element, But if you add the event listener to the parent, how will you know which element was clicked?

Simple: when the event bubbles up to the UL element, you check the event object's target property to gain a reference to the actual clicked node.

// Add a parent click listener
document.getElementById('parentList').addEventListener('click',function(e) {
  if(e.target && e.target.nodeName == "LI") {
    alert(e.target.innerHTML+" was clicked!");
  }
});

This solution is good, but requires very specific script detection for each block of code.
For example, if now, our list require secondary actions, and our HTML layout ends like this:

<ul id="parentList">
  <li>
    <img src='image-1.jpg'><span>Image 1</span>
    <ul class='secondaryActions'>
      <li>edit title</li>
      <li>delete image</li>
    </ul>
  </li>
  <li>
    <img src='image-2.jpg'><span>Image 2</span>
    <ul class='secondaryActions'>
      <li>edit title</li>
      <li>delete image</li>
    </ul>
  </li>
  ...
  <li>
    <img src='image-100.jpg'><span>Image 100</span>
    <ul class='secondaryActions'>
      <li>edit title</li>
      <li>delete image</li>
    </ul>
  </li>
</ul>

our script will get more complicated, in order to detect wich element is being clicked, and wich action should be run


The Perfect solution

Simple solutions are always the best, and there is nothing more simple, that tell the script what he has to do.

For each element that requires a click event, lets add a data-attribute with the information we require, "controller","function" and "parameters".

<ul id='parentList'>
  <li data-action='app,show,1'>
    <img src='image-1.jpg'><span>Image 1</span>
    <ul class='secondaryActions'>
      <li data-action='app,edit,1'>edit title</li>
      <li data-action='app,delete,1'>delete image</li>
    </ul>
  </li>
  <li data-action='app,show,2'>
    <img src='image-2.jpg'><span>Image 2</span>
    <ul class='secondaryActions'>
      <li data-action='app,edit,2'>edit title</li>
      <li data-action='app,delete,2'>delete image</li>
    </ul>
  </li>
  ...
  <li data-action='app,show,2'>
    <img src='image-2.jpg'><span>Image 2</span>
    <ul class='secondaryActions'>
      <li data-action='app,edit,2'>edit title</li>
      <li data-action='app,delete,2'>delete image</li>
    </ul>
  </li>
</ul>

Now, in order to detect and proccess our events, we only use one function in our app, that redirects all calls to the proper object and function.

var eventManager = function(attr,e){
  
  // get the event target
  var target = e.target;
  
  // search for a node with the data-action attribute
  while(target){

    if(!target.getAttribute) break;
    
    var dataAction = target.getAttribute(attr);

    if(dataAction) {
      
      var
      elements = dataAction.split(','),
      controller = elements[0]||null,
      funct = elements[1]||null,
      params = elements[2]||null
      obj = window,
      path = controller.split('.');

      // find controller 
      for(var m=0;m<path.length;m++) {
        obj = obj[path[m]];
        if (!obj) break;
      }
      
      // trigger function
      if (obj&&obj[funct]) obj[funct](params);

      // stop searching
      break;

    }
    target = target.parentNode;
  }
};

// our event 
document.addEventListener('click',eventManager.bind(this,'data-action'));

On resume

Now, on click event, our system will try to find and object indicated in 'data-action', and call the specific function if exists.

We can update our innerHTML, remode nodes, append nodes or manipulate the HTML in any way we want, the code will keep working and our problems with memory leaks associated to events handling will dissapear.

In less than 20 lines of code, and just with one event, we redirect all our actions to the proper objects


See it working

You can check the online demo here.