官术网_书友最值得收藏!

Altering the journey – the event object

We have already seen one situation in which event bubbling can cause problems. To show a case in which .hover() does not help our cause, we'll alter the collapsing behavior that we implemented earlier.

Suppose we wish to expand the clickable area that triggers the collapsing or expanding of the style switcher. One way to do this is to move the event handler from the label, <h3>, to its containing <div> element. In Listing 3.9, we added a click handler to #switcher h3; we will attempt this change by attaching the handler to #switcher instead:

// Unfinished code
$(document).ready(function() {
 $('#switcher').click(function() {
    $('#switcher button').toggleClass('hidden');
  });
});

Listing 3.11

This alteration makes the entire area of the style switcher clickable to toggle its visibility. The downside is that clicking on a button also collapses the style switcher after the style on the content has been altered. This is due to event bubbling; the event is first handled by the buttons, then passed up through the DOM tree until it reaches the <div id="switcher"> element, where our new handler is activated and hides the buttons.

To solve this problem, we need access to the event object. This is a DOM construct that is passed to each element's event handler when it is invoked. It provides information about the event, such as where the mouse cursor was at the time of the event. It also provides some methods that can be used to affect the progress of the event through the DOM.

Tip

Event object reference

For detailed information about jQuery's implementation of the event object and its properties, see http://api.jquery.com/category/events/event-object/.

To use the event object in our handlers, we only need to add a parameter to the function:

$(document).ready(function() {
 $('#switcher').click(function(event) {
    $('#switcher button').toggleClass('hidden');
  });
});

Note that we have named this parameter event because it is descriptive, not because we need to. Naming it flapjacks or anything else for that matter would work just as well.

Event targets

Now we have the event object available to us as the variable event within our handler. The property event.target can be helpful in controlling where an event takes effect. This property is a part of the DOM API, but is not implemented in some older browser versions; jQuery extends the event object as necessary to provide the property in every browser. With .target, we can determine which element in the DOM was the first to receive the event. In the case of a click event, this will be the actual item clicked on. Remembering that this gives us the DOM element handling the event, we can write the following code:

// Unfinished code
$(document).ready(function() {
  $('#switcher').click(function(event) {
 if (event.target == this) {
      $('#switcher button').toggleClass('hidden');
    }
  });
});

Listing 3.12

This code ensures that the item clicked on was <div id="switcher">, not one of its sub-elements. Now, clicking on buttons will not collapse the style switcher, but clicking on the switcher's background will. However, clicking on the label, <h3>, now does nothing, because it too is a sub-element. Instead of placing this check here, we can modify the behavior of the buttons to achieve our goals.

Stopping event propagation

The event object provides the .stopPropagation() method, which can halt the bubbling process completely for the event. Like .target, this method is a basic DOM feature, but cannot be safely used as such in Internet Explorer 8 or older. As long as we register all of our event handlers using jQuery, though, we can use it with impunity.

We'll remove the event.target == this check we just added, and instead add some code in our buttons' click handlers:

$(document).ready(function() {
$('#switcher').click(function(event) {
    $('#switcher button').toggleClass('hidden');
  });
});
$(document).ready(function() {
  $('#switcher-default').addClass('selected');
 $('#switcher button').click(function(event) {
    var bodyClass = this.id.split('-')[1];
    $('body').removeClass().addClass(bodyClass);
    $('#switcher button').removeClass('selected');
    $(this).addClass('selected');
 event.stopPropagation();
  });
});

Listing 3.13

As before, we need to add a parameter to the function we're using as the click handler so we have access to the event object. Then, we simply call event.stopPropagation() to prevent any other DOM element from responding to the event. Now our click is handled by the buttons, and only the buttons; clicks anywhere else on the style switcher will collapse or expand it.

Preventing default actions

If our click event handler was registered on a link element (<a>) rather than a generic <button> element outside of a form, we would face another problem. When a user clicks on a link, the browser loads a new page. This behavior is not an event handler in the same sense as the ones we have been discussing; instead, this is the default action for a click on a link element. Similarly, when the Enter key is pressed while the user is editing a form, the submit event may be triggered on the form, but then the form submission actually occurs after this.

If these default actions are undesired, calling .stopPropagation() on the event will not help. These actions occur nowhere in the normal flow of event propagation. Instead, the .preventDefault() method will serve to stop the event in its tracks before the default action is triggered.

Tip

Calling .preventDefault() is often useful after we have done some tests on the environment of the event. For example, during a form submission, we might wish to check that required fields are filled in and prevent the default action only if they are not.

Event propagation and default actions are independent mechanisms; either of them can be stopped while the other still occurs. If we wish to halt both, we can return false at the end of our event handler, which is a shortcut for calling both .stopPropagation() and .preventDefault() on the event.

Delegating events

Event bubbling isn't always a hindrance; we can often use it to great benefit. One great technique that exploits bubbling is called event delegation. With it, we can use an event handler on a single element to do the work of many.

In our example, there are just three <button> elements that have attached click handlers. But what if there were many? This is more common than you might think. Consider, for example, a large table of information in which each row has an interactive item requiring a click handler. Implicit iteration makes assigning all of these click handlers easy, but performance can suffer because of the looping being done internally to jQuery, and because of the memory footprint of maintaining all the handlers.

Instead, we can assign a single click handler to an ancestor element in the DOM. An uninterrupted click event will eventually reach the ancestor due to event bubbling, and we can do our work there.

As an example, let's apply this technique to our style switcher (even though the number of items does not demand the approach). As seen in Listing 3.12 previously, we can use the event.target property to check what element is under the mouse cursor when the click event occurs.

$(document).ready(function() {
  $('#switcher').click(function(event) {
 if ($(event.target).is('button')) {
      var bodyClass = event.target.id.split('-')[1];
      $('body').removeClass().addClass(bodyClass);
      $('#switcher button').removeClass('selected');
      $(event.target).addClass('selected');
      event.stopPropagation();
    }
  });
});

Listing 3.14

We've used a new method here called .is(). This method accepts the selector expressions we investigated in the previous chapter and tests the current jQuery object against the selector. If at least one element in the set is matched by the selector, .is()returns true. In this case, $(event.target).is('button') asks whether the element clicked is a <button> element. If so, we proceed with the previous code, with one significant alteration: the keyword this now refers to <div id="switcher">, so every time we are interested in the clicked button, we must now refer to it with event.target.

Tip

.is() and .hasClass()

We can test for the presence of a class on an element with .hasClass(). The .is() method is more flexible, however, and can test any selector expression.

We have an unintentional side-effect from this code, however. When a button is clicked now, the switcher collapses, as it did before we added the call to .stopPropagation(). The handler for the switcher visibility toggle is now bound to the same element as the handler for the buttons, so halting the event bubbling does not stop the toggle from being triggered. To sidestep this issue, we can remove the .stopPropagation() call and instead add another .is() test. Also, since we're making the entire switcher <div> element clickable, we ought to toggle the hover class while the user's mouse is over any part of it:

$(document).ready(function() {
 $('#switcher').hover(function() {
    $(this).addClass('hover');
  }, function() {
    $(this).removeClass('hover');
  });
});
$(document).ready(function() {
  $('#switcher').click(function(event) {
 if (!$(event.target).is('button')) {
      $('#switcher button').toggleClass('hidden');
    }
  });
});
$(document).ready(function() {
  $('#switcher-default').addClass('selected');
  $('#switcher').click(function(event) {
    if ($(event.target).is('button')) {
      var bodyClass = event.target.id.split('-')[1];
      $('body').removeClass().addClass(bodyClass);
      $('#switcher button').removeClass('selected');
      $(event.target).addClass('selected');
    }
  });
});

Listing 3.15

This example is a bit overcomplicated for its size, but as the number of elements with event handlers increases, so does event delegation's benefit. Also, we can avoid some of the code repetition by combining the two click handlers and using a single if-else statement for the .is() test:

$(document).ready(function() {
  $('#switcher-default').addClass('selected');
  $('#switcher').click(function(event) {
 if ($(event.target).is('button')) {
      var bodyClass = event.target.id.split('-')[1];
      $('body').removeClass().addClass(bodyClass);
      $('#switcher button').removeClass('selected');
      $(event.target).addClass('selected');
 } else {
      $('#switcher button').toggleClass('hidden');
    }
  });
});

Listing 3.16

While our code could still use some fine tuning, it is approaching a state at which we can feel comfortable using it for what we set out to do. Nevertheless, for the sake of learning more about jQuery's event handling, we'll back up to Listing 3.15 and continue to modify that version of the code.

Tip

Event delegation is also useful in other situations we'll see later, such as when new elements are added by DOM manipulation methods (Chapter 5, Manipulating the DOM) or Ajax routines (Chapter 6, Sending Data with Ajax).

Using built-in event-delegation capabilities

Because event delegation can be helpful in so many situations, jQuery includes a set of tools to aid developers in using this technique. The .on() method we have already discussed can perform event delegation when provided with appropriate parameters:

$('#switcher').on('click', 'button', function() {
  var bodyClass = event.target.id.split('-')[1];
  $('body').removeClass().addClass(bodyClass);
  $('#switcher button').removeClass('selected');
  $(this).addClass('selected');
});

Listing 3.17

When a selector expression is provided as the second argument to .on(), jQuery binds the click handler to the #switcher object, but compares event.target against the selector expression—in this case, 'button'. If it matches, jQuery maps the this keyword to the matched element. Otherwise, the event handler is not executed at all.

Tip

We'll fully examine this use of .on(), as well as the .delegate() and .undelegate() methods, in Chapter 10, Advanced Events.

主站蜘蛛池模板: 固原市| 绥化市| 汾西县| 黑水县| 彰武县| 盐边县| 太康县| 黎川县| 滁州市| 涪陵区| 沿河| 怀集县| 突泉县| 德阳市| 忻城县| 巩留县| 桃江县| 金秀| 新干县| 孟村| 霍林郭勒市| 诏安县| 理塘县| 保亭| 丘北县| 叶城县| 涡阳县| 中方县| 柳江县| 义马市| 河间市| 拜泉县| 东乌珠穆沁旗| 斗六市| 胶南市| 高唐县| 晋州市| 安康市| 买车| 霸州市| 新竹县|