Skip to content

Commit

Permalink
Document abortable event listeners (#2759)
Browse files Browse the repository at this point in the history
* Copy edits for “Introduction to events”

* Push “fix fixable flaws” for removeEventListener()

* Drop link to non-standard/undocumented detachEvent

* Document abortable event listeners

This patch includes the following changes:

- Document the "signal" parameter for the addEventListener() options object
- “Add an abortable listener” example code added to addEventListener() article
- Add a similar simple example code to he “Introduction to events” guide
- In both “Introduction to events” guide and removeEventListener() article,
  add prose mentions that you can pass an AbortSignal to an addEventListener()
  call, and abort/cancel/remove the listener by calling abort() on the
  controller owning the AbortSignal.

Related:

- whatwg/dom@83037a1
- https://dom.spec.whatwg.org/#dictdef-addeventlisteneroptions
- https://dom.spec.whatwg.org/#ref-for-concept-event-listener①
- https://dom.spec.whatwg.org/#ref-for-abortsignal③
  • Loading branch information
sideshowbarker authored Mar 1, 2021
1 parent 1594b4b commit 010fcff
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 20 deletions.
26 changes: 19 additions & 7 deletions files/en-us/learn/javascript/building_blocks/events/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,9 @@ <h3 id="Inline_event_handlers_—_dont_use_these">Inline event handlers — don'
<p><strong>Note</strong>: Separating your programming logic from your content also makes your site more friendly to search engines.</p>
</div>

<h3 id="addEventListener_and_removeEventListener">addEventListener() and removeEventListener()</h3>
<h3 id="adding_and_removing_event_handlers">Adding and removing event handlers</h3>

<p>The newest type of event mechanism is defined in the <a href="https://www.w3.org/TR/DOM-Level-2-Events/">Document Object Model (DOM) Level 2 Events</a> Specification, which provides browsers with a new function — <code><a href="/en-US/docs/Web/API/EventTarget/addEventListener">addEventListener()</a></code>. This functions in a similar way to the event handler properties, but the syntax is obviously different. We could rewrite our random color example to look like this:</p>
<p>The modern mechanism for adding event handlers is the <code><a href="/en-US/docs/Web/API/EventTarget/addEventListener">addEventListener()</a></code> method. Using it, we could rewrite our random color example to look like this:</p>

<pre class="brush: js">const btn = document.querySelector('button');

Expand All @@ -198,25 +198,37 @@ <h3 id="addEventListener_and_removeEventListener">addEventListener() and removeE
<p><strong>Note</strong>: You can find the <a href="https://github.com/mdn/learning-area/blob/master/javascript/building-blocks/events/random-color-addeventlistener.html">full source code</a> for this example on GitHub (also <a href="https://mdn.github.io/learning-area/javascript/building-blocks/events/random-color-addeventlistener.html">see it running live</a>).</p>
</div>

<p>Inside the <code>addEventListener()</code> function, we specify two parameters the name of the event we want to register this handler for, and the code that comprises the handler function we want to run in response to it. Note: It is perfectly appropriate to put all the code inside the <code>addEventListener()</code> function, in an anonymous function, like this:</p>
<p>Inside the <code>addEventListener()</code> function, we specify two parameters: the name of the event we want to register this handler for, and the code that comprises the handler function we want to run in response to it. Note: It is perfectly appropriate to put all the code inside the <code>addEventListener()</code> function, in an anonymous function, like this:</p>

<pre class="brush: js">btn.addEventListener('click', function() {
var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
document.body.style.backgroundColor = rndCol;
});</pre>

<p>This mechanism has some advantages over the older mechanisms discussed earlier. First, there is a counterpart function, <code><a href="/en-US/docs/Web/API/EventTarget/removeEventListener">removeEventListener()</a></code>, which removes a previously added listener. For example, this would remove the listener set in the first code block in this section:</p>
<p>This mechanism has some advantages over the older mechanisms discussed here earlier. First, there is a counterpart function, <code><a href="/en-US/docs/Web/API/EventTarget/removeEventListener">removeEventListener()</a></code>, which removes a previously added event handler. For example, this would remove the event handler set in the first code block in this section:</p>

<pre class="brush: js">btn.removeEventListener('click', bgChange);</pre>

<p>This isn't significant for simple, small programs, but for larger, more complex programs it can improve efficiency to clean up old unused event handlers. Plus, this allows you to have the same button performing different actions in different circumstances — all you have to do is add or remove event handlers as appropriate.</p>
<p>Event handlers can also be removed by passing an {{domxref("AbortSignal")}} to {{domxref("EventTarget/addEventListener()", "addEventListener()")}} and then, later, calling {{domxref("AbortController/abort()", "abort()")}} on the controller owning the <code>AbortSignal</code>. For example, to add an event handler that we can remove with an <code>AbortSignal</code>:</p>

<p>Second, you can register multiple handlers for the same listener. The following two handlers wouldn't both be applied:</p>
<pre class="brush: js">const controller = new AbortController();
btn.addEventListener('click', function() {
var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
document.body.style.backgroundColor = rndCol;
}, { signal: controller.signal }); // pass an AbortSignal to this handler</pre>

<p>Then the event handler created by the code above can be removed like this:</p>

<pre class="brush: js">controller.abort(); // removes any/all event handlers associated with this controller</pre>

<p>For simple, small programs, cleaning up old, unused event handlers isn’t necessary — but for larger, more complex programs, it can improve efficiency. Plus, the ability to remove event handlers allows you to have the same button performing different actions in different circumstances — all you have to do is add or remove handlers.</p>

<p>The second advantage that {{domxref("EventTarget/addEventListener()", "addEventListener()")}} has over the older mechanisms discussed here earlier is that it allows you to register multiple handlers for the same listener. The following two handlers wouldn't both be applied:</p>

<pre class="brush: js">myElement.onclick = functionA;
myElement.onclick = functionB;</pre>

<p>The second line overwrites the value of <code>onclick</code> set by the first line. This would work, however:</p>
<p>The second line overwrites the value of <code>onclick</code> set by the first line. What would work, however, is the following:</p>

<pre class="brush: js">myElement.addEventListener('click', functionA);
myElement.addEventListener('click', functionB);</pre>
Expand Down
40 changes: 40 additions & 0 deletions files/en-us/web/api/eventtarget/addeventlistener/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ <h3 id="Parameters">Parameters</h3>
does call <code>preventDefault()</code>, the user agent will do nothing other than
generate a console warning. See <a href="#improving_scrolling_performance_with_passive_listeners">Improving scrolling performance with
passive listeners</a> to learn more.</dd>
<dt><code>signal</code></dt>
<dd>An {{domxref("AbortSignal")}}. The listener will be removed when the given <code>AbortSignal</code>’s {{domxref("AbortController/abort()", "abort()")}} method is called.</dd>
<dt>{{non-standard_inline}} <code>mozSystemGroup</code></dt>
<dd>A {{jsxref("Boolean")}} indicating that the listener should be added to the
system group. Available only in code running in XBL or in the
Expand Down Expand Up @@ -249,6 +251,44 @@ <h4 id="Result">Result</h4>

<p>{{EmbedLiveSample('Add_a_simple_listener')}}</p>

<h3 id="add_a_abortable_listener">Add an abortable listener</h3>

<p>This example demonstrates how to add an <code>addEventListener()</code> that can be aborted with an {{domxref("AbortSignal")}}.</p>

<h4 id="HTML">HTML</h4>

<pre class="brush: html">&lt;table id="outside"&gt;
&lt;tr&gt;&lt;td id="t1"&gt;one&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td id="t2"&gt;two&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
</pre>

<h4 id="JavaScript">JavaScript</h4>

<pre class="brush: js">// Add an abortable event listener to table
const controller = new AbortController();
const el = document.getElementById("outside");
el.addEventListener("click", modifyText, { signal: controller.signal } );

// Function to change the content of t2
function modifyText() {
const t2 = document.getElementById("t2");
if (t2.firstChild.nodeValue == "three") {
t2.firstChild.nodeValue = "two";
} else {
t2.firstChild.nodeValue = "three";
controller.abort(); // remove listener after value reaches "three"
}
}

</pre>

<p>In the above example just above, we modify the code in the previous example such that after the second row’s content changes to "three", we call <code>abort()</code> from the {{domxref("AbortController")}} we passed to the <code>addEventListener()</code> call. That results in the value remaining as "three" forever — because we no longer have any code listening for a click event.</p>

<h4 id="Result">Result</h4>

<p>{{EmbedLiveSample('add_a_abortable_listener')}}</p>

<h3 id="Event_listener_with_anonymous_function">Event listener with anonymous function
</h3>

Expand Down
23 changes: 12 additions & 11 deletions files/en-us/web/api/eventtarget/removeeventlistener/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
title: EventTarget.removeEventListener()
slug: Web/API/EventTarget/removeEventListener
tags:
- API
- DOM
- DOM Element Methods
- EventTarget
- Gecko
- Method
- Reference
- browser compatibility
- removeEventListener
- API
- DOM
- DOM Element Methods
- EventTarget
- Gecko
- Method
- Reference
- browser compatibility
- removeEventListener
---
<div>{{APIRef("DOM Events")}}</div>

Expand All @@ -22,6 +22,8 @@
and various optional options that may affect the matching process; see
{{anch("Matching event listeners for removal")}}</span></p>

<p>Note that event listeners can also be removed by passing an {{domxref("AbortSignal")}} to an {{domxref("EventTarget/addEventListener()", "addEventListener()")}} and then later calling {{domxref("AbortController/abort()", "abort()")}} on the controller owning the signal.</p>

<h2 id="Syntax">Syntax</h2>

<pre
Expand Down Expand Up @@ -211,7 +213,7 @@ <h2 id="Polyfill_to_support_older_browsers">Polyfill to support older browsers</
beginning of your scripts, allowing the use of <code>addEventListener()</code> and
<code>removeEventListener()</code> in implementations that do not natively support it.
However, this method will not work on Internet Explorer 7 or earlier, since extending
the {{domxref("Element.prototype")}} was not supported until Internet Explorer 8.</p>
the <code>Element.prototype</code> was not supported until Internet Explorer 8.</p>

<pre class="brush: js">if (!Element.prototype.addEventListener) {
var oListeners = {};
Expand Down Expand Up @@ -268,5 +270,4 @@ <h2 id="See_also">See also</h2>

<ul>
<li>{{domxref("EventTarget.addEventListener()")}}</li>
<li>{{non-standard_inline}}{{domxref("EventTarget.detachEvent()")}}</li>
</ul>
4 changes: 2 additions & 2 deletions files/en-us/web/guide/events/event_handlers/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ <h2 id="Registering_onevent_handlers">Registering onevent handlers</h2>
<code>document.querySelector("button")<strong>.onclick = function(event) { … }</strong></code>.</li>
</ul>

<p>An <code>on<em>event</em></code>'s event handler property serves as a placeholder of sorts, to which a single event handler can be assigned. In order to allow multiple handlers to be installed for the same event on a given object, you can call its {{domxref("EventTarget.addEventListener", "addEventListener()")}} method, which manages a list of handlers for the given event on the object. A handler can then be removed from the object by calling its {{domxref("EventTarget.removeEventListener", "removeEventListener()")}} function.</p>
<p>An <code>on<em>event</em></code>'s event handler property serves as a placeholder of sorts, to which a single event handler can be assigned. In order to allow multiple handlers to be installed for the same event on a given object, you can call its {{domxref("EventTarget.addEventListener", "addEventListener()")}} method, which manages a list of handlers for the given event on the object. A handler can then be removed from the object either by calling its {{domxref("EventTarget.removeEventListener", "removeEventListener()")}} function, or else, if an {{domxref("AbortSignal")}} was passed to the {{domxref("EventTarget/addEventListener()", "addEventListener()")}} which set the handler, calling {{domxref("AbortController/abort()", "abort()")}} on the controller owning the signal.</p>

<p>When an event occurs that applies to an element, each of its event handlers is called to allow them to handle the event, one after another. You don't need to call them yourself, although you can do so in many cases to easily simulate an event taking place. For example, given a button object <code>myButton</code>, you can do <code>myButton.onclick(myEventObject)</code> to call the event handler directly. If the event handler doesn't access any data from the event object, you can leave out the event when calling <code>onclick()</code>.</p>

Expand Down Expand Up @@ -103,7 +103,7 @@ <h3 id="Event_handlers_parameters_this_binding_and_the_return_value">Event handl
<li><code>event</code>, <code>source</code>, <code>lineno</code>, <code>colno</code>, and <code>error</code> for the {{domxref("GlobalEventHandlers.onerror", "onerror")}} event handler. Note that the <code>event</code> parameter actually contains the error message as a string.</li>
</ul>

<p>When the event handler is invoked, the <code>this</code> keyword inside the handler is set to the DOM element on which the handler is registered. For more details, see <a href="/en-US/docs/Web/JavaScript/Reference/Operators/this#In_an_inline_event_handler">the <code>this</code> keyword documentation</a>.</p>
<p>When the event handler is invoked, the <code>this</code> keyword inside the handler is set to the DOM element on which the handler is registered. For more details, see <a href="/en-US/docs/Web/JavaScript/Reference/Operators/this#in_an_inline_event_handler">the <code>this</code> keyword documentation</a>.</p>

<p>The return value from the handler determines if the event is canceled. The specific handling of the return value depends on the kind of event; for details, see <a href="https://html.spec.whatwg.org/multipage/webappapis.html#the-event-handler-processing-algorithm">"The event handler processing algorithm" in the HTML specification</a>.</p>

Expand Down

0 comments on commit 010fcff

Please sign in to comment.