Skip to content

Instantly share code, notes, and snippets.

@AloofBuddha
Last active February 3, 2017 17:18
Show Gist options
  • Save AloofBuddha/ff6439f83f5c6073fc83a8b1b5bc57d6 to your computer and use it in GitHub Desktop.
Save AloofBuddha/ff6439f83f5c6073fc83a8b1b5bc57d6 to your computer and use it in GitHub Desktop.
Event Delegation Examples

Event Delegation

Here's an example of event delegation and when you would use it.

Here is an interactive jsbin of with-delegation.js https://jsbin.com/pufide/8/edit?html,js,console,output JSBin is in general a great resource for trying out front-end code without having to do a lot of setup!


All files share the same index.html

incorrect-approach.js implements a list which 'selects' (highlights in red) on hover. However, it only works for the original list of items, not for newly added items through our form input, as we aren't explicitly setting up event handlers there.

no-delegation.js is identical to incorrect-approach.js except that now all items (the original list and those added from the form) have the correct event handler functionality. We accomplish this by creating a helper function to create a 'movieItem' that adds the event handlers to each item. We then call it in two places, when making the original list, and when adding fromt he form.

delegation.js is like no-delegation.js but (you guessed it!) we implement our logic with event-delegation. Instead of having a special movieItem function where we add the handlers to each movieItem, instead we register the event handlers on the moviesContainer, but delegate to the individual item. This way, both our original list elements and newly added ones 'share' the same handlers without needing to think about it when adding new elements to the list.

var movies = ['Battlefield Earth', 'La La Land'];
// create jQuery objects for elements we care about
var $moviesContainer = $('#movies-container');
var $addMovieButton = $('#add-movie-button');
var $newMovieInput = $('#new-movie-input');
// map each existing movie to a jQuery object
var $movies = movies.map(function (movie) {
var $movieItem = $('<li>' + movie + '</li>');
// and explicitly add event handlers to each item
$movieItem.on('mouseenter', function () {
$movieItem.css('background-color', 'red');
});
$movieItem.on('mouseleave', function () {
$movieItem.css('background-color', '');
});
return $movieItem;
});
// then add to them to the container
// (yes - you can append a list of jQuery objects like this!)
$moviesContainer.append($movies);
// set up click handler for adding a new movie
$addMovieButton.on('click', function () {
// grab the input value, trim
var newMovie = $newMovieInput.val().trim();
// if non-empty, add to the moviesContainer
if (newMovie) {
$moviesContainer.append('<li>' + newMovie + '</li>');
}
});
// note how our event handlers were only registered on the original list items
// new items therefore don't have this property as we haven't explicitly set it
<!DOCTYPE html>
<html>
<!-- shared html doc for the examples -->
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Event Delegation in jQuery</title>
</head>
<body>
<h1>Event Delegation in jQuery</h1>
<h3>Movies</h3>
<ul id="movies-container"></ul>
<pre>(hover to select)</pre>
<label>Movie: <input type="text" id="new-movie-input"></label>
<button id="add-movie-button">Add</button>
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
<!-- include the correct js file you want to test here -->
<!-- <script src="/js/main.js"></script> -->
</body>
</html>
var movies = ['Battlefield Earth', 'La La Land'];
// create jQuery objects for elements we care about
var $moviesContainer = $('#movies-container');
var $addMovieButton = $('#add-movie-button');
var $newMovieInput = $('#new-movie-input');
// map each existing movie to a jQuery object
var $movies = movies.map(createMovieItem);
// then add to them to the container
// (yes - you can append a list of jQuery objects like this!)
$moviesContainer.append($movies);
// set up click handler for adding a new movie
$addMovieButton.on('click', function () {
// grab the input value, trim
var newMovie = $newMovieInput.val().trim();
// if non-empty, add to the moviesContainer
if (newMovie) {
var $newMovie = createMovieItem(newMovie);
$moviesContainer.append($newMovie);
}
});
// our movie => jQuery object function
// I made it a seperate function to account for how much we are doing
// and my desire to not re-implement this logic in both the initial data phase
// and the addNewMovie phase
function createMovieItem(movie) {
var $movieItem = $('<li>' + movie + '</li>');
// explicitly add event handlers to each item
$movieItem.on('mouseenter', function () {
$movieItem.css('background-color', 'red');
});
$movieItem.on('mouseleave', function () {
$movieItem.css('background-color', '');
});
return $movieItem;
}
// this approach works, but we needed to make a function for creating a new movieItem where each
// one has the event handlers explciitly added to it
// BONUS - how I would do it with ES6 (mostly just adding arrow functions and template strings)
const movies = ['Battlefield Earth', 'La La Land'];
const $moviesContainer = $('#movies-container'),
$addMovieButton = $('#add-movie-button'),
$newMovieInput = $('#new-movie-input'),
$movies = movies.map(movie => `<li>${movie}</li>`);
// I like chaining!
$moviesContainer
.append($movies)
.on('mouseenter', 'li', function () {
$(this).css('background-color', 'red')
})
.on('mouseleave', 'li', function () {
$(this).css('background-color', '');
});
// Note that I *didn't* use arrow functions above because arrow functions do not define their own `this`!
$addMovieButton.click(() => {
const newMovie = $newMovieInput.val().trim();
if (newMovie) {
$moviesContainer.append($(`<li>${newMovie}</li>`));
}
});
var movies = ['Battlefield Earth', 'La La Land'];
// create jQuery objects for elements we care about
var $moviesContainer = $('#movies-container');
var $addMovieButton = $('#add-movie-button');
var $newMovieInput = $('#new-movie-input');
// map each existing movie to a jQuery object
var $movies = movies.map(function (movie) {
return '<li>' + movie + '</li>';
});
// then add to them to the container
$moviesContainer.append($movies);
// set up a event-delegate handlers for the container
// we only set this up once, but it works even if we add new items
$moviesContainer.on('mouseenter', 'li', function () {
$(this).css('background-color', 'red');
});
$moviesContainer.on('mouseleave', 'li', function () {
$(this).css('background-color', '');
});
// NO CHANGES HERE!
$addMovieButton.click(function () {
// grab the input value, trim
var newMovie = $newMovieInput.val().trim();
// if non-empty, add to movies and moviesContainer
if (newMovie) {
$moviesContainer.append($('<li>' + newMovie + '</li>'));
}
});
// Note how in this version we used an event handler with delegation *on the container*, and as a result
// the event handlers we want to register per item are only defined once.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment