Advanced Event Handling

1 ADVANCED EVENT HANDLING

1.1 EVENT HANDLER METHODS

PluralSight: Advanced Techniques in JavaScript and jQuery

When we last left off, this is what our code looked like:

HTML:

<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>

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;
}

JavaScript:

$(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(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 now you’re all caught up. So, we built this using shorthand jQuery methods for event handling, and we’ve learned about the more-powerful longer methods for event handling. So what we’re going to do now, like pros, will be to replace the shorthand methods with the long methods.

For instance, this:

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

can be replaced with this:

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

Likewise…

    .on('dblclick',function(event){})
// and
.on('mousedown',function(event){})

No difference, at least visibly, in functionality. How is this any better? Well, for one, because we can do something like combine click and dblclick and the first mousedown handling since most things about them otherwise is the same. Now it looks like this:

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

And a doubleclick produces:

mousedown: SPAN
click: SPAN
mousedown: SPAN
click: SPAN
dblclick: SPAN

The event doesn’t bubble up the DOM chain, and we don’t see the second mousedown message because it fails the checks we have in place (i.e., isPropagationStopped()). Lastly, we are getting highlighted text again, because we don’t have preventDefault being called. To deal with this we can write a conditional that checks for the event.type:

.on('click dblclick mousedown',function(event){
      if (!event.isPropagationStopped() &&
         !event.isImmediatePropagationStopped() &&
         !event.isDefaultPrevented()){
            showEventMessage.call(this, {eventType: event.type});
            event.stopPropagation();

            if (event.type === 'mousedown')
              event.preventDefault();
         }
    })

And for the sake of completion, double-clicking still selects text in IE, which can be stopped if we add this:

    .on('selectstart', function(event){
      return false;
    });

Just remember, IE’s market share pays for all the good deeds of the Bill and Melinda Gates Foundation.

Anyway. Next we’ll look at .off(). Let’s say we’re attaching handlers to dynamically loaded content. Like, a newsfeed, for instance. Well, every time that newsfeed gets refreshed, the handlers are reattached, and a common problem is that everything works right the first time. On the second time events are handled twice. On the third time, events are handled three times. In the current script it’s written in such a way that upon handling an event we also do things like stopPropagation so that the next time the event would be handled it’d first check to see if stopPropagation had been called—which it has, so it won’t handle the event again. (Also, this isn’t a scenario where we’d have the bug). But regardless, we’re still just tacking additional event handlers onto the element, which isn’t the cleanest way of dealing with it. Also, there are situations where we’d need the event to propagate up the DOM. So how could we deal with it differently? Rather, how do we make sure that the handler we need to attach is not already attached? Well, let’s get to it.

The simplest way of doing things is to remove all event handlers before putting any new ones on. This would be done by calling .off() as the first thing in the chain.

$('.clickable')
    .off()
    .on('click dblclick mousedown',function(event){
      if (!event.isPropagationStopped() &&
         !event.isImmediatePropagationStopped() &&
         !event.isDefaultPrevented()){
            showEventMessage.call(this, {eventType: event.type});
            event.stopPropagation();

            if (event.type === 'mousedown')
              event.preventDefault();
         }
    })

This is also most dangerous. Since we only have a small script here and we know everything that’s going on, we can do it without concern. But on a larger app, we could run into serious problems by removing all handlers so carelessly. That’s right, carelessly. This is less careless because we’re only removing click handlers:

$('.clickable')
    .off('click')
    .on('click dblclick mousedown',function(event){
      if (!event.isPropagationStopped() &&
         !event.isImmediatePropagationStopped() &&
         !event.isDefaultPrevented()){
            showEventMessage.call(this, {eventType: event.type});
            event.stopPropagation();

            if (event.type === 'mousedown')
              event.preventDefault();
         }
    })

If we copy .off() to the end of the chain, we won’t get any click messages at all. But what if we copy it somewhere else, in the middle (and also add click to the second mousedown so we have something to really look at):

$('.clickable')
    .off('click')
    .on('click dblclick mousedown',function(event){
      if (!event.isPropagationStopped() &&
         !event.isImmediatePropagationStopped() &&
         !event.isDefaultPrevented()){
            showEventMessage.call(this, {eventType: event.type});
            event.stopPropagation();

            if (event.type === 'mousedown')
              event.preventDefault();
         }
    })
    .off('click')
    .on('mousedown click', function(event){
      if (!event.isPropagationStopped() &&
         !event.isImmediatePropagationStopped() &&
         !event.isDefaultPrevented()){
           showEventMessage.call(this, {eventType: event.type, 
                                  suffix: '#2<br/>' });
        }
    })
    .on('selectstart', function(event){
      return false;
    })

The results:

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

What’s the narrative behind this?

  1. Remove all click handlers
  2. Add a click, dblclick, and mousedown handler
  3. Remove all click handlers
  4. Add a mousedown#2, and a click#2 handler
  5. The mousedown message displays
  6. Mousedown elements propagation is stopped
  7. Mousedown #2 doesn’t display since it checks to see if propagation is stopped
  8. No clicks happen since the handler has been removed
  9. Click #2 happens, and then bubbles up the DOM chain
  10. Dblclick happens

If we add mousedown to off, so that we have

    .off('click mousedown')

We end up with this:

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

Where mousedown #2 bubbles up the DOM, and mousedown #1 never gets to appear.

1.2 NAMED FUNCTIONS

PluralSight: Advanced Techniques in JavaScript and jQuery

So far we’ve been using anonymous functions to add handlers. There’s another way: named functions! Why? The reasons listed by the tutorial are that they can be removed easily, centralized, and updated for global impact.
For instance, if we create a named function, we can remove it without touching any other handler function.

Next, the tutorial adds a named function to the existing code, but I cannot figure out how it’s able to be placed where it is. See line 20 for the screwy bit:

$(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);
  }, // why is there a comma here? how is this next part crammed in here with a comma?
  namedHandler = function(event){
    if (!event.isPropagationStopped() && 
        !event.isImmediatePropagationStopped() &&
        !event.isDefaultPrevented()){
      showEventMessage.call(this, { eventType: 'namedHandler ' + event.type });
      event.stopPropagation();
    }
  }

  $('.clickable')
    .off('click')
    .on('click dblclick mousedown',function(event){
      if (!event.isPropagationStopped() &&
         !event.isImmediatePropagationStopped() &&
         !event.isDefaultPrevented()){
            showEventMessage.call(this, {eventType: event.type});
            event.stopPropagation();

            if (event.type === 'mousedown')
              event.preventDefault();
         }
    })
    .off('click mousedown')
    .on('mousedown click', function(event){
      if (!event.isPropagationStopped() &&
         !event.isImmediatePropagationStopped() &&
         !event.isDefaultPrevented()){
           showEventMessage.call(this, {eventType: event.type, 
                                  suffix: '#2<br/>' });
        }
    })
    .on('selectstart', function(event){
      return false;
    })
});

Well, I tried to break it down…is it a function with multiple bodies? A function with multiple code blocks? Are there code blocks in JavaScript? What’s happening? Thanks to [StackOverflow: “Javascript variable definition: Commas vs Semicolons”] (http://stackoverflow.com/questions/3781406/javascript-variable-definition-commas-vs-semicolons), I think that this:

},
  namedHandler = function(event){
    if (!event.isPropagationStopped() &&

is equal to

};
  var namedHandler = function(event){
    if (!event.isPropagationStopped() &&

Yeesh! If there’s one criticism I have of this tutorial so far, it’s that the code examples are all written in syntax that someone at the level of this tutorial will not have learned yet.

Now, if we call that function in our current chain, excerpted here:

$('.clickable')
    .off('click')
    .on('click dblclick', namedHandler)
// …and so forth

On doubleclick we end up with this:

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

See, namedHandler does its dblclick thing and then blocks the other dblclick from being activated. After removing the .off() from the middle of the chain, the output is:

mousedown: SPAN
namedHandler click: SPAN
mousedown: SPAN
namedHandler click: SPAN
namedHandler dblclick: SPAN

As for .off(), we can use it to show how to remove just click event handling from the namedHandler.

.off('click', namedHandler);

namedHandler is still attached for dblclick events. Compare this output to the last one:

mousedown: SPAN
click: SPAN
mousedown: SPAN
click: SPAN
namedHandler dblclick: SPAN

Event handlers operate in order. If you need to ensure that one has operated before another, you need write some logic to determine whether or not the first has run yet. The tutorial’s example is: if a form needs to validate before it can be submitted, and submit won’t process if the validation hasn’t worked out. Let’s say someone else also attaches a function to the submit to do something like send a notification message that a form has been submitted. If the notification portion fires before the validation portion, then notifications will be sent for all the failed submission attempts as well. So the admin might receive three notifications for every one form sent. Bad news!

How do we fix this? In a single page example like we’re working on, it’s fine to put handlers in on an element by element basis like this, but in a larger, dynamic site, all the handlers should come from a single library so that other developers can see what they’re really working with.

1.3 NAMESPACE

PluralSight: Advanced Techniques in JavaScript and jQuery

When we’re using .off(), we should really only be removing event handlers that we ourselves have added. Otherwise we can’t be sure what of past or future developers we’re removing! This is why we should use an event namespace. Here’s our latest JavaScript, with the namedHandler() removed and all the off methods as well.

$(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')
    .on('click dblclick mousedown',function(event){
      if (!event.isPropagationStopped() &&
         !event.isImmediatePropagationStopped() &&
         !event.isDefaultPrevented()){
            showEventMessage.call(this, {eventType: event.type});
            event.stopPropagation();

            if (event.type === 'mousedown')
              event.preventDefault();
         }
    })
    .on('mousedown click', function(event){
      if (!event.isPropagationStopped() &&
         !event.isImmediatePropagationStopped() &&
         !event.isDefaultPrevented()){
           showEventMessage.call(this, {eventType: event.type, 
                                  suffix: '#2<br/>' });
        }
    })
    .on('selectstart', function(event){
      return false;
    })
});

Namespaces are added using a dot syntax, where the namespace is placed after the event type. So, for a namespace called demo

$('.clickable')
    .on('click.demo dblclick.demo mousedown.demo',function(event){
      if (!event.isPropagationStopped() &&
         !event.isImmediatePropagationStopped() &&
         !event.isDefaultPrevented()){
            showEventMessage.call(this, {eventType: event.type});
            event.stopPropagation();

            if (event.type === 'mousedown')
              event.preventDefault();
         }
    })
    .on('mousedown.demo click.demo', function(event){
      if (!event.isPropagationStopped() &&
         !event.isImmediatePropagationStopped() &&
         !event.isDefaultPrevented()){
           showEventMessage.call(this, {eventType: event.type, 
                                  suffix: '#2<br/>' });
        }
    })
    .on('selectstart.demo', function(event){
      return false;
    })
});

If we run this and click on the button, we get:

mousedown: INPUT
click: INPUT

Totally normal. It’s a click event, not a click.demo event. The namespace is not part of the event—it’s just how jQuery manages events. We can more easily control event by assigning them namespaces, even when using anonymous functions. Let’s add to the top of the chain a new click.test event handler.

.on('click.test', function(event){
      showEventMessage.call(this,
                           { eventType: 'test ' + event.type
                           })
    })

Upon clicking the button:

mousedown: INPUT
test click: INPUT
click: INPUT

Compare to the previous input. It’s pretty much what you’d expect. If we turn off event handlers for click.demo (.off('click.demo'), we end up seeing:

mousedown: INPUT
test click: INPUT
test click: DIV

And if we switch it to remove everything from demo namespace:

  .off('.demo')

It yields:

test click: INPUT
test click: DIV

1.4 DELEGATION

PluralSight: Advanced Techniques in JavaScript and jQuery

A lot of information really fast. Time to paraphrase.

The tutorial’s previous chapter discussed attaching events further up the DOM (whether someplace we specify or the document object by default). For example, let’s say we attach an event handler to a selected element, an li. If you do something like this:

$('li').bind('click', function(event){
  // stuff
});

.bind() will attach a listener to each li in the list. For every li, there’s a copy of this function in memory! Also, any other lis added dynamically will not have the function event handler associated with them.

If we used .live() instead we wouldn’t have either of these issues. Yet, again, the tutorial stresses that we shouldn’t be using .live() SO STOP TEACHING ME HOW TO USE THIS THING YOU DON’T WANT ME TO USE! Depending on how the .on() method is called, jQuery will treat it like bind or delegate–as discussed before, we’d use the optional parameter for the selector where we want the handler attached. For instance:

$('ul').on('click', 'li', function(event){
   // stuff
});

This is the reverse of what I expected based on the last chapter’s explanation, at least as far as syntax goes. When you click on the li, it bubbles up to the ul, which is where it’s processed. This works for static AND dynamic content. And now the syntax makes more sense—because we’re not attaching it to an element that doesn’t exist yet. We’re attaching it to a parent that does exist, inside which dynamic elements will appear. Also, in this example, the event will only bubble up one level, rather than continue up the DOM chain. The further separated the targeted and delegated elements are from one another, the greater the possible delay in handling their events.

1.5 CUSTOM EVENTS

PluralSight: Advanced Techniques in JavaScript and jQuery

Using jQuery we can also create custom events and “internally defined objects” – a phrase that means nothing to me so far. The custom events are similar to click or dblclick–they work the same. Most things we can think of already have events associated with them, but apparently, I may become creative enough that I want to invent more. Also they can be for events that are not part of the DOM.

These are triggered using .trigger(). It can be used both for our custom events, as well as simulating user interactions with the DOM, such as clicking.

For the tutorial, the event they’d like to trigger is the user clicking on something three times. We’ve removed all the previous handlers, and here’s what our code looks like now:

$(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')
    .on('click', function(event){
      showEventMessage.call(this,{
        eventType: event.type
      });
  });
});

The event handler that’s there is to test that regular events are still working. If you click on clickable you get:

click: SPAN
click: P
click: DIV

Now things begin to get more complicated. How will we keep track of each click? We’ll make a counter. But that could potentially mean dozens of counters. Maybe even millions. And the user may never click on anything. So, instead of creating them up front, we can create them as needed—when the user clicks on something.

“The best place to keep data associated with an object is in the data object that jQuery exposes with the data method.”

Yikes. There’s so much there. I’ve certainly seen data-* attributes floating around, but I’ve never used them.

“We can put any kind of information in this object that our application needs.”

Well, that seems cool. Here’s the code with my notes:

$('.clickable')
    .on('click', function(event){
      // yes, we can make variables that being with a $
      // and in this case, we're making a variable that is a reference to the jQuery version of `this`
      // and $this is ultimately the element that was clicked, that triggered the event.
      var $this = $(this),
          // now we're declaring another variable by using this comma shorthand so we don't have to add `; var`
          // if clickcount exists already, get it. otherwise, 0. and then add 1 to whichever those numbers is.
          clickCount = ($this.data('clickcount') || 0 ) + 1;
      // this sets the data-clickcount to the clickCount value
      // the data persists across multiple executions of the event handler because it's associated with
      // the DOM element, not with the handler function. 
      $this.data('clickcount', clickCount);

      showEventMessage.call(this,{
        // now we prepend the clickCount to the event type. i was pretty confused by
        // '' + clickCount // it turns out it could just be written
        // clickCount
        eventType: '' + clickCount + ') ' + event.type
      });
  });

Let’s test it. I’ll click on something four times. The result:

1) click: SPAN
1) click: P
1) click: DIV
2) click: SPAN
2) click: P
2) click: DIV
3) click: SPAN
3) click: P
3) click: DIV
4) click: SPAN
4) click: P
4) click: DIV

That works as expected. Now I’m going to click on three different things once.

1) click: INPUT
1) click: DIV
1) click: P
2) click: DIV
1) click: SPAN
1) click: P
3) click: DIV

Now we can see as the events bubble up the DOM and how there’s multiple counters at work here. When I click the input, it’s the first time I click on the div also. When I click on the P, it’s the second time I click on the div. And when I click on the span, it’s the first time I click on its p, and the third time I click on the div.

This is fun, but now we need to make something happen when the click count reaches 3.

if (clickCount === 3)
          // trigger the custom `click3` event that we made up
          $this.trigger('click3')

Okay, so we trigger the custom event, but where is it defined? How do we make it work? Easy: we don’t need to! In the same way that when you click a click event is triggered, this has triggered a click3 event and off it goes, bubbling up the DOM chain for us to do whatever we want with. So now we just create an event handler and when the click3 shows up, it’ll do its thing. Here’s our code:

$('.clickable')
    .on('click', function(event){
      // yes, we can make variables that being with a $
      // and in this case, we're making a variable that is a reference to the jQuery version of `this`
      // and $this is ultimately the element that was clicked, that triggered the event.
      var $this = $(this),
          // now we're declaring another variable by using this comma shorthand so we don't have to add `; var`
          // if clickcount exists already, get it. otherwise, 0. and then add 1 to whichever those numbers is.
          clickCount = ($this.data('clickcount') || 0 ) + 1;
      // this sets the data-clickcount to the clickCount value
      // the data persists across multiple executions of the event handler because it's associated with
      // the DOM element, not with the handler function. 
      $this.data('clickcount', clickCount);

      showEventMessage.call(this,{
        // now we prepend the clickCount to the event type. i was pretty confused by
        // '' + clickCount // it turns out it could just be written
        // clickCount
        eventType: '' + clickCount + ') ' + event.type });

        if (clickCount === 3)
          // trigger the custom `click3` event that we made up
          $this.trigger('click3');
      })

    .on('click3', function(event){
    // if we don't stopPropagation, it'll fire for clickable parent elements too, even when it's processed more than 3 clicks
    event.stopPropagation();
    showEventMessage.call(this, {eventType: event.type});
    $(this).addClass('highlight');
  });

And if I click on something three times.

1) click: INPUT
1) click: DIV
2) click: INPUT
2) click: DIV
3) click: INPUT
click3: INPUT
3) click: DIV
click3: DIV

So now we know how to create custom events and apply them to objects in the DOM. Now we’re going to apply custom event to objects that are not part of the DOM. Say wha?

The example the tutorial uses is that we’ll create records in the background and trigger something when the process completes. The instructor reminds us that there’s various ways we could do this, but we’re here to learn about custom events, not to figure out the best way to accomplish this task, you insufferable know-it-alls.

First we’ll create an internal object. Easy.

internalObject = { records: [], maxCount: 5 };

And then we create the loadRecord function:

loadRecord = function(){
    var id = internalObject.records.length;
    if (id < internalObject.maxCount){
      // generate and push a new object into the array. 
      // it'll contain a record no. and a value.
      internalObject.records.push({
        'description': 'Record id' + id,
        value: Math.floor(Math.random() * 5000)
      });
      // then it'll pause before processing the next record
      setTimeout(loadRecord, Math.floor(Math.random() * 1000));
    } else {
      // otherwise (if maxCount met), trigger 'recordsloaded' event
      // we turn the internalObject into a jQuery object by sticking in that jQuery parens thing. $(likethis). 
      // jQuery then returns a standard jQuery object, the same as if we'd used a selector. but instead of referencing
      // a DOM object, it references an internal object variable. 
      $(internalObject).trigger('recordsloaded')
    }
  ```

And then we call that function when the button is clicked.

```javascript
        if ($this.attr('type') === 'button')
          loadRecord();

And when we trigger recordsloaded, here’s the handler:

$(internalObject)
    // when the recordsloaded event is triggered on the internalObjects
    .on('recordsloaded', function(event){
      // do the normal 'hey something is triggered, print stuff about it'  
      showEventMessage.call(this, {eventType: event.type});
        // and then print each of the records
        $.each(internalObject.records, function(){
          $('#Messages').append(' -- ' + this.description + ': ' + this.value + '<br />');
        });
    });

Before running this code, I’m just going to show the whole thing so we can see where everything belongs.

$(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);
  },
  // a new variable, containing the records array and a maxCount of 5 for simple tests.
  internalObject = { records: [], maxCount: 5 },
  // a new function
  loadRecord = function(){
    var id = internalObject.records.length;
    if (id < internalObject.maxCount){
      // generate and push a new object into the array. 
      // it'll contain a record no. and a value.
      internalObject.records.push({
        'description': 'Record id' + id,
        value: Math.floor(Math.random() * 5000)
      });
      // then it'll pause before processing the next record
      setTimeout(loadRecord, Math.floor(Math.random() * 1000));
    } else {
      // otherwise (if maxCount met), trigger 'recordsloaded' event
      // we turn the internalObject into a jQuery object by sticking in that jQuery parens thing. $(likethis). 
      // jQuery then returns a standard jQuery object, the same as if we'd used a selector. but instead of referencing
      // a DOM object, it references an internal object variable. 
      $(internalObject).trigger('recordsloaded')
    }
  }

  $('.clickable')
    .on('click', function(event){
      // yes, we can make variables that being with a $
      // and in this case, we're making a variable that is a reference to the jQuery version of `this`
      // and $this is ultimately the element that was clicked, that triggered the event.
      var $this = $(this),
          // now we're declaring another variable by using this comma shorthand so we don't have to add `; var`
          // if clickcount exists already, get it. otherwise, 0. and then add 1 to whichever those numbers is.
          clickCount = ($this.data('clickcount') || 0 ) + 1;
      // this sets the data-clickcount to the clickCount value
      // the data persists across multiple executions of the event handler because it's associated with
      // the DOM element, not with the handler function. 
      $this.data('clickcount', clickCount);

      showEventMessage.call(this,{
        // now we prepend the clickCount to the event type. i was pretty confused by
        // '' + clickCount // it turns out it could just be written
        // clickCount
        eventType: '' + clickCount + ') ' + event.type });

        if (clickCount === 3)
          // trigger the custom `click3` event that we made up
          $this.trigger('click3');

        if ($this.attr('type') === 'button')
          loadRecord();
      })

    .on('click3', function(event){
    // if we don't stopPropagation, it'll fire for clickable parent elements too, even when it's processed more than 3 clicks
    event.stopPropagation();
    showEventMessage.call(this, {eventType: event.type});
    $(this).addClass('highlight');
  }); // end clickable selected

  $(internalObject)
    // when the recordsloaded event is triggered on the internalObjects
    .on('recordsloaded', function(event){
      // do the normal 'hey something is triggered, print stuff about it'  
      showEventMessage.call(this, {eventType: event.type});
        // and then print each of the records
        $.each(internalObject.records, function(){
          $('#Messages').append(' -- ' + this.description + ': ' + this.value + '<br />');
        });
    });


}); // end jQuery on document ready

When we click the button (and wait a few seconds)

1) click: INPUT
1) click: DIV
recordsloaded: unknown
-- Record id0: 3546
-- Record id1: 2289
-- Record id2: 2503
-- Record id3: 4880
-- Record id4: 3957

So everything is working as expected. Right down to the recordsloaded: unknown, which is what we wrote in line 16 in cases where eventTarget.nodeName is undefined. If we click it again, without delay we get:

1) click: INPUT
1) click: DIV
recordsloaded: unknown
-- Record id0: 3546
-- Record id1: 2289
-- Record id2: 2503
-- Record id3: 4880
-- Record id4: 3957
2) click: INPUT
recordsloaded: unknown
-- Record id0: 3546
-- Record id1: 2289
-- Record id2: 2503
-- Record id3: 4880
-- Record id4: 3957
2) click: DIV

See, it’s the same five records from the first time we clicked. Why? And since they’re already in the array, we don’t need to go through the delayed part of re-generating them. Also, you can see the clicks are counted from the previous click, and we can also see where in the process the custom event is triggered: after the click event on the button (input) is handled but before the click event on the div is handled. It’s treated as just another event it needs to handle in order.

1.6 EVENT PARAMETERS

PluralSight: Advanced Techniques in JavaScript and jQuery

We’re getting down to the last section of knowing everything there is to know about whatever it is we’re learning. In short: we can pass additional data as parameters via .on() and .trigger() methods, or we can specify it when we attach the handler. The data can be in the form of an array or object. Cleaning up the previous section’s code a bit, here’s what we’re beginning with:

$(function(){
  var showEventMessage = function(options){
    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
    }, options);
    var message = options.eventType + ': ' +
        (options.eventTarget.nodeName || 'unknown') +
        options.suffix;
    $('#Messages').append(message);
  },
  internalObject = { records: [], maxCount: 5 },
  loadRecord = function(){
    var id = internalObject.records.length;
    if (id < internalObject.maxCount){
      internalObject.records.push({
        'description': 'Record id' + id,
        value: Math.floor(Math.random() * 5000)
      });
      setTimeout(loadRecord, Math.floor(Math.random() * 1000));
    } else {
      $(internalObject).trigger('recordsloaded')
    }
  }

  $('.clickable')
    .on('click', function(event){
      var $this = $(this),
          clickCount = ($this.data('clickcount') || 0 ) + 1;
      $this.data('clickcount', clickCount);

      showEventMessage.call(this,{
        eventType: '' + clickCount + ') ' + event.type });

        if (clickCount === 3)
          $this.trigger('click3');

        if ($this.attr('type') === 'button')
          loadRecord();
      })

    .on('click3', function(event){
    event.stopPropagation();
    showEventMessage.call(this, {eventType: event.type});
    $(this).addClass('highlight');
  }); // end clickable selected

  $(internalObject)
    .on('recordsloaded', function(event){
      showEventMessage.call(this, {eventType: event.type});
        $.each(internalObject.records, function(){
          $('#Messages').append(' -- ' + this.description + ': ' + this.value + '<br />');
        });
    });


}); // end jQuery on document ready

Here’s our new variable:

  notifyObject = $({ nodeName: 'INTERNAL' }),

It’s a jQuery object, which has an object inside it with a single k/v pair. In the last section, where it shows unknown because we don’t have a node name property, all we have to do is somehow extend it with a node name property and then it’ll show that instead.

Next, we change

      $(internalObject).trigger('recordsloaded')

to

      $(notifyObject).trigger('recordsloaded')

The idea being that we’ll make a single object from which we can attach all custom events in the future. And that would be notifyObject I guess! We have to make sure the handler is written targeting the notifyObject, not the internalObject. The only change is on the first line where:

  $(internalObject)
    .on('recordsloaded', function(event){
      showEventMessage.call(this, {eventType: event.type});
        $.each(internalObject.records, function(){
          $('#Messages').append(' -- ' + this.description + ': ' + this.value + '<br />');
        });
    });

becomes

  $(notifyObject)
    .on('recordsloaded', function(event){
      showEventMessage.call(this, {eventType: event.type});
        $.each(internalObject.records, function(){
          $('#Messages').append(' -- ' + this.description + ': ' + this.value + '<br />');
        });
    });

And when we click the button, the output now is:

1) click: INPUT
1) click: DIV
recordsloaded: INTERNAL
-- Record id0: 1233
-- Record id1: 4280
-- Record id2: 2084
-- Record id3: 97
-- Record id4: 3656

I get it. But also I’m so confused. It’s time to trace the path of things so I can get a handle on the flow.

/****
1. I click on the button
****/

$(function(){
/****
9. add a new message with details about the click. 
****/   
  var showEventMessage = function(options){
    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
    }, options);
    var message = options.eventType + ': ' +

/****
10. and this is where the nodeName shows up from earlier
****/   

        (options.eventTarget.nodeName || 'unknown') +
        options.suffix;
/****
11. and the message is appended. the end!
****/   

    $('#Messages').append(message);
  },
  internalObject = { records: [], maxCount: 5 },
/****
6. ...generating it on the notifyObject object and sending along the nodeName
****/       
  notifyObject = $({ nodeName: 'INTERNAL' }),
/****
4. Which generates and loads records until the max is hit
****/      
  loadRecord = function(){
    var id = internalObject.records.length;
    if (id < internalObject.maxCount){
      internalObject.records.push({
        'description': 'Record id' + id,
        value: Math.floor(Math.random() * 5000)
      });
      setTimeout(loadRecord, Math.floor(Math.random() * 1000));
    } else {
/****
5. Which triggers a recordsloaded event. No different than a click!
****/       
      $(notifyObject).trigger('recordsloaded');
    }
  }

/****
2. on click, all this stuff happens
****/
  $('.clickable')
    .on('click', function(event){
      var $this = $(this),
          clickCount = ($this.data('clickcount') || 0 ) + 1;
      $this.data('clickcount', clickCount);

      showEventMessage.call(this,{
        eventType: '' + clickCount + ') ' + event.type });

        if (clickCount === 3)
          $this.trigger('click3');
/****
3. Most particularly, this, where we loadRecord();
****/
        if ($this.attr('type') === 'button')
          loadRecord();
      })

    .on('click3', function(event){
    event.stopPropagation();
    showEventMessage.call(this, {eventType: event.type});
    $(this).addClass('highlight');
  }); // end clickable selected

  $(notifyObject)

/****
7. And here's where we handle the recordsloaded event
****/ 

    .on('recordsloaded', function(event){
/****
8. call the showEventMessage as a method on the notifyObject, and the event type as a parameter
****/     
      showEventMessage.call(this, {eventType: event.type});
      $.each(internalObject.records, function(){
        $('#Messages').append(' -- ' + this.description + ': ' + this.value + '<br />');
      });
    });


}); // end jQuery on document ready

Okay, makes sense. Moving on, right now we’re accessing the internalObject by just calling it—which we can do because it’s in this file. But the new scenario is what if we couldn’t do that? What if it was some external process? We’re going to pretend that only the loadRecord function is able to access the internalObject. All we have to do is make this:

$(notifyObject).trigger('recordsloaded');

into this:

$(notifyObject).trigger('recordsloaded', internalObject);

And then, heading over to the event handler, we’ll change

    .on('recordsloaded', function(event){

to include the argument theObject, to be

    .on('recordsloaded', function(event, theObject){

which will contain the parameters sent over from the trigger. The next step would be to use that object passed in as a parameter instead of calling internalObject directly:

      $.each(internalObject.records, function(){

becomes

      $.each(theObject.records, function(){

When we run it, it still works, but now they’re coming via the parameter, not the internal variable.

For standard events, we don’t have the same sort of control over what’s being sent. So we can’t send a parameter. How do we do it? We can include it as an optional second parameter:

$('.clickable')
    .on('click', function(event){

becomes

  $('.clickable')
    .on('click', internalObject, function(event){//...};

And while we’re at it, we’ll extend the handler to tell us how many records there are:

if ($this.attr('type') === 'button')
          loadRecord();

becomes

        if ($this.attr('type') === 'button'){
          var theObject = event.data || { records: [] };
          $('#Messages')
            .append('Record count: ' + theObject.records.length + '<br/>');
          loadRecord();
        }

The data that was passed in as a parameter is attached to the event object as the data property. When we included the internalObject with the on() method, it told jQuery to pass that data along with the event every time the handler function is called. jQuery adds the data to the event parameter that is already passed to our handler. jQuery extends the single parameter object that our handler expects. . .The parameter count didn’t change. It’s just up to us to make use of the new data property of the event object. If we don’t include the internalObject as the second parameter, the event.data will be undefined.

To test, click the button. The record is 0. After the records load, click the button again, and it shows a record count of 5. If you refresh and click twice slowly, it’ll show how many records are loaded up to this point, followed by the records loaded message twice.

1.7 MY TURN

Okay, so now I’m going to try to recreate this thing. From scratch. Before I start, I need to describe what it’s going to be: two boxes that should look like this:

theirs

Things that should be clickable are the title, both ps, the word clickable and of course the button. When you click, mousedown, or double click on something it should print a line in the right box saying which number occurrence of the event it is, what sort of event it is, and also what the element is that the event happened on. You know, nevermind—it shouldn’t show the mousedown or dblclick. If something is clicked three times, a custom event for triple clicking should occur which will highlight the element, and make a new message with the type ‘click3’. There should be an internal object that produces records with random numbers as their content. They should be created at random intervals to simulate a remote connection. When they’re created they should be put into an array and kept there, and there shouldn’t be allowed to be more than five. And when they hit five, a custom event should be triggered that lets us know the records are loaded. Rather than the element name, it should say INTERNAL so we know it’s an internal element. And then it should print out those records. Oh, right, and this should happen when we hit the button.

Okay, so I made it close enough that I’m not going to cry.

<body>
  <div id="left" class="box">
    <h5 class="clickable">Work Area</h5>
    <hr />
    Click Me
    <p class="clickable">Click me, too!</p>
    <p>This is some more text with an embedded <span class="clickable">clickable</span> element.
  </div>
  <div id="right" class="box">
    <h5>Messages</h5>
    <hr />
  </div>
</body>
h5 {
  padding: 0;
  margin: 0;
}

.box {
  position: absolute;
  width: 300px;
  height: 200px;
  border: 1px solid black;
  padding: 5px;
  overflow-y: auto;
  background: radial-gradient(#fff,#ccc)
}

#right {
  left: 340px;  
}

my-attempt

$(function(){

  var addMessage = function(e){
    var type = e.type,
        target = e.target.nodeName;

    $('#messages').append(type + ": " + target + "<br />")
    console.log(e)
  }

  $('.clickable')
    .on('click dblclick mousedown', function(e){
      addMessage(e);
    });
}); // end document ready

At this point when you click, it shows the event and the target, but it’s not showing the bubbling up. And now I made these changes and it began working. Things I did include adding some more clickable spots, making clickable things have a pointer cursor, and then switching the target from e.target.nodeName to e.currentTarget.nodeName–which I guess is the different between the element that began the chain of events vs the element that’s currently the event is currently at.

<body>
  <div id="workarea" class="clickable">
    <h5 class="clickable">Work Area</h5>
    <hr />
    Click Me
    <p class="clickable">Click me, too!</p>
    <p class="clickable">This is some more text with an embedded <span class="clickable">clickable</span> element.
  </div>
  <div id="messages">
    <h5>Messages</h5>
    <hr />
  </div>
</body>
h5 {
  padding: 0;
  margin: 0;
}

#workarea, #messages {
  position: absolute;
  width: 300px;
  height: 200px;
  border: 1px solid black;
  padding: 5px;
  overflow-y: auto;
  background: radial-gradient(#fff,#ccc)
}

#messages {
  left: 340px;  
}

.clickable {
  cursor: pointer;
}
$(function(){

  var addMessage = function(e){
    var type = e.type
        target = e.currentTarget.nodeName
        console.log(e)

    $('#messages').append(type + ": " + target + "<br />")
  }

  $('.clickable')
    .on('click dblclick mousedown', function(e){
      addMessage(e);
    });
}); // end document ready

So the next part is that we have to count the number of event occurrences for each element. So we need a counter for each element. But I remember writing that it’d be wasteful to set them in advance, so we shouldn’t set them until something is clicked. I just have no idea how to do this. So, I’m cheating—THIS ROUND. The way it’s done before is that we send the clicked element into the function and then extend it.

So I switch

addMessage(e);

to

      addMessage.call(this, {type: e.type, target: e.currentTarget.nodeName});

And this:

var addMessage = function(e){
    var type = e.type,
        target = e.currentTarget.nodeName;
    $('#messages').append(type + ": " + target + "<br />")
  }
````

becomes:

```javascript
  var addMessage = function(e){
    var type = e.type,
        target = e.target;
    $('#messages').append(type + ": " + target + "<br />")
  }

Now we get the same results as a few steps ago, but we’re using .call() instead of just calling the function directly. Now I edit it to this:

var addMessage = function(e){
    var type = e.type,
        target = e.target.nodeName;
    $('#messages').append(type + ": " + target + "<br />")
  }

  $('.clickable')
    .on('click dblclick mousedown', function(e){
      addMessage.call(this, {type: e.type, target: this});
    });

Where, when I call addMessage, I switch the target to this, being the object that was clicked on. That may be a mistake since we’re already calling the function as a method on this, which I think means its context would be this regardless. Okay, I’m correct…I just made this change, where I don’t send this in as a parameter, and assume that this is already what I need it to be.

var addMessage = function(e){
    var type = e.type,
        target = this.nodeName;
    $('#messages').append(type + ": " + target + "<br />")
  }

  $('.clickable')
    .on('click dblclick mousedown', function(e){
      addMessage.call(this, {type: e.type});
    });

It works just the same. So now, I’ve gotten the object I clicked on sent off to a function, like sending a child to boarding school. It’s time to see if I can extend it. This would be done using the jQuery .extend(). The way it works is that you basically can add two things together. So you do $.extend(addStuffToMe,AddThisStuff); and the stuff you add has to be in the form of an object. That’s important because even though I knew it, I was treating it like a function and becoming exasperated that simple things like a = 1 weren’t working. So, now this is working and it prints 1 four times in the console:

  var addMessage = function(e){
    $.extend(this,{
      counter: 1
    })
    console.log(this.counter);
    var type = e.type,
        target = this.nodeName;
    $('#messages').append(type + ": " + target + "<br />")
  }

So, now let’s see if we can take this to the next level. Now we’re really getting somewhere. I’ve got a counter working. Here’s the code:

var addMessage = function(e){
    $.extend(this,{
      counter: this.counter || 0
    })
    this.counter++;
    console.log(this.counter);
    var type = e.type,
        target = this.nodeName;
    $('#messages').append(this.counter + ") " + type + ": " + target + "<br />")
  } 

Not complicated, really. I’m not sure if the output is correct. Here it is when I double-click on the word clickable:

1) mousedown: SPAN
1) mousedown: P
1) mousedown: DIV
2) click: SPAN
2) click: P
2) click: DIV
3) mousedown: SPAN
3) mousedown: P
3) mousedown: DIV
4) click: SPAN
4) click: P
4) click: DIV
5) dblclick: SPAN
5) dblclick: P
5) dblclick: DIV

So, the counter increments every time an event is triggered on an element. What’s good is that we can see three individual counters incrementing separately here. And not just incrementing, but also maintaining their values from event to event. I like this test even better, where I first click on the span, and then on the button:

1) mousedown: SPAN
1) mousedown: P
1) mousedown: DIV
2) click: SPAN
2) click: P
2) click: DIV
1) mousedown: BUTTON
3) mousedown: DIV
2) click: BUTTON
4) click: DIV

Now we can see that things really are happening. Now I’m recalling that there’s more to it than this. We should only be tracking CLICK events, and at 3, we should highlight the element that’s reached 3. Okay, I just added that to my instructions. So, let’s see if we can make that happen. Here’s the new code, where we check if the event is a click and only increment it if so.

var addMessage = function(e){
    $.extend(this,{
      counter: this.counter || 0
    })
    var type = e.type,
        target = this.nodeName;  
    if (type === 'click')
      this.counter++;
    $('#messages').append(this.counter + ") " + type + ": " + target + "<br />")
  }

Results of the same test:

0) mousedown: SPAN
0) mousedown: P
0) mousedown: DIV
1) click: SPAN
1) click: P
1) click: DIV
0) mousedown: BUTTON
1) mousedown: DIV
1) click: BUTTON
2) click: DIV

We can see that the click is the only thing incrementing the counter, however, it’s misleading to see all those 0 counts. Let’s see if we can remove them. Gotcha:

var addMessage = function(e){
    $.extend(this,{
      counter: this.counter || 0
    })
    var type = e.type,
        target = this.nodeName,
        prefix = "";
    if (type === 'click')
      prefix = ++this.counter + ") ";
    $('#messages').append(prefix + type + ": " + target + "<br />")
  }

Results:

mousedown: SPAN
mousedown: P
mousedown: DIV
1) click: SPAN
1) click: P
1) click: DIV
mousedown: BUTTON
mousedown: DIV
1) click: BUTTON
2) click: DIV

Now let’s clean this up by only showing clicks.

var addMessage = function(e){
    $.extend(this,{
      counter: this.counter || 0
    })
    var type = e.type,
        target = this.nodeName,
        prefix = "";
    if (type === 'click'){
      prefix = ++this.counter + ") ";
      $('#messages').append(prefix + type + ": " + target + "<br />")
    }
  }

Result:

1) click: SPAN
1) click: P
1) click: DIV
1) click: BUTTON
2) click: DIV

Now it’s time to make the triple-click highlighter. Here’s the CSS:

.highlight {
  background-color: yellow;
}

And here’s the javascript:

    if (type === 'click'){
      prefix = ++this.counter + ") ";
      $('#messages').append(prefix + type + ": " + target + "<br />")

      if (this.counter >= tripleClick)
        $(this).addClass('highlight')
    }

So this works well, but it’s not a custom event. It’s just a conditional. Oops. Let’s see if we can make a custom event somehow. As I recall, you don’t have to “create” a custom event—you just have to trigger it. And then have a handler for it. So first I’m going to make the handler because that sounds like fun. So, first I removed everything we have so far about the triple click, and I moved the exciting bits over to a new handler:

  $('.clickable')
    .on('click dblclick mousedown', function(e){
      addMessage.call(this, {type: e.type});
    })
    .on('trplclick', function(e){
      console.log("triple click!")
      $(this).addClass('highlight')
    });

Now the tougher bits. How do I make something trigger a custom handler? I began to type in Google that very question, and when I got to the word “trigger” I remembered it’s probably done using jQuery’s trigger. So…

    if (type === 'click'){
      prefix = ++this.counter + ") ";
      $('#messages').append(prefix + type + ": " + target + "<br />");

      if (this.counter >= 3)
        $(this).trigger('trplclick');
    }

And it works! I just remembered it’s supposed to send a message that says the type of thing is click3. So now I’ve got to do that.

.on('trplclick', function(e){
      $(this).addClass('highlight')
      addMessage.call(this, {type: e.type});
    });

And now I’m changing trplclick to click3 and here’s the whole code:

$(function(){

  var addMessage = function(e){
    $.extend(this,{
      counter: this.counter || 0
    })

    var type = e.type,
        target = this.nodeName,
        prefix = "";

    if (type === 'click'){
      prefix = ++this.counter + ") ";
      $('#messages').append(prefix + type + ": " + target + "<br />");

      if (this.counter >= 3)
        $(this).trigger('click3');
    }

    if (type === 'click3')
      $('#messages').append(prefix + type + ": " + target + "<br />");
  }

  $('.clickable')
    .on('click dblclick mousedown', function(e){
      addMessage.call(this, {type: e.type});
    })
    .on('click3', function(e){
      $(this).addClass('highlight')
      addMessage.call(this, {type: e.type});
    });
}); // end document ready

Okay, so now it’s internal object time.

someObject = {
    records: [],
    maxRecords: 5,
    makeRecords: function(){
      record = Math.floor(Math.random() * 1e4);
      recordsCount = this.records.length;

      if (recordsCount < 5){
        setTimeout(function(){
          someObject.records.push({id: recordsCount, content: record});
          someObject.makeRecords();
        }, Math.random()*1e3) 
      }
    } // end makeRecords
  };

  someObject.makeRecords();

This works. I’m not really sure what to do with this, though:

  $('button')
    .on('click', function(e){
      someObject.makeRecords();
      addMessage.call(someObject.records);
    });

Because I get an error back because e.type is undefined. I don’t see a real reason to send a type along, as I feel that’s a somewhat hacky solution. There’s not really a type, is there? Well, let’s get back to that in a moment. Here’s what I’m thinking for printing the records:

makeRecords: function(){
      record = Math.floor(Math.random() * 1e4);
      recordsCount = this.records.length;

      if (recordsCount < 5){
        setTimeout(function(){
          someObject.records.push({id: recordsCount, content: record});
          someObject.makeRecords();
        }, Math.random()*1e3) 
      } else {
        this.printRecords();
      }
    }, // end makeRecords
    printRecords: function(){
      $.each(someObject.records, function(k,v){
        console.log(v.id + ": " + v.content)
      })
    }

I don’t know how I knew there was an .each function in jQuery, except that I miss Ruby, and that’s the sort of thing that would make sense in Ruby—except that in Ruby I’d call it as a method. This seems a roundabout implementation, because it’s just a standalone function, but it’s cleaner than doing a for loop, though I suspect the performance isn’t as good. Anyhow, I learned something new. And now I just need to figure out how to get them printed as a message in a way that I feel comfortable with. Oh, right…instead of console.log, just going straight to the source:

      $('#messages').append(prefix + type + ": " + target + "<br />");

So it’s working, however, the goal was to make it trigger a custom event based on the internal object, not simply make a conditional do whatever. I’ll show you what isn’t working:

console.log("about to trigger");
$(someObject).trigger('maxObjectsCreated');
// …
$(someObject)
    .on('maxRecordsCreated', function(e){
      console.log("ok")
      someObject.printRecords();
    });

The console.log bit is working. If I compare it to the original, I’ve implemented things far more amateurishly, but here’s the similar parts:

$(notifyObject).trigger('recordsloaded', internalObject);
// …
$(notifyObject)
    .on('recordsloaded', function(event, theObject){
      showEventMessage.call(this, {eventType: event.type});
      $.each(theObject.records, function(){
        $('#Messages').append(' -- ' + this.description + ': ' + this.value + '<br />');
      });
    });

Do you see the problem? It’s that in one spot I call it maxRecordsCreated and on the other, maxObjectsCreated. Nice job!

Okay, I finished all the instructions. I didn’t do it the same as the original. But I did it. The real question now is: what’s more important, imitating the concepts of the tutorial alone, or imitating everything about the tutorial? Ugh…maybe I need to imitate all of it. Let’s look at my current final code, followed by figuring out how to make it better.

$(function(){
  var addMessage = function(e){
    $.extend(this,{
      counter: this.counter || 0
    })
    var type = e.type,
        target = this.nodeName,
        prefix = "";

    if (type === 'click'){
      prefix = ++this.counter + ") ";
      $('#messages').append(prefix + type + ": " + target + "<br />");

      if (this.counter >= 3)
        $(this).trigger('click3');
    }

    if (type === 'click3')
      $('#messages').append(prefix + type + ": " + target + "<br />");

    if (type === 'maxRecordsCreated')
      target = "INTERNAL"
      $('#messages').append(prefix + type + ": " + target + "<br />");
  },

  someObject = {
    records: [],
    maxRecords: 5,
    makeRecords: function(){
      record = Math.floor(Math.random() * 1e4);
      recordsCount = this.records.length;

      if (recordsCount < 5){
        setTimeout(function(){
          someObject.records.push({id: recordsCount, content: record});
          someObject.makeRecords();
        }, Math.random()*1e2) 
      } else {
        $(this).trigger('maxRecordsCreated');
      }
    }, // end makeRecords
    printRecords: function(){
      $.each(someObject.records, function(k,v){
        $('#messages').append("    #" + v.id + ": " + v.content + "<br />");
      })
    }
  };

  $('.clickable')
    .on('click dblclick mousedown', function(e){
      addMessage.call(this, {type: e.type});
    })
    .on('click3', function(e){
      $(this).addClass('highlight')
      addMessage.call(this, {type: e.type});
    });

  $('button')
    .on('click', function(e){
      someObject.makeRecords();
    });

  $(someObject)
    .on('maxRecordsCreated', function(e){
      addMessage.call(this, {type: e.type});
      this.printRecords();
    });
}); // end document ready

Here’s the first thing, the use of $.extend(). In mine, it’s pretty straightforward. We take an existing jQuery object, and we extend it:

var addMessage = function(e){
    $.extend(this,{
      counter: this.counter || 0
    })

    var type = e.type,
        target = this.nodeName,
        prefix = "";

so now this existing jQuery object, which is a button or span or whatever the user’s clicked on, now has this counter property. Here’s the tutorial’s version:

var showEventMessage = function(options){
  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
    }, options); 

How is all this magical “if undefined, use this default” stuff happening? Like this. options is our e. We’re not doing anything with it at this juncture, but the tutorial is. The magic works like this: when you extend something, it takes the first parameter and then merges the second parameter with it—which overwrites the values any identical keys of the first. So what’s happening here is that we’re creating a new variable with default values, and then we’re merging the passed-in parameter that may or may not have the same keys. Essentially, like this:

a = { b: 1, c: 2, d: 3}

a = $.extend({
  c: "two",
  d: "three",
  e: "four"
}, a);

// a is { b: 1, c: 2,  d: 3,  e: "four" }

Why is the eventType default there? It was used in the tutorial to show that we’d replaced it with something. So now I’ve switched mine up to:

var addMessage = function(options){
    options = $.extend({
      counter: this.counter || 0,
      type: 'UNDEFINED',
      target: this.nodeName,
      prefix: ""
    }, options)

And the only bummer is that I have to write options.type instead of just type now. Etc. And it’s worth asking why their target defaults to this while mine to this.nodeName? Well, if I don’t do it like that, I end up with an reference to an object or something wonky. How do they deal with it? Well, if we’re being correct, the target shouldn’t be the nodeName—so they get the correct name to show up by referring to nodeName each time they need it. So this:

      $('#messages').append(options.prefix + options.type + ": " + options.target + "<br />");

would become:

      $('#messages').append(options.prefix + options.type + ": " + options.target.nodeName + "<br />");

And the next big difference is that I’ve got three conditionals based on the options.type we’re dealing with. They don’t. Somehow everything just works out for without changing anything. Well, let’s see where everything goes because I’m a bit overwhelmed by bits of code scattered everywhere. First, they define the message as a variable and then…I should be embarrassed because I kept repeating the same line of code each time for the message. Now it’s replaced with the variable.

Next, rather than the bunch of conditionals, it always just goes ahead and appends the message. Well, okay, so I can remove my conditionals—but where do they go, then? I don’t know. We’ll revisit that subject.

The next thing is that they divide the internalObject into two objects, one with two properties, the other with one property. Mine’s a single object filled with everything I could think of. Mine:

someObject = {
    records: [],
    maxRecords: 5,
    makeRecords: function(){
      record = Math.floor(Math.random() * 1e4);
      recordsCount = this.records.length;

      if (recordsCount < 5){
        setTimeout(function(){
          someObject.records.push({id: recordsCount, content: record});
          someObject.makeRecords();
        }, Math.random()*1e2) 
      } else {
        $(this).trigger('maxRecordsCreated');
      }
    }, // end makeRecords
    printRecords: function(){
      $.each(someObject.records, function(k,v){
        $('#messages').append("    #" + v.id + ": " + v.content + "<br />");
      })
    }
  };

Theirs:

internalObject = { records: [], maxCount: 5 }, 
notifyObject = $({ nodeName: 'INTERNAL' }),

After which loadRecord is a function. Next, it looks like while I have this simple “on click” bit of code, they’ve packed it full of stuff. Mine:

    .on('click dblclick mousedown', function(e){
      addMessage.call(this, {type: e.type});
    })

In theirs, they’re passing in data with the on function. Here’s the example from the jQuery documentation: .on( events [, selector ] [, data ], handler ). What’s the data? internalObject. That’s about it. Also, they’ve done away with the need for one of the conditionals because dblclick and mousedown aren’t even captured anymore! So now it looks like this:

.on('click', internalObject, function(e){
      addMessage.call(this, {type: e.type});
    })

The first thing they do is measure the click count. While I extended the clickable object with a clicks counter property, they use a data attribute. Oops. I don’t remember how to do any of that. How do they get the clickcount message? Sort of hacky-like:

      showEventMessage.call(this,{
        eventType: '' + clickCount + ') ' + event.type });

And it’s after this that we move the trigger for click3. So now I’ve made a first pass through the original, and I’ll take it from the top again. Just to show you where we’re at with the new code:

$(function(){
  var addMessage = function(options){
    options = $.extend({
      counter: this.counter || 0,
      type: 'UNDEFINED',
      target: this,
      prefix: ""
    }, options);

  var message = options.type + ": " + (options.target.nodeName || 'unknown') + "<br />"
 /*       
    if (options.type === 'click'){
      $('#messages').append(message);


    if (options.type === 'maxRecordsCreated'){
      options.target.nodeName = "INTERNAL";
      $('#messages').append(message);
    }
*/
    $('#messages').append(message);
  },

  internalObject = { records: [], maxRecords: 5 },
  notifyObject = $({ nodeName: "INTERNAL" }),

  makeRecords = function(){
      record = Math.floor(Math.random() * 1e4);
      recordsCount = internalObject.records.length;

      if (recordsCount < 5){
        setTimeout(function(){
          internalObject.records.push({id: recordsCount, content: record});
          internalObject.makeRecords();
        }, Math.random()*1e2) 
      } else {
        $(notifyObject).trigger('maxRecordsCreated', internalObject);
      }
    }; // end makeRecords

  /*someObject = {    
    printRecords: function(){
      $.each(someObject.records, function(k,v){
        $('#messages').append("    #" + v.id + ": " + v.content + "<br />");
      })
    }
  };*/

  $('.clickable')
    .on('click', internalObject, function(e){
      var clickCount = ($(this).data('clickcount') || 0) + 1;

      $(this).data('clickcount', clickCount);

      addMessage.call(this, {type: clickCount + ') ' + e.type});

      if (clickCount >= 3)
          $(this).trigger('click3');

      if ($(this).attr('type') === 'button')
          $(this).trigger('click3')

      $('button')
      .on('click', function(e){
        someObject.makeRecords();
      });
    })

    .on('click3', function(e){
      $(this).addClass('highlight')
      addMessage.call(this, {type: e.type});
    });

  $(notifyObject)
    .on('maxRecordsCreated', function(e, theInternalObject){
      addMessage.call(this, {type: e.type});
      $.each(theInternalObject.records, function(k,v){
        $('#messages').append("    #" + v.id + ": " + v.content + "<br />");
      })
    });
}); // end document ready

So, first I’ll remove the counter from the extended object at the beginning. Next, in makeRecords, the id is set as a variable…

From there I began having trouble reconciling the differences, so I went through and began changing line by line the differences, which generally was a case of name differences. And then, maybe I’ll just end this by giving my feelings overall: what a load of crap!

Onward to the next chapter.

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