Learning jQuery(Fourth Edition)
上QQ阅读APP看书,第一时间看更新

Simulating user interaction

At times, it is convenient to execute code that we have bound to an event, even if the normal circumstances of the event are not occurring. For example, suppose we wanted our style switcher to begin in its collapsed state. We could accomplish this by hiding buttons from within the stylesheet, or by adding our hidden class or calling the .hide() method from a $(document).ready() handler. Another way would be to simulate a click on the style switcher so that the toggling mechanism we've already established is triggered.

The .trigger() method allows us to do just this:

$(document).ready(function() {
  $('#switcher').trigger('click');
});

Listing 3.23

Now when the page loads, the switcher is collapsed just as if it had been clicked, as shown in the following screenshot:

If we were hiding content that we wanted people without JavaScript enabled to see, this would be a reasonable way to implement graceful degradation.

The .trigger() method provides the same set of shortcut methods that .on() does. When these shortcuts are used with no arguments, the behavior is to trigger the action rather than bind it:

$(document).ready(function() {
  $('#switcher').click();
});

Listing 3.24

Reacting to keyboard events

As another example, we can add keyboard shortcuts to our style switcher. When the user types the first letter of one of the display styles, we will have the page behave as if the corresponding button was clicked. To implement this feature, we will need to explore keyboard events, which behave a bit differently from mouse events.

There are two types of keyboard events: those that react to the keyboard directly (keyup and keydown) and those that react to text input (keypress). A single character entry event could correspond to several keys, for example, when the Shift key in combination with the X key creates the capital letter X. While the specifics of implementation differ from one browser to the next (unsurprisingly), a safe rule of thumb is: if you want to know what key the user pushed, you should observe the keyup or keydown event; if you want to know what character ended up on the screen as a result, you should observe the keypress event. For this feature, we just want to know when the user presses the D, N, or L key, so we will use keyup.

Next, we need to determine which element should watch for the event. This is a little less obvious than with mouse events, where we have a visible mouse cursor to tell us about the event's target. Instead, the target of a keyboard event is the element that currently has the keyboard focus. The element with focus can be changed in several ways, including using mouse clicks and pressing the Tab key. Not every element can get the focus, either; only items that have default keyboard-driven behaviors such as form fields, links, and elements with a .tabIndex property are candidates.

In this case, we don't really care what element has the focus; we want our switcher to work whenever the user presses one of the keys. Event bubbling will once again come in handy, as we can bind our keyup event to the document element and have assurance that eventually any key event will bubble up to us.

Finally, we will need to know which key was pressed when our keyup handler gets triggered. We can inspect the event object for this. The .which property of the event contains an identifier for the key that was pressed, and for alphabetic keys, this identifier is the ASCII value of the uppercase letter. With this information, we can now create an object literal of letters and their corresponding buttons to click. When the user presses a key, we'll see if its identifier is in the map, and if so, trigger the click:

$(document).ready(function() {
  var triggers = {
    D: 'default',
    N: 'narrow',
    L: 'large'
  };
  $(document).keyup(function(event) {
    var key = String.fromCharCode(event.which);
    if (key in triggers) {
      $('#switcher-' + triggers[key]).click();
    }
  });
});

Listing 3.25

Presses of these three keys now simulate mouse clicks on the buttons—provided that the key event is not interrupted by features such as Firefox's search for text when I start typing.

As an alternative to using .trigger() to simulate this click, let's explore how to factor out code into a function so that more than one handler can call it in this case, both click and keyup handlers. While not necessary in this case, this technique can be useful in eliminating code redundancy:

$(document).ready(function() {
  // Enable hover effect on the style switcher
  $('#switcher').hover(function() {
    $(this).addClass('hover');
  }, function() {
    $(this).removeClass('hover');
  });
  // Allow the style switcher to expand and collapse
  var toggleSwitcher = function(event) {
    if (!$(event.target).is('button')) {
      $('#switcher button').toggleClass('hidden');
    }
  };
  $('#switcher').on('click', toggleSwitcher);
  // Simulate a click so we start in a collapsed state
  $('#switcher').click();
  // The setBodyClass() function changes the page style
  // The style switcher state is also updated
  var setBodyClass = function(className) {
    $('body').removeClass().addClass(className);
    $('#switcher button').removeClass('selected');
    $('#switcher-' + className).addClass('selected');
    $('#switcher').off('click', toggleSwitcher);
    if (className == 'default') {
      $('#switcher').on('click', toggleSwitcher);
    }
  };
  // Begin with the switcher-default button "selected"
  $('#switcher-default').addClass('selected');
  // Map key codes to their corresponding buttons to click
  var triggers = {
    D: 'default',
    N: 'narrow',
    L: 'large'
  };
  // Call setBodyClass() when a button is clicked
  $('#switcher').click(function(event) {
    if ($(event.target).is('button')) {
      var bodyClass = event.target.id.split('-')[1];
      setBodyClass(bodyClass);
    }
  });
  // Call setBodyClass() when a key is pressed
  $(document).keyup(function(event) {
    var key = String.fromCharCode(event.keyCode);
    if (key in triggers) {
      setBodyClass(triggers[key]);
    }
  });
});

Listing 3.26

This final revision consolidates all the previous code examples of this chapter. We have moved the entire block of code into a single $(document).ready() handler and made our code less redundant.