Event Handling

1.1 Shorthand Methods

PluralSight: Advanced Techniques in JavaScript and jQuery

I sure love jQuery. And every time I think I should get away from it, I find a new reason why I should use it more frequently. Anyway, this is how I was introduced to event handlers in the first place. Although I didn’t know what events or handlers were. I just knew do y when x happens.

Here’s a list of shorthand events used for common events:

  • .blur()
  • .change()
  • .click()
  • .dblclick()
  • .focus(), focusin(), .focusout()
  • .hover()
  • .keydown(), .keypress(), .keyup()
  • .load()
  • .mousedown(), .mouseenter(), .mouseleave(), .mousemove(), .mouseout(), .mouseover(), .mouseup()

Now we’re going to make something. Here’s the HTML:

<body>
  <div id="WorkArea">
    <h5>Work Area</h5>
    <hr />

  </div>
  <div id="Messages">
    <h5>Messages</h5>
    <hr />
  </div>
</body>

Here’s the CSS

#WorkArea, #Messages {
  position: absolute;
  background-image: radial-gradient(30px 30px, #FFF, #EEE);
  border: 1px solid black;
  width: 300px;
  height: 400px;
  top: 40px;
  overflow-y: auto;
  padding: 5px;
}

#WorkArea {
  left: 10px;
}

#Messages {
  left: 330px;
}

.clickable {
  cursor: pointer;
}

.highlight {
  background-color: yellow;
}

h5 {
  margin-top: -3px;
  margin-bottom: -3px;
}

And here’s the JavaScript:

$(function(){

});

So if you load all this up you’ll end up with two divs and a button, but the button won’t do anything. Here’s the script to make the button add ‘Click Occurred’ to the Messages div.

$(function(){
  $('.clickable').click(function(){
    $('#Messages').append("Click occurred.")
  })
});

Now it works. But it sure would be nice if we could make each message appear on its own line.

    $('#Messages').append("Click occurred.<br />")

1.2 message Function

PluralSight: Advanced Techniques in JavaScript and jQuery

Now we’re going to do some other stuff to make it more…flexible? Or something.

$(function(){
  // the object clicked on, like the button is sent as the argument
  var showEventMessage = function(options){
    // and we use jQuery's .extend function to add more properties to it
    options = $.extend({
      eventType: 'CLICK', // if eventType is not supplied, use this default value
      eventTarget: this, // if eventTarget is not supplied, use this default value
      suffix: '<br/>' // if suffix is not supplied, use this default value
    // lastly, it returns the options?
    }, options);

    // this constructs the output message
    // first it gives the eventType, which we defined above and then ':'
    var message = options.eventType + ': ' +
        // and then either the nodeName (i.e., INPUT for an input button), or 'unknown' if there is none
        (options.eventTarget.nodeName || 'unknown') +
        // and lastly, the suffix, which we define above (i.e., a line break)
        options.suffix;
    $('#Messages').append(message);
  }
  $('.clickable').click(function(){
    showEventMessage.call(this); 
  })
});

.call allows us to sort of call the function in reverse. If we target .clickable and a user clicks on one, then this refers to the object that was clicked on. What we’d like to do is treat this as if it had a method showEventMessage–which it doesn’t. What it would look like if it did would be:

this.showEventMessage(myParameter)

which instead is passing the context in, (not as a parameter!).

showEventMessage.call(this);

would be the same as

showEventMessage.call(this,undefined)

because we’re not passing in any parameters yet! here’s how we might pass in parameter, which is an object containing one k/v pair.

showEventMessage.call(this, {suffix: '!<br/>'});  

further, here’s how we could avoid using .call() altogether

showEventMessage({ eventTarget: this });

1.3 More Elements

PluralSight: Advanced Techniques in JavaScript and jQuery

Here’s an updated HTML so we have more things to click on:

<body>
  <div id="WorkArea">
    <h5 class="clickable">Work Area</h5>
    <hr />

    <p class="clickable">
      Click me, too!
    </p>
    <p>This is some more text with an embedded
      <span class="clickable"> clickable </span> element.
    </p>
  </div>
  <div id="Messages">
    <h5>Messages</h5>
    <hr />
  </div>
</body>

And when you click around, you get something like this:

CLICK: P
CLICK: SPAN
CLICK: H5
CLICK: INPUT

Next we chain a .dblclick to our .click :

$('.clickable')
  .click(function(){
    showEventMessage.call(this);
  })
  .dblclick(function(){
    showEventMessage.call(this, {eventType: 'double click'});
  });

When we double click on a word, the word is highlighted. Let’s say we don’t want that to happen—how could we stop it? Well, we could first listen for mousedown events.

$('.clickable')
    .click(function(){
      showEventMessage.call(this);
  })
    .dblclick(function(){
      showEventMessage.call(this, {eventType: 'double click'});
  })
    .mousedown(function(){
      showEventMessage.call(this, {eventType: 'mouse down'})
      return false;
  })

Here we can see the .mousedown() event as it happens. Actually, it shows an event message before the click event does. Just as you’d expect even though we wrote them in a different order. So what does the return false do here—would it mess up the chaining? The tutorial makes sure to note that

“we’re chaining the attachment of the event handler, not the actual function”

that in short means that regardless of whether or not anything is returned, the next jQuery function chained won’t be dealing with the returned value, but rather with the targeted element. The return false only runs when the event is handled, not at any other time.

According to W3C specifications, event handlers aren’t supposed to return a value at all.

So why return anything at all? It’s a browser trick! It tells the browser to cancel the event. If we tell the browser to return false on mousedown, we’re telling the browser to not do the normal process for that event. In this case, it’d be highlighting text. Now, when we double click, it’s not highlighting the text because it never reaches that point of execution in the browser.

1.4 Propagation

PluralSight: Advanced Techniques in JavaScript and jQuery

Here’s some terms I’m scared of:

  • propagation
  • bubble up
  • stopPropagation
  • stopImmediatePropagation

If I’m not scared of them by the end of this tutorial, I’ll count it a success.

When an event occurs, it may be handled by something we wrote, or it may be handled by something else. The example given in the tutorial is the mouseover event—it’s currently not handled by any element in our page. By which we mean that we can mouseover anything on the page and we don’t have anything special that occurs during the process, like text highlighting or pictures changing or something like that. Yet, mouseovers are happening all the time. And what happens to them? They’re passed to their parent element. So, if some span is mouseover’d and there’s no handler written for that span, next its parent, a div, is checked for a mouseover handler. This continues up the DOM tree—which is also known as “bubbling up the DOM”—where it eventually reaches the Document Object and is disposed of if there’s still no handler.

So, let’s say we want to stop propagation. The way we’ve done it so far is by using return false–which works, but it’s not supposed to be officially supported, so any browser could drop support for it at any time if they felt like it. Fortunately, there’s something else: preventDefault(). It prevents the default behavior of the event. For instance, the default behavior when clicking on a link is that the browser follows the href URL somewhere. If we preventDefault() to a click event on a link, it’ll prevent the browser from following that URL.

Prior to IE9 this wasn’t supported—you’d have to return false, however, using jQuery fixes this, where if preventDefault() is not supported then it’ll return false.

Okay, so here’s the new HTML, which just adds some extra clickables to elements so that we can really see propagation in action.

<body>
  <div id="WorkArea" class="clickable">
    <h5 class="clickable">Work Area</h5>
    <hr />

    <p class="clickable">
      Click me, too!
    </p>
    <p class="clickable">This is some more text with an embedded
      <span class="clickable"> clickable </span> element.
    </p>
  </div>
  <div id="Messages">
    <h5>Messages</h5>
    <hr />
  </div>
</body>

You know what else I’m scared of?—this whole event thing—it sort of emerges from nowhere and both where and wherefore it doth go remaineth to me a mystery. For instance, this:

.click(function(){
showEventMessage.call(this);

becomes this:

.click(function(event){
      showEventMessage.call(this, { eventType: event.type });  

Uh oh. The change makes a little more sense when you’re converting something from:
eventType: 'mouse down' to eventType: event.type — but rationale becomes spotty when you’re just replacing a this. Unfortunately, the tutorial explanation is spotty as well.

“jQuery passes the standard JavaScript event as a parameter to our anonymous function.”

Let’s learn more…

1.4.1 The Event Object

There’s surprisingly little relevant info on the “event object” subject—at least on page 1 of Google. Everyone acknowledges that there’s this mysterious event object, but beyond that very little. So I’m playing a bit of detective here.

Mozilla Developer Network / Web / API / Event

According to this documentation, Event() creates an event object and returns it to the caller. Okay, so an event is an object. I’d never really thought of it as an object before, but—it would appear that it’s pretty much like anything else. You can make new ones using a constructor. So…

a = new Event() \\ TypeError: Not enough arguments to Event.

\\ this was just a guess, but I figure we have to pass something in that would maybe create a click.
a = new Event(`click`); 

\\ click { target: null, isTrusted: false, eventPhase: 0, bubbles: false, cancelable: false, defaultPrevented: false, timeStamp: 1483497978389333, NONE: 0, CAPTURING_PHASE: 1, AT_TARGET: 2, BUBBLING_PHASE: 3 }

Actually, it turns out you can pass in onclick and you get the same result. Or {eventType: ‘click’} — and in all these cases, nothing is returned that gives any indication that the value passed in matters.

So, I guess that when an event occurs, an event object is created. Okay, but where does the argument come from? How do we know when to send it, or when not to, or what to call it? I’ve seen it called event as well as e–I suppose this is convention—but what if I want to pass in something else instead?

Dottoro: web reference

And lookie, on this poorly named page (ljogqtqm.php) is the answer I’m looking for (and hoping is correct):

“In Firefox, Opera, Google Chrome, Safari and Internet Explorer from version 9, when an event occurs, an event object is initialized and passed to the event handlers as the first parameter. The type of the event object depends on the current event.
In Internet Explorer before version 9, an event object is passed to an event handler only if it is registered with the attachEvent method. If an event listener is registered with the attachEvent method, the passed event object is a copy of the window.event object in all Internet Explorer versions.” [emphasis mine]

So, in short, when is the event object created? When an event occurs. How does it end up as a parameter? It’s passed to the handler as the first parameter.

1.5 Propagation, continued…

PluralSight: Advanced Techniques in JavaScript and jQuery

So, considering the same bit of script:

.click(function(event){
      showEventMessage.call(this, { eventType: event.type });  

So—on click, do some anonymous function, and pass in the event object as the first (and only) parameter. First, it’s easier to write consistent, generic code this way, and second, somehow it seems cleaner (probably because it’s consistent and generic!). Anyway, testing this code out, the results are like this when I click on the word “clickable”:

mousedown: SPAN
click: SPAN
click: P
click: DIV

The lower-case results show that we’re using the value of a parameter, rather than the hardcoded ‘CLICK’ we had before. But the real point of interest here is that three clicks events are captured when all I’ve done is click once. The mousedown occurs first, of course, but then the click on the SPAN occurs. It then bubbles up the chain to it’s parent, P, and then further up the chain to the parent DIV. And that’s just the elements that we’ve made clickable–in other words, by making them clickable we’ve made it possible to perform some action as the event occurs. If nothing is set on the element, then the event continues up the chain and doesn’t notify us.

There is one thing wrong with this output though: according to the tutorial we should be getting three mousedown events captured, not just one. When I removed the return false; it begins sending the expected messages:

mousedown: SPAN
mousedown: P
mousedown: DIV
click: SPAN
click: P
click: DIV

However, upon double-clicking on words, they still highlight—which is not what we want. preventDefault() has the same effect. stopPropagation() has the same issues as return false. So?

In the meantime, you’ll see here that ALL the mousedown events are captured and propagate up the chain before ANY of the click events do.

Here’s a double-click:

mousedown: SPAN
mousedown: P
mousedown: DIV
click: SPAN
click: P
click: DIV
mousedown: SPAN
mousedown: P
mousedown: DIV
click: SPAN
click: P
click: DIV
dblclick: SPAN
dblclick: P
dblclick: DIV

The first mousedown propagates, followed by the first click. Then the second ones. And then the dblclicks. Ah hah—the tutorial didn’t have the return false in there in the first place, but since it didn’t show us the code, we didn’t know. Now they’ve replaced return false and we get the same sort of results we had before. For instance, here’s the same double-click.

mousedown: SPAN
click: SPAN
click: P
click: DIV
mousedown: SPAN
click: SPAN
click: P
click: DIV
dblclick: SPAN
dblclick: P
dblclick: DIV

So now there’s no text highlighting again, and we only get a single mousedown (on the first element we click) but we still get all the propagated clicks and double-clicks. Now we move on to replacing return false with preventDefault(). Again, in my test I get:

mousedown: SPAN
mousedown: P
mousedown: DIV
click: SPAN
click: P
click: DIV
mousedown: SPAN
mousedown: P
mousedown: DIV
click: SPAN
click: P
click: DIV
dblclick: SPAN
dblclick: P
dblclick: DIV

And words still highlight. The tutorial says,

preventDefault() is a method of the event object. It is not an intrinsic JavaScript function.”

Which indicates that we haven’t copied the code correctly because this is what we have written:

.mousedown(function(event){
      showEventMessage.call(this, {eventType: event.type})
      preventDefault();

When what we should have is:

.mousedown(function(event){
      showEventMessage.call(this, {eventType: event.type})
      event.preventDefault();

Ah hah! Now it’s preventing word highlights. But we’re still getting three mousedowns. So the default behavior of the event is prevented, but everything else happens as usual (as opposed to return false preventing the event from continuing to bubble up the DOM). If this was IE, preventDefault() would not be supported, and instead it would just return false. Thus, different behaviors on different browsers.

So which should be used? It’s important to keep in mind that they produce different results. preventDefault will stop only the default behavior, while return false prevents the event from propagating up the DOM tree, stopping it from being processed by other elements. There’s another option for this though, stopPropagation, which stops the event from propagating up the DOM tree.

.mousedown(function(event){
      showEventMessage.call(this, {eventType: event.type})
      event.stopPropagation();
      event.preventDefault();

Why aren’t we chaining the two? Because stopPropagation does not return the event object. Neither does preventDefault. The result of a double-click?

mousedown: SPAN
click: SPAN
click: P
click: DIV
mousedown: SPAN
click: SPAN
click: P
click: DIV
dblclick: SPAN
dblclick: P
dblclick: DIV

A single mousedown and no highlighted text. Just like return false when it’s working properly! Again, stopPropagation will prevent parent elements from processing the event, but it won’t prevent the current element from processing it. To illustrate this, we’ll chain an additional mousedown event:

.mousedown(function(event){
      showEventMessage.call(this, {eventType: event.type})
      event.stopPropagation();
      event.preventDefault();
    // return false;
  })
    .mousedown(function(event){
      showEventMessage.call(this, {eventType: event.type, 
                                  suffix: '#2<br/>' });
  })

And on double-click:

mousedown: SPAN
mousedown: SPAN#2
click: SPAN
click: P
click: DIV
mousedown: SPAN
mousedown: SPAN#2
click: SPAN
click: P
click: DIV
dblclick: SPAN
dblclick: P
dblclick: DIV

Why are we getting both mousedowns? Why isn’t stopPropagation preventing the event from being processed a second time? Because the element is not its own parent—it’s on the same level. To stop the event from being processed again, we’d use the stopImmediatePropagation method instead:

.mousedown(function(event){
      showEventMessage.call(this, {eventType: event.type})
      event.stopImmediatePropagation();
      event.preventDefault();
    // return false;
  })

Which results:

mousedown: SPAN
click: SPAN
click: P
click: DIV
mousedown: SPAN
click: SPAN
click: P
click: DIV
dblclick: SPAN
dblclick: P
dblclick: DIV

We can prevent multiple click events by adding stopPropagation or stopImmediatePropagation to the click event handler:

$('.clickable')
    .click(function(event){
      showEventMessage.call(this, { eventType: event.type }); 
      event.stopImmediatePropagation();
  })

Result:

mousedown: SPAN
click: SPAN
mousedown: SPAN
click: SPAN
dblclick: SPAN
dblclick: P
dblclick: DIV

Why use one instead of the other? If we use stopImmediatePropagation then we’ll ensure that future code that handles clicks will not work. That may not be desirable in the future. If we use stopPropagation then we won’t prevent future developers from handling click events, but we won’t get the benefits of stopImmediatePropagation stopping additional processing on the current click event. So what should we do?

The tutorial presents an option that doesn’t seem to actually be a solution: detecting whether or not something like stopPropagation has been called on an element, and only running the subsequent code if it has not. Well…isn’t that the default behavior? Or rather, isn’t that default result?

$('.clickable')
    .click(function(event){
      showEventMessage.call(this, { eventType: event.type }); 
      event.stopImmediatePropagation();
  })
    .dblclick(function(event){
      if (!event.isPropagationStopped() &&
         !event.isImmediatePropagationStopped() &&
         !event.isDefaultPrevented()){
            showEventMessage.call(this, {eventType: event.type});
      }
    })
    .mousedown(function(event){
      if (!event.isPropagationStopped() &&
         !event.isImmediatePropagationStopped() &&
         !event.isDefaultPrevented()){
          showEventMessage.call(this, {eventType: event.type})
          event.stopImmediatePropagation();
          event.preventDefault();
      }
    })
    .mousedown(function(event){
      if (!event.isPropagationStopped() &&
         !event.isImmediatePropagationStopped() &&
         !event.isDefaultPrevented()){
           showEventMessage.call(this, {eventType: event.type, 
                                  suffix: '#2<br/>' });
        }
      })

And the result:

mousedown: SPAN
click: SPAN
mousedown: SPAN
click: SPAN
dblclick: SPAN
dblclick: P
dblclick: DIV

Still not sure what the big deal is.

1.6 No Shorthand

PluralSight: Advanced Techniques in JavaScript and jQuery

So here’s a list of jQuery event handling methods. If I write bind, what I mean is .bind()

  • bind
  • unbind
  • live
  • die
  • delegate
  • undelegated
  • on
  • off
  • one

The only ones I’ve used are on and one–although these all ultimately use on as part of their functionality.

1.6.1 bind / unbind

PluralSight: Advanced Techniques in JavaScript and jQuery

Attach event handlers to elements on the page. It would look like this:

element.bind('click', function(event){
  // …
});

As opposed to

element.click(function(event){
  // …
});

You can see here that bind is a more generic way of adding event handlers, and how click is truly shorthand for it. With bind, the eventType is the first parameter, and the handler function is the second paramenter.

.unbind() is the opposite—it unbinds an event handler that’s added using bind. Like this:

element.unbind('click');)

These work for static content. But for dynamically loaded elements, they won’t work, even if the selector is correct. Which is why we have the live method.

1.6.2 Live / Die

PluralSight: Advanced Techniques in JavaScript and jQuery

element.live('click', function(event){
  // …
})

.live() attaches handlers not just to elements that currently exist, but also to elements that may exist in the future. This is how it works: it attaches a listener to the document object and listens for events bubbling up the DOM tree. If an event bubbles up to the document object (which we know eventually happens before being discarded if not handled). jQuery then checks to see if the event matches the element/event used with the .live() method. It then processes the handler for those elements.

.die() removes any handler attached via .live().

1.6.3 delegate / undelegate

PluralSight: Advanced Techniques in JavaScript and jQuery

Delegate is similar to live, but more powerful. It’s more efficient, and “plays more nicely when included in method chains.” Okay. The similarity is that it attaches the event handlers further up the DOM (than the element in question). Where it gets really interesting is that you can specify which element you’d like to attach the event handler—as in, it doesn’t need to be attached to the document object.

element.delegate(elementAttachedTo, 'click', function(event){
  // …
})

.undelegate() removes event handlers attached via .delegate().

1.6.4 On, Off, One

PluralSight: Advanced Techniques in JavaScript and jQuery

Guess what? As cool as all this stuff is, in modern times we use .on() instead of bind, live, or delegate. So forget everything up until this point maybe.

And yes, .off() is used instead of unbind, die, and undelegated.

Currently, when we use any bind, live, delegate–on is being used behind the scenes. Eventually support for the older methods will probably be dropped. on can be used the same as bind or live

element.on('click', function(){
  //…
});

And if it should act like delegate, simply add in an extra parameter, although in the opposite order from delegate:

element.on('click', 'elementAttachedTo', function(){
  //…
});

Lastly, the one method executes the function once, after which jQuery automatically executes the off method and it’s detached. The end!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s