- Learning jQuery(Fourth Edition)
- Jonathan Chaffer Karl Swedberg
- 1741字
- 2021-08-13 17:18:49
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.
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.
- Mobile Application Development:JavaScript Frameworks
- Mastering Objectoriented Python
- 深入淺出WPF
- Visual C++實(shí)例精通
- Neo4j Essentials
- Building an RPG with Unity 2018
- 網(wǎng)站構(gòu)建技術(shù)
- ANSYS Fluent 二次開(kāi)發(fā)指南
- C#實(shí)踐教程(第2版)
- HTML5移動(dòng)前端開(kāi)發(fā)基礎(chǔ)與實(shí)戰(zhàn)(微課版)
- 少兒編程輕松學(xué)(全2冊(cè))
- Mastering PostgreSQL 11(Second Edition)
- Web前端開(kāi)發(fā)實(shí)戰(zhàn)教程(HTML5+CSS3+JavaScript)(微課版)
- Instant OpenCV for iOS
- 區(qū)塊鏈社會(huì):區(qū)塊鏈助力國(guó)家治理能力現(xiàn)代化