e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith{{>guide-examples-style}}
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<h2>Understanding the problem</h2>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<p>Consider the following Todo List widget:</p>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<fieldset id="todo-example">
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<legend>Todo List</legend>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<ol>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith <li><button class="delete-todo" title="remove">remove</button>Read YUI documentation</li>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith <li><button class="delete-todo" title="remove">remove</button>Build awesome web app</li>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith <li><button class="delete-todo" title="remove">remove</button>Profit!</li>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith</ol>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<input id="todo"> <button id="add-todo" type="button">add</button>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith</fieldset>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<script>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke SmithYUI().use('node-event-delegate', 'event-key', function (Y) {
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith var todoList = Y.one('#todo-example ol'),
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith newTask = Y.one('#todo');
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith todoList.delegate('click', function () {
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith this.ancestor('li').remove();
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith }, 'button');
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith function addTodo() {
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith todoList.append(
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith '<li><button class="delete-todo">remove</button>' +
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith newTask.get('value') +
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith '</li>');
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith newTask.set('value', '');
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith }
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith Y.one('#add-todo').on('click', addTodo);
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith newTask.on('key', addTodo, 'enter');
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith});
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith</script>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<p>All tasks are given a "remove" button. When new tasks are added, they
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithshould get a remove button that removes that task. Here's the markup for
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smiththis:</p>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith```
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<fieldset id="todo-example">
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<legend>Todo List</legend>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<ol>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith <li><button class="delete-todo">remove</button>Read YUI documentation</li>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith <li><button class="delete-todo">remove</button>Build awesome web app</li>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith <li><button class="delete-todo">remove</button>Profit!</li>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith</ol>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<input id="todo"> <button id="add-todo" type="button">add</button>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith</fieldset>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith```
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<p>In the old days, you would have four click subscriptions:</p>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<ol>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith <li>The remove button for #1</li>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith <li>The remove button for #2</li>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith <li>The remove button for #3</li>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith <li>The add button for creating new tasks</li>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith</ol>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<p>When a user types in a new task and clicks the <em>add</em> button, a new
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith&lt;li&gt; and corresponding &lt;button&gt; are created, and a fifth click
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithsubscription is added, one for the new button. The callback for the remove
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithbuttons could be unique for each button, or a generic function that determined
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithwhich item to remove based on some other info from the event or button.</p>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<p>When a user clicks on one of the remove buttons, the item is removed. The
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithassociated click event subscription is left in the system, taking up memory.
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke SmithSo to solve this, maybe the event subscription is detached before the item is
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithremoved. Now there are four initial subscriptions and additional logic to
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithproperly detach subscriptions before items are removed.</p>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<p>Over time, the number of items on the todo list grows, and so the number of
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithsubscriptions in the system, and thus memory consumed, grows with it.
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke SmithAdditionally, if at some point, the entire list needs to be cleared, that's a
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithlot of subscriptions to detach before it's ok to flush the list's
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith`innerHTML`.</p>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<h2>What is event delegation?</h2>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<p>Event delegation is a way to reduce the number of subscriptions used to
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithsupport this system. In the example case, only two click subscriptions are
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithneeded: one for the add button, and one for every remove button click. The
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithsecond one is the <em>delegated subscription</em>. Here's how to think about
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithit:</p>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<p>The key to event delegation is understanding that a click on a remove button
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithis also a click on</p>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<ul>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith <li>the list item that the button is in</li>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith <li>the list itself</li>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith <li>the &lt;fieldset&gt; that the list is in</li>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith <li>etc up to the &lt;body&gt; and finally the `document`<a
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith href="#footnote1">[1]</a></li>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith</ul>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<p>Instead of subscribing to the button's "click" event, <em>you can subscribe
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithto the list's "click" event</em><a href="#footnote2">[2]</a>.</p>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<h2>You clicked somewhere, but <em>where</em>?</h2>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<p>When you click anywhere on the document, the browser dispatches a click
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithevent that is assigned an `e.target` property corresponding to <em>the element
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smiththat triggered the event</em>. For example, if you click on "Profit!", the
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithevent originated from the &lt;li&gt; with "Profit!" in it, so `e.target` will
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithbe that &lt;li&gt; element<a href="#footnote2">[3]</a>.</p>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<p>With these two bits of information, we can create a single click
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithsubscription to respond to every button click in the Todo list.</p>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith```
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithfunction handleClick(e) {
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith // look at e.target
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith}
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke SmithY.one('#todo-example ol').on('click', handleClick);
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith```
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<p>Now since there are no subscriptions tied directly to the individual
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithbuttons, we can add new items to the list without needing to add more
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithsubscriptions. Similarly, we can remove items or even clear the list's
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith`innerHTML` without needing to detach subscriptions because there aren't any
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithsubscriptions inside the list to clear.</p>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<h2>More work in the event subscriber</h2>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<p>Since any click inside the list is now triggering the event subscriber, it
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithwill be executed for button clicks, but also for clicks on the task item's text
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith(e.g. "Profit!"). To make sure this click happened on a button, we need to
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithinspect `e.target` to make sure it is a button.</p>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith```
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithfunction handleClick(e) {
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith if (e.target.get('tagName').toLowerCase() === 'button') {
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith // remove the item
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith }
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith}
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith```
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<p>This can start to get tricky when you're triggering on an element that can
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithcontain children. For example, if there were no buttons, but instead you
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithwanted to remove items just by clicking on the &lt;li&gt;, you'd need to check
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithif `e.target` was an &lt;li&gt;. But if it's not, you have to look at
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith`e.target`'s `parentNode` and potentially that node's `parentNode` and so on,
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithbecause `e.target` will always refer to the most specific element that received
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smiththe click. This can amount to a lot of filtering code wrapping the item
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithremoval logic, which hinders the readability of your app.</p>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
77c61f48946d06f2e1a4339a1ea83fa6f49ed085Luke Smith<h2>Let `node.delegate(...)` do the work for you</h2>
77c61f48946d06f2e1a4339a1ea83fa6f49ed085Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<p>This is where <a href="../event/#event-delegation">`node.delegate(...)`</a>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithcomes in. `node.delegate(...)` boils down the filtering logic to a css
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithselector, passed as the third argument. The subscribed callback will only
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithexecute if the event originated from an element that matches (or is contained
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithin an element that matches) this css selector. This allows the code to power
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smithour Todo widget to look like this:</p>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith```
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke SmithYUI().use('node-event-delegate', 'event-key', function (Y) {
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith var todoList = Y.one('#todo-example ol'),
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith newTask = Y.one('#todo');
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith // clicks inside the todo list on a <button> element will cause the
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith // button's containing <li> to be removed
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith todoList.delegate('click', function () {
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith this.ancestor('li').remove();
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith }, 'button');
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith // Adding a new task is only appending a list item
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith function addTodo() {
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith todoList.append(
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith '<li><button class="delete-todo">remove</button>' +
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith newTask.get('value') +
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith '</li>');
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith newTask.set('value', '');
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith }
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith Y.one('#add-todo').on('click', addTodo);
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith newTask.on('key', addTodo, 'enter'); // enter also adds todo (see event-key)
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith});
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith```
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<hr>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<h3>Footnotes</h3>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith<ol id="footnotes">
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith <li id="footnote1">If there are click subscriptions at multiple points in
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith the DOM heirarchy, they will be executed in order from most specific (the
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith button) to least specific (document) unless `e.stopPropagation()` is
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith called along the line. This will prevent subscriptions from elements higher
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith up the parent axis from executing.</li>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith <li id="footnote2">We're using the "click" event here, but this all applies
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith to other events as well.</li>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith <li id="footnote3">Actually the event originated from the text node inside
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith the &lt;li&gt;, but IE reports the origin (the `srcElement` in IE) as the
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith &lt;li&gt;, which is probably what developers want, anyway. YUI fixes
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith `e.target` to bet the element for browsers that report it as the text
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith node.</li>
e9b01de77b1a53553e58caf4f0c5392735102fc1Luke Smith</ol>