Javascript Event Listeners

Most websites today are stuck in the mud.

They sit there, present content, and fail to solicit any interaction from the user.  This site is no exception.  I write content, you read it.  End of story.

But some websites are far more interactive.  They present rich, immersive interfaces and simulate the kind of interactive behavior typical of desktop applications.

I build this second category of websites for a living.  And at times it can be tricky to wire the right kind of functionality to the right element.

Let’s walk step-by-step through an example of adding users from a dynamic database to an invite list as an example.

Prerequisites

If you haven’t already, you should take a look at jQuery.  It’s an incredible powerful Javascript framework that abstracts much of the functionality we need into an easy-to-use API.

It’s also compatible with all major browsers and eliminates the need to remember the minute differences between the ways browsers implement specific features.

The Application

Our application will have two elements – the list of potential event attendees and the list of invited attendees.

This screenshot is taken from an actual application, but note it has two columns – the left provides a list of users in the database, the right a list of people we want to invite.  User creation and submitting the user list are beyond the scope of this tutorial.

Get a List of Users

First, we need to fetch a list of users from the server.  We’ll fetch the list as XML and map the returned data on to an internal Javascript element.

The server structures data like this:

1
2
3
4
5
6
7
8
9
>
  >
    >Eric Mann>
    >>
    >
      >Administrator>
    >
  >
>

All we care about now are the and elements.  In the real application, we pass this data into an autocomplete system to power the drop down suggestion list.  Don’t worry about that part for now, but here’s the code that gets our user list:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$(document).ready(function() {
  var users = [];

  $.ajax({
    url:      '/AJAX/Users',
    dataType: 'xml',
    success:  function(xmlResponse) {
      var data = $('user', xmlResponse).map(function() {
        return {
          username: $('username', this).text(),
          email:    $('email', this).text()
        };
      });

      users = data.get();
    }
  });
});

Now our users object contains an array of JSON objects describing our user base. We can search this array for specific user names, add them to our list of invited attendees, and eventually send actual invitations to their mapped email addresses.

Create a List of Attendees

Our attendee list will be a basic HTML list displaying user names and holding their email addresses in data elements:

1
2
3
4
5
6
<ul id="userList">
  <li class="user-item">
    <span class="user" data-email="">Eric Mann</span>
    <span class="del-user">[ x ]</span>
  </li>
</ul>

Whenever a new user is added from our master list, we just add a new element to this HTML list.

1
2
3
4
5
var newUser = $('
  • ');
    newUser.append($('+ user.email + '">' + user.username + ''));
    newUser.append($('[ x ]'));

    $('ul#userList').append(newUser);
  • Deleting Users

    This is the crux of the issue. We need to wire some code to listen for clicks on the element so that we know when to delete a user and which user to delete.

    A first pass would be to attach to the click event:

    1
    2
    3
    4
    $('.del-user').click(function(e) {
      // Remove the parent
  • element from the list

  •   $(this).parent().remove();
    });

    There’s one very large issue with this code – it won’t work.

    This event listener is attached once when the page is loaded. Every subsequent user added to the list will be lacking this event listener. A very basic fix would be to wrap this code in a function and call it whenever we add new listeners, thus re-adding our click handlers each time.

    That is a poor approach. It’s bulky, inefficient, and sloppy.

    It was also the way I did things for years.

    The better approach is to use event delegation. Instead of binding to the click event of the delete element, we bind to the click event of the parent

      and listen for any events that bubble up to it.

      The majority of browser events bubble, or propagate, from the deepest, innermost element (the event target) in the document where they occur all the way up to the body and the document element…

      When a selector is provided, the event handler is referred to as delegated. The handler is not called when the event occurs directly on the bound element, but only for descendants (inner elements) that match the selector. jQuery bubbles the event from the event target up to the element where the handler is attached (i.e., innermost to outermost element) and runs the handler for any elements along that path matching the selector. (jQuery documentation)

      Our code changes somewhat to this:

      1
      2
      3
      4
      $('ul#userList').on('click', 'li.user-item .del-user', function(e) {
        // Remove the parent
    • element from the list

    •   $(this).parent().remove();
      });

      We bind the event listener once, and it works for every user added to the list, not just those present when the page first loads.

      Why This is Important

      Every time you touch the underlying DOM of a webpage takes time and resources from the browser. Every time you add or remove an event listener, you poke a little at the DOM.

      Imagine this example were a bit different – instead of adding interactivity to a simple list element you add it to a table. Now imagine the table is very large.

      If you’re adding a new event listener to every cell in the table, you’re touching the DOM a lot! With some browsers, this kind of manipulation is very expensive. Your site and application will be slow to load, slow to adapt, and slow to use.

      By binding your event listener to the table instead and delegating your event handling, you only touch the DOM once and let the underlying Javascript framework do the rest.

      Where else might your event listeners benefit from delegation versus explicit binding?

      About Eric

      Eric Mann is a writer, web developer, and outdoorsman living in the Pacific Northwest. If he's not working with new technologies in web development, you can probably find him out running, climbing, or hiking with his dog.

      Leave a Reply