Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.javascript > #12700

Re: David Mark's Essential Javascript Tips - Volume #8 - Tip #47E - Attaching and Detaching Event Listeners

From Adam Silver <adambsilver@gmail.com>
Newsgroups comp.lang.javascript
Subject Re: David Mark's Essential Javascript Tips - Volume #8 - Tip #47E - Attaching and Detaching Event Listeners
Date 2012-03-14 07:04 -0700
Organization http://groups.google.com
Message-ID <46382.214.1331733850767.JavaMail.geo-discussion-forums@vbel7> (permalink)
References <6d565e55-dae8-4056-870c-8c3340a8e992@u32g2000yqe.googlegroups.com>

Show all headers | View raw


Thanks David,

We have most of this on the Jessie project now.

On Thursday, December 15, 2011 1:09:58 AM UTC, David Mark wrote:
> Attaching and Detaching Event Listeners
> 
> Attaching (and to a lesser extent detaching) event listeners is a
> crucial part of most browser scripting applications. Do it wrong and
> your application will likely be crippled and any end-user will tell
> you a crippled application is worse than one that doesn't work at all.
> 
> The first question is what type of object(s) will need listeners
> attached. Windows? Documents? Elements? All of these? This is not a
> question to be taken lightly as attaching a listener to the wrong
> object will often fail silently (or work in one browser but not the
> next).
> 
> There isn't any practical way to cram all three of these scenarios
> into one function. For one, there is no standard way to determine one
> type of host object from another. That should shoot down the idea
> right there. You will need at least one function per host object type.
> 
> Let's start with windows. The first thing you need to know is that
> window objects were not part of the standard recommendations used by
> the authors of many (if not most) browsers in use today. It doesn't
> really matter if HTML5 "standardizes" the window object now as HTML5
> exists only on paper. Perhaps in five years HTML5's recommendations
> will be relevant to consider. For now, save for its history, the
> window object is one big unknown.
> 
> // Degrades in IE 8-
> // Also degrades in some older browsers that lack this method on
> window objects
> // No frames
> 
> // NOTE: Abbreviated for examples -- use isHostMethod to detect host
> methods
> 
> if (window.addEventListener) {
>   var attachWindowListener = function(eventType, fn) {
>     window.addEventListener(eventType, fn, false);
>   };
> }
> 
> Now the question is what sort of events would history and common sense
> tell you belong to the window? These four come to mind:-
> 
> - load
> - scroll
> - resize
> - orientationchange
> 
> You should never assume that other DOM events (e.g. click, mousedown)
> will bubble up to the window object because there is no standard
> recommendation that says they have to.
> 
> if (window.addEventListener) {
>   var attachWindowListener = function(eventType, fn) {
> 
>     // Remove this line on deployment -- for debugging only
>     if (!(/^(load|scroll|resize|orientationchange)$/.test(eventType)))
> {
>       throw new Error('Use attachListener with an element.');
>     }
> 
>     window.addEventListener(eventType, fn, false);
>   };
> }
> 
> Will all four of those work on the window object? Certainly can't say
> for sure. ISTM that there has been at least one browser in the last
> decade that required "scroll" listeners to be attached to the
> document. So you would rarely use this directly, but through a wrapper
> that attaches multiple listener types (e.g. attachReadyListener from
> previous edition).
> 
> For another example, an attachOrientationListeners wrapper would
> attach both "orientationchange" and "resize" listeners (and then deal
> with only one type, determined the first time through). This is all
> you can do as there is no reliable way to detect what sort of event
> listeners will fire.
> 
> You can detect which DOM0 event handler attributes (e.g.
> "ontouchstart") are supported (as seen in a dozens of variations of my
> original test), but that is only for use with elements and doesn't
> give you enough information to decide which types of listeners your
> script should attach (unless it is using setAttribute to create event
> handler attributes!)
> 
> As noted, the above renditions degrade in IE 8-. In some contexts,
> that's perfect. In others, it's a show-stopper. You should be able to
> create a forked rendition that uses attachEvent at this point. If not,
> see my listener examples on JSPerf (link at end).
> 
> What about documents? The middle child is neglected; other than
> DOMContentLoaded, there's typically nothing to do and therefore no
> wrappers are needed.
> 
> That leaves elements:-
> 
> // Degrades in IE 8-
> // Also degrades in some older browsers that lack this method on
> window objects
> // No frames
> 
> // NOTE: Abbreviated for examples -- use isHostMethod to detect host
> methods
> 
> if (document.documentElement &&
> document.documentElement.addEventListener) {
>   var attachListener = function(el, eventType, fn) {
>     el.addEventListener(eventType, fn, false);
>   };
> 
>   var detachListener = function(el, eventType, fn) {
>     el.removeEventListener(eventType, fn, false);
>   };
> }
> 
> How about "binding" the - this - object for the listener?
> 
> if (attachListener && Function.prototype.call) {
>   var bindListener = function(el, eventType, fn, thisObject) {
> 
>     var listener = function(e) {
>       fn.call(thisObject || el, e);
>     };
> 
>     attachListener(el, eventType, listener);
> 
>     // NOTE: Returns bound listener function
> 
>     return listener;
>   };
> 
>   var unbindListener = function(el, eventType, boundListener) {
>     detachListener(el, eventType, boundListener);
>   };
> }
> 
> Why include the seemingly redundant "unbindListener"? To reinforce
> that it expects a previously bound listener, not the original
> function.
> 
> That should close the book on "event registries". There's no point in
> reproducing that functionality; just let the browser handle it. You
> say you don't want to "keep track" of the returned function reference?
> Just put it where you wherever you were going to track the original
> function (you don't need it anymore).
> 
> What about delegation? The first thing you need is the event target:-
> 
> // Will not work in IE 8-, so use only with attachListener renditions
> that degrade in IE 8-
> 
> function getEventTarget(e) {
>   var target = e.target;
> 
>   // Check if not an element (e.g. a text node)
> 
>   if (1 != target.nodeType) {
> 
>     // Set reference to parent node (which must be an element)
> 
>     target = target.parentNode;
>   }
>   return target;
> }
> 
> ...Or if you will be using a forked rendition to support IE 8-:-
> 
> function getEventTarget(e) {
>   var target = e.target;
> 
>   if (target) {
> 
>     // Check if not an element (e.g. a text node)
> 
>     if (1 != target.nodeType) {
> 
>       // Set reference to parent node (which must be an element)
> 
>       target = target.parentNode;
>     }
>   } else {
>     target = e.srcElement;
>   }
>   return target;
> }
> 
> You may also want the "related target" for certain pointer-related
> events:-
> 
> function getRelatedEventTarget(e) {
>   var target = e.relatedTarget;
> 
>   // Check if not an element (e.g. a text node)
> 
>   if (1 != target.nodeType) {
> 
>     // Set reference to parent node (which must be an element)
> 
>     target = target.parentNode;
>   }
>   return target;
> }
> 
> ...but you will have to wait for the book (or dig through My Library)
> for the IE rendition of that one.
> 
> Once you have the target, the standard procedure is to find an
> ancestor matching a predetermined set of criteria (e.g. tag name,
> class name, etc.) and use it for the - this - object when calling the
> "listener" (really just an unattached callback function).
> 
> The signature looks like this:-
> 
> var delegateListener = function(el, eventType, fn, fnDelegate) {
>   // ...
> }
> 
> Instead of setting of using a single reference for - this - for all
> callbacks, a reference is supplied dynamically by the delegation
> callback function. Will leave that as exercise. Note that it will have
> to return a function, as in the "bindListener" function.
> 
> Also note that you could create a wrapper for this function called
> "delegateListenerByQuery" and have the callback function run a query
> to find the appropriate ancestor. Like most schemes involving queries,
> this is not recommended at all past the mock-up stage. Event handling
> needs to be done as simply and quickly as possible, which lets queries
> out. Besides, there are no ancestor-based queries (except in a newer
> version of MooTools I think). :) I think it's safe to say that most of
> the time you will find the ancestor in fewer hops going up from the
> element, rather than down from the document. YMMV.
> 
> You might be tempted to "bundle" all of these (four!) arguments in a
> single options object. Don't. It's just not a good idea at this
> (foundation) level. These functions get called often at run time, but
> are often wrapped in higher-level functions, so the application
> developer is not often typing out the signatures. Creating and
> discarding an object on every function call is just a self-imposed
> performance penalty. Granted, that technique can make for some really
> "cool" looking code. :)
> 
> How about preventing the default action?
> 
> // NOTE: Does not work in IE 8-, so use only with attachListener
> renditions that degrade in IE 8-
> 
> function cancelDefault(e) {
>   e.preventDefault();
> }
> 
> For IE 8-:-
> 
> function cancelDefault(e) {
>   e.returnValue = false;
> }
> 
> These are created as companion renditions to the attach/detachListener
> functions. If using the forked rendition, put each in the appropriate
> "prong".
> 
> And that's about it, isn't it? You want to cancel bubbling. No, you
> just think you want to do that. It's never a good idea as your widget
> (or whatever) does not own the event. Other listeners may very well be
> interested in what it has to say.
> 
> And speaking of bubbling, not all events can be expected to bubble.
> These come to mind as suspect:-
> 
> - focus
> - blur
> - change
> - submit
> - reset
> 
> As for the last two, you'd have to have a pretty crazy design to want
> to delegate those. If you have such a design, you need to change it as
> there is not a chance in hell in "normalizing" those reliably. You'd
> really have to be touched to try.
> 
> As for the change event, delegation is highly context-sensitive,
> particularly in deciding just when a SELECT should indicate it has
> changed. There's three common scenarios and a whole chapter in the
> book on that.
> 
> It can be useful to delegate focus/blur (e.g. for instant validation
> feedback). Though there is not a chance of "normalizing" those either.
> As always, you have to think about what you are going to do with the
> functions. Once you have the basic focus/blur delegation, you have the
> foundation for the most basic change rendition.
> 
> Of course, you may never need any of these higher-level wrappers as
> you may not need for focus/blur/etc. events to bubble. The one thing
> you don't need is to start tangling up the basic attach/detach logic
> with "normalization" code.
> 
> If using XHR (asynchronous, of course), SQL, Geo Location or anything
> else that calls back asynchronously, in conjunction with DOM events, a
> common message queue can make it easier to manage the interactions
> between the user, their local storage, the server and the document.
> You don't need AOR or "custom events", just a simple structure to
> queue up and deliver messages to "subscribers". This is how widgets
> talk to applications, frameworks and other widgets.
> 
> What to expect from the "standard" libraries (e.g. jQuery). What else?
> One pair of methods for all types of host objects, complicated event
> registries, (in the case of jQuery) no way to set the - this - object
> without calling an obscure (and highly amusing) "proxy" method, logic
> tangled up in queries (e.g. "Live"), etc. They also tangle up their
> basic functions with all manner of confused "normalization" logic,
> further muddying the context in which these scripts can be expected to
> "work". This is unfortunate as they typically have no fallback plan.
> If browsers don't meet *their* expectations, they are just not
> responsible for what their script may do to *your* document. You saw
> the browser icons. As for the end-users, they are on their own;
> anything goes.
> 
> And if you are lucky, they may even throw in some browser sniffing. So
> you've got that to look forward to. :)
> 
> At the very least, you'd expect the libraries to fix the Internet
> Explorer Memory Leak Problem. But such expectations are dashed as most
> (including jQuery last I checked) create leaks and then try to "fix"
> them in an unload listener. Yes, they don't avoid creating leaks, but
> just accept that leaks are a given and tack on an unload listener to
> "clean up" on navigation. Thanks Crockford. Love the CSS Resets too! :
> (
> 
> But what if your site is one of those single-page applications that
> never navigates? It will continue to leak as you add listeners and, if
> it runs long enough, will eventually crash the browser (or at least
> the tab).
> 
> Hopefully, if you have been paying attention over the years, you
> avoided turning your site into a single-page wonder. The entire
> chapter on managing browser history reads: don't even think about it
> (you idiot).
> 
> http://www.cinsoft.net/
> http://www.twitter.com/cinsoft
> http://jsperf.com/browse/david-mark

Back to comp.lang.javascript | Previous | Next — Next in thread | Find similar | Unroll thread


Thread

Re: David Mark's Essential Javascript Tips - Volume #8 - Tip #47E - Attaching and Detaching Event Listeners Adam Silver <adambsilver@gmail.com> - 2012-03-14 07:04 -0700
  Re: David Mark's Essential Javascript Tips - Volume #8 - Tip #47E - Attaching and Detaching Event Listeners David Mark <dmark.cinsoft@gmail.com> - 2012-03-14 13:41 -0700
    Re: David Mark's Essential Javascript Tips - Volume #8 - Tip #47E - Attaching and Detaching Event Listeners Andrew Poulos <ap_prog@hotmail.com> - 2012-03-15 15:36 +1100
      Re: David Mark's Essential Javascript Tips - Volume #8 - Tip #47E - Attaching and Detaching Event Listeners David Mark <dmark.cinsoft@gmail.com> - 2012-03-14 22:37 -0700
        Re: David Mark's Essential Javascript Tips - Volume #8 - Tip #47E - Attaching and Detaching Event Listeners Norman Peelman <npeelmandog@cfl.rr.com> - 2012-03-15 06:38 -0400
          Re: David Mark's Essential Javascript Tips - Volume #8 - Tip #47E - Attaching and Detaching Event Listeners David Mark <dmark.cinsoft@gmail.com> - 2012-03-15 05:38 -0700

csiph-web