Events - The jQuery API - Web Development with jQuery (2015)

Web Development with jQuery (2015)

Part I. The jQuery API

Chapter 3. Events

jQuery offers a powerful and comprehensive event API. jQuery's event API provides wrapper methods that accommodate most events in JavaScript. jQuery's event API also provides the capability to attach events it doesn't explicitly support via its event methods. The jQuery event API even has the capability to apply events to elements that might not even exist in the document yet. Events can be neatly organized and namespaced within jQuery, another feature it offers above and beyond the baseline provided by JavaScript. Your events can be neatly organized into named categories, which make it a lot easier to manage events. Having named events also makes it possible to easily remove them.

In this chapter, you learn everything you need to know to work with jQuery's event API. You learn how to use jQuery's event wrapper methods such as click() or hover(). You also learn how to use methods such as on() and off(). You can use the on() and off()methods to attach an event handler function to any event, whether it is a native JavaScript event or a custom event that you've created. The on() and off() methods can also attach events to elements that might not even exist in the document yet. In addition, on() andoff() can name and organize events, which is useful if you need to manage or remove events as easily as creating them. You also learn how to create completely custom events for your applications by virtue of the trigger() method as well as the on() and off()methods. Custom events can make your own applications highly extensible and flexible.

The Various Event Wrapper Methods

jQuery's event API started with the goal of providing a bridge between the different browsers' disparate methods for dealing with event attachment. There was a time in the not-so-distant past that there was the Microsoft way of dealing with events, and then there was the standardized way of dealing with events. Because of Microsoft's work on Internet Explorer, this is no longer an issue, and you have to worry about this only if you need to support those older versions of Internet Explorer that don't support the standard way of attaching events. Thankfully, jQuery already deals with the browser differences for you. The jQuery 1.x branch provides legacy support for versions of Internet Explorer that don't support the standardized way. The jQuery 2.x branch does away with things such as legacy support for older versions of Internet Explorer, so the 2.x branch will not provide universal event support.

With the differences in browser support safely behind us for the most part, the jQuery event API has taken up the task of making it easier to work with events in JavaScript in general, and it succeeds well at doing so. The first collection of methods you take a look at in this chapter are a collection of methods that provide API wrappers around the most-used events in JavaScript. These methods make it possible to do two things:

· To easily attach a callback function to an event

· To easily trigger an event

You can find a comprehensive list of event methods in Appendix D, “Events.”

The following example demonstrates the jQuery event wrapper method, click(). Remember, this and all examples are available for free with the book's source code download materials from www.wrox.com/go/webdevwithjquery. This example is available in the accompanying materials as Example 3-1.html.

<!DOCTYPE HTML>

<html lang='en'>

<head>

<meta http-equiv='X-UA-Compatible' content='IE=Edge' />

<meta charset='utf-8' />

<title>Finder</title>

<script src='../jQuery.js'></script>

<script src='../jQueryUI.js'></script>

<script src='Example 3-1.js'></script>

<link href='Example 3-1.css' rel='stylesheet' />

</head>

<body>

<div id='finderFiles'>

<div class='finderDirectory' data-path='/Applications'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>Applications</span>

</div>

</div>

<div class='finderDirectory' data-path='/Library'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>Library</span>

</div>

</div>

<div class='finderDirectory' data-path='/Network'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>Network</span>

</div>

</div>

<div class='finderDirectory' data-path='/Sites'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>Sites</span>

</div>

</div>

<div class='finderDirectory' data-path='/System'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>System</span>

</div>

</div>

<div class='finderDirectory' data-path='/Users'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>Users</span>

</div>

</div>

</div>

</body>

</html>

The preceding HTML is styled with the following style sheet, Example 3-1.css.

html,

body {

width: 100%;

height: 100%;

}

body {

font: 12px "Lucida Grande", Arial, sans-serif;

background: rgb(189, 189, 189) url('images/Bottom.png') repeat-x bottom;

color: rgb(50, 50, 50);

margin: 0;

padding: 0;

}

div#finderFiles {

border-bottom: 1px solid rgb(64, 64, 64);

background: #fff;

position: absolute;

top: 0;

right: 0;

bottom: 23px;

left: 0;

overflow: auto;

user-select: none;

-webkit-user-select: none;

-moz-user-select: none;

-ms-user-select: none;

}

div.finderDirectory {

float: left;

width: 150px;

height: 100px;

overflow: hidden;

}

div.finderIcon {

background: url('images/Folder 48x48.png') no-repeat center;

background-size: 48px 48px;

height: 56px;

width: 54px;

margin: 10px auto 3px auto;

}

div.finderIconSelected {

background-color: rgb(204, 204, 204);

border-radius: 5px;

}

div.finderDirectoryName {

text-align: center;

}

span.finderDirectoryNameSelected {

background: rgb(56, 117, 215);

border-radius: 8px;

color: white;

padding: 1px 7px;

}

Finally, the JavaScript in Example 3-1.js adds some selection functionality to the folders that you created.

$(document).ready(

function()

{

$('div.finderDirectory, div.finderFile').click(

function(event)

{

$('div.finderIconSelected')

.removeClass('finderIconSelected');

$('span.finderDirectoryNameSelected')

.removeClass('finderDirectoryNameSelected');

$(this).find('div.finderIcon')

.addClass('finderIconSelected');

$(this).find('div.finderDirectoryName span')

.addClass('finderDirectoryNameSelected');

}

);

$('div.finderDirectory, div.finderFile')

.filter(':first')

.click();

}

);

The collection of files that make up Example 3-1 results in what you see in Figure 3.1, when the file is loaded into Safari.

image

Figure 3.1

Example 3-1 demonstrates a simple use of the click() method both to attach a callback method and to trigger the event. Most of jQuery's event wrapper methods work exactly like this, with just a few exceptions, which are events such as hover(), which accept multiple callback methods: one for the mouseover event and one for the mouseout event.

The document ready() event method is also an example of a wrapper event method, which jQuery creates for the DOMContentLoaded event.

In the example, you attach the callback function. You start by selecting all the <div> elements in the document with the class names finderDirectory and finderFile.

$('div.finderDirectory, div.finderFile').click(

function(event)

{

$('div.finderIconSelected')

.removeClass('finderIconSelected');

$('span.finderDirectoryNameSelected')

.removeClass('finderDirectoryNameSelected');

$(this).find('div.finderIcon')

.addClass('finderIconSelected');

$(this).find('div.finderDirectoryName span')

.addClass('finderDirectoryNameSelected');

}

);

When a click event fires and the callback function is executed, there is a bit of logic that handles visually selecting a file or a folder. You start by removing a selection, which is to say you select the <div> element with class name finderIconSelected, and then you remove the finderIconSelected class name from it. You then do the same thing with the <span> element with class name finderDirectoryNameSelected. Then the function selects and adds those same class names, finderIconSelected and finderDirectoryNameSelected, to elements that exist inside the element that the event fires on. That element, the element the event fires on, is made available within the callback function within the object stored in the this keyword.

Attaching Other Events

jQuery's event API provides wrapper methods for most events, but there are some events that there are no methods for. Which events, you might ask? Events like those found in the HTML5 drag-and-drop API, for example. There are no jQuery-provided dragstart()or drop() methods like there are jQuery-provided click() or mouseover() methods.

For those events, you need to use the on() and off() methods, which attach event handlers to any named event. The following example takes the script in Example 3-1.js and rewrites it to use the on(), off(), and trigger() methods instead of the respective built-in methods for each of the events.

$(document).on(

'DOMContentLoaded',

function()

{

$('div.finderDirectory, div.finderFile').on(

'click',

function(event)

{

$('div.finderIconSelected')

.removeClass('finderIconSelected');

$('span.finderDirectoryNameSelected')

.removeClass('finderDirectoryNameSelected');

$(this).find('div.finderIcon')

.addClass('finderIconSelected');

$(this).find('div.finderDirectoryName span')

.addClass('finderDirectoryNameSelected');

}

);

$('div.finderDirectory, div.finderFile')

.filter(':first')

.trigger('click');

}

);

This example is identical in functionality to Example 3-1; you can find it in the source materials as Example 3-2.

Instead of $(document).ready(), $(document).on('DOMContentLoaded') provides identical functionality. You can think of jQuery's on() method as being close to the standard addEventListener() method that you'd use in JavaScript if you weren't working with a JavaScript framework. It simply has more features built into it to make working with events a lot easier.

Instead of $('div.finderDirectory, div.finderFile').click(), you use $('div.finderDirectory, div.finderFile').on('click'). Finally, to trigger an event to be fired, instead of just calling the event method, like click(), you call the trigger() method with the event name as its argument, such as, trigger('click').

Attaching Persistent Event Handlers

A convenient and cool feature of jQuery's on() and off() methods is the concept of attaching events to nodes in the DOM that might not even exist when you create the event handler. Internally, this feature works by attaching an event to a node that is higher up the DOM tree and thus does exist at the time the event handler is processed and attached.

For example, you might attach a click event to the document object. Then, by providing a selector to the second argument of the on() method, you create a persistent event handler that applies to only the nodes described by the selector. Those nodes described by the selector can exist or not exist at the time the event handler is created; the only catch is the nodes must exist inside the object the event handler is attached to.

Then using event propagation, the event takes place and bubbles up the DOM tree to the element the event handler is attached to. jQuery continuously looks at the event.target property to see if the node that received the event is described by the selector that you provide. If it is, then it applies the event handler.

The following example, which can be found in the source materials as Example 3-3, takes the previous two examples and implements the concept of persistent events. (jQuery's documentation has also referred to this concept as live events.) You begin with modifying the HTML so that some files can be added after the event handler is created.

<!DOCTYPE HTML>

<html lang='en'>

<head>

<meta http-equiv='X-UA-Compatible' content='IE=Edge' />

<meta charset='utf-8' />

<title>Finder</title>

<script src='../jQuery.js'></script>

<script src='../jQueryUI.js'></script>

<script src='Example 3-3.js'></script>

<link href='Example 3-3.css' rel='stylesheet' />

</head>

<body>

<div id='finderFiles'>

<div class='finderDirectory finderNode' data-path='/Applications'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>Applications</span>

</div>

</div>

<div class='finderDirectory finderNode' data-path='/Library'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>Library</span>

</div>

</div>

<div class='finderDirectory finderNode' data-path='/Network'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>Network</span>

</div>

</div>

<div class='finderDirectory finderNode' data-path='/Sites'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>Sites</span>

</div>

</div>

<div class='finderDirectory finderNode' data-path='/System'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>System</span>

</div>

</div>

<div class='finderDirectory finderNode' data-path='/Users'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>Users</span>

</div>

</div>

</div>

<div id='finderAdditionalFiles'>

<div class='finderFile finderNode' data-path='/index.html'>

<div class='finderIcon'></div>

<div class='finderFileName'>

<span>index.html</span>

</div>

</div>

<div class='finderFile finderNode' data-path='/Departments.html'>

<div class='finderIcon'></div>

<div class='finderFileName'>

<span>Departments.html</span>

</div>

</div>

<div class='finderFile finderNode' data-path='/Documents.html'>

<div class='finderIcon'></div>

<div class='finderFileName'>

<span>Documents.html</span>

</div>

</div>

</div>

</body>

</html>

The style sheet that you used for the previous two examples is modified a bit as well to add class names for file nodes and directory nodes.

html,

body {

width: 100%;

height: 100%;

}

body {

font: 12px "Lucida Grande", Arial, sans-serif;

background: rgb(189, 189, 189) url('images/Bottom.png') repeat-x bottom;

color: rgb(50, 50, 50);

margin: 0;

padding: 0;

}

div#finderFiles {

border-bottom: 1px solid rgb(64, 64, 64);

background: #fff;

position: absolute;

top: 0;

right: 0;

bottom: 23px;

left: 0;

overflow: auto;

user-select: none;

-webkit-user-select: none;

-moz-user-select: none;

-ms-user-select: none;

}

div#finderAdditionalFiles {

display: none;

}

div.finderDirectory,

div.finderFile {

float: left;

width: 150px;

height: 100px;

overflow: hidden;

}

div.finderIcon {

background: url('images/Folder 48x48.png') no-repeat center;

background-size: 48px 48px;

height: 56px;

width: 54px;

margin: 10px auto 3px auto;

}

div.finderFile div.finderIcon {

background-image: url('images/Safari Document.png');

}

div.finderIconSelected {

background-color: rgb(204, 204, 204);

border-radius: 5px;

}

div.finderDirectoryName,

div.finderFileName {

text-align: center;

}

span.finderDirectoryNameSelected,

span.finderFileNameSelected {

background: rgb(56, 117, 215);

border-radius: 8px;

color: white;

padding: 1px 7px;

}

And finally, the JavaScript is modified to use a persistent event, as well as to add some new files when you double-click anywhere on the document to test the concept of a persistent event handler.

$(document).on(

'DOMContentLoaded',

function()

{

$('div#finderFiles').on(

'click',

'div.finderDirectory, div.finderFile',

function(event)

{

$('div.finderIconSelected')

.removeClass('finderIconSelected');

$('span.finderDirectoryNameSelected')

.removeClass('finderDirectoryNameSelected');

$('span.finderFileNameSelected')

.removeClass('finderFileNameSelected');

$(this).find('div.finderIcon')

.addClass('finderIconSelected');

$(this).find('div.finderDirectoryName span')

.addClass('finderDirectoryNameSelected');

$(this).find('div.finderFileName span')

.addClass('finderFileNameSelected');

}

);

$('div#finderFiles div.finderNode:first')

.trigger('click');

var addedAdditionalFiles = false;

$('body').dblclick(

function()

{

if (addedAdditionalFiles)

{

return;

}

$('div#finderAdditionalFiles > div.finderFile').each(

function()

{

$('div#finderFiles').append(

$(this).clone()

);

}

);

addedAdditionalFiles = true;

}

);

}

);

When the code is loaded into a browser and a dblclick event is dispatched, you can see results similar to Figure 3.2.

image

Figure 3.2

This example rewrites Example 3-2 to include a persistent event handler and some additional HTML to test that persistent event handler. The click event handler is attached to the <div> element with the id name finderFiles. This is done because that <div> element will always exist. The second argument, the selector 'div.finderDirectory, div.finderFile', sets up the persistent event handler. The event is attached to the <div> with the id name finderFiles, but the selector argument keeps the event handler from being executed unless the event originates on an element matching the selector.

$('div#finderFiles').on(

'click',

'div.finderDirectory, div.finderFile',

function(event)

{

$('div.finderIconSelected')

.removeClass('finderIconSelected');

$('span.finderDirectoryNameSelected')

.removeClass('finderDirectoryNameSelected');

$('span.finderFileNameSelected')

.removeClass('finderFileNameSelected');

$(this).find('div.finderIcon')

.addClass('finderIconSelected');

$(this).find('div.finderDirectoryName span')

.addClass('finderDirectoryNameSelected');

$(this).find('div.finderFileName span')

.addClass('finderFileNameSelected');

}

);

The event handler is given some new code to deal with the semantics of having files in addition to directories.

$('div#finderFiles div.finderNode:first')

.trigger('click');

The event is triggered on the first <div> element with the class name finderNode.

Next, you set up a variable to keep track of whether the additional files have been added to the entire collection of files and folders, which tests whether an element has to exist for a persistent event handler to get applied.

var addedAdditionalFiles = false;

Technically, these new elements do exist in the DOM, but they do not exist within the <div> element that acts as a container for directory and file nodes, and thus, the requisite events associated with file and directory nodes are not yet applied.

$('body').dblclick(

function()

{

if (addedAdditionalFiles)

{

return;

}

$('div#finderAdditionalFiles > div.finderFile').each(

function()

{

$('div#finderFiles').append(

$(this).clone()

);

}

);

addedAdditionalFiles = true;

}

);

First, you check the variable addedAdditionalFiles; if that variable is true, then execution of the dblclick handler returns. If addedAdditionalFiles is false, then you look inside the <div> with the id name finderAdditionalFiles for some extra <div> elements with class names finderFile, and each of those are added to the other <div> element with the id name finderFiles.

When you click one of the new <div> elements, you notice that selection happens without any additional effort. This is what it means to use a persistent event handler; the event continues to work when new elements are added that match the selector argument. Still using the file manager metaphor, this makes it possible to attach just one event handler for many files or folders, instead of an event handler for each file and folder. If you have a lot of files and folders in the DOM, this also has the advantage of substantially increasing performance. So, persistent event handlers benefit you in two key ways.

1. The element does not have to exist when the event handler is created. The element can be created later; it just has to match the selector that you provide to the on() method.

2. Client-side browser performance can be substantially boosted because you can reduce the number of event handlers that you need for a given event to just one from potentially many.

Removing Event Handlers

The on() method has a companion method called off(), which removes event handlers from a document. jQuery also provides a useful way of discerning which events should be removed by virtue of its capability to namespace event handlers.

Within a more complicated client-side application, you can quickly lose track of which scripts create which event handlers. This is easily remedied by the introduction of named events by jQuery.

The syntax used to name an event is simple: In the argument where you name the event, you add a dot and then the name that you want to use. The syntax works similarly to class names. And like class names, using multiple dots will allow you to refer to multiple names. And referring to any one name refers to any event using that name (even if that event has multiple names attached to it).

The following example, which can be found in the source materials as Example 3-4, demonstrates how to work with named events, as well as how to dynamically apply and remove an event handler. You begin with the same HTML that you worked with in preceding examples; you add two new buttons to dynamically apply and remove events.

<!DOCTYPE HTML>

<html lang='en'>

<head>

<meta http-equiv='X-UA-Compatible' content='IE=Edge' />

<meta charset='utf-8' />

<title>Finder</title>

<script src='../jQuery.js'></script>

<script src='../jQueryUI.js'></script>

<script src='Example 3-4.js'></script>

<link href='Example 3-4.css' rel='stylesheet' />

</head>

<body>

<div id='finderFiles'>

<div class='finderDirectory finderNode' data-path='/Applications'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>Applications</span>

</div>

</div>

<div class='finderDirectory finderNode' data-path='/Library'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>Library</span>

</div>

</div>

<div class='finderDirectory finderNode' data-path='/Network'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>Network</span>

</div>

</div>

<div class='finderDirectory finderNode' data-path='/Sites'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>Sites</span>

</div>

</div>

<div class='finderDirectory finderNode' data-path='/System'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>System</span>

</div>

</div>

<div class='finderDirectory finderNode' data-path='/Users'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>Users</span>

</div>

</div>

<div class='finderFile finderNode' data-path='/index.html'>

<div class='finderIcon'></div>

<div class='finderFileName'>

<span>index.html</span>

</div>

</div>

<div class='finderFile finderNode' data-path='/Departments.html'>

<div class='finderIcon'></div>

<div class='finderFileName'>

<span>Departments.html</span>

</div>

</div>

<div class='finderFile finderNode' data-path='/Documents.html'>

<div class='finderIcon'></div>

<div class='finderFileName'>

<span>Documents.html</span>

</div>

</div>

</div>

<div id='finderActions'>

<button id='finderApplyEventHandler'>

Apply Event Handler

</button>

<button id='finderRemoveEventHandler'>

Remove Event Handler

</button>

</div>

</body>

</html>

The following CSS is applied to the HTML document; it adds some new CSS:

html,

body {

width: 100%;

height: 100%;

}

body {

font: 12px "Lucida Grande", Arial, sans-serif;

background: rgb(189, 189, 189) url('images/Bottom.png') repeat-x bottom;

color: rgb(50, 50, 50);

margin: 0;

padding: 0;

}

div#finderFiles {

border-bottom: 1px solid rgb(64, 64, 64);

background: #fff;

position: absolute;

z-index: 1;

top: 0;

right: 0;

bottom: 23px;

left: 0;

overflow: auto;

user-select: none;

-webkit-user-select: none;

-moz-user-select: none;

-ms-user-select: none;

}

div#finderAdditionalFiles {

display: none;

}

div.finderDirectory,

div.finderFile {

float: left;

width: 150px;

height: 100px;

overflow: hidden;

}

div.finderIcon {

background: url('images/Folder 48x48.png') no-repeat center;

background-size: 48px 48px;

height: 56px;

width: 54px;

margin: 10px auto 3px auto;

}

div.finderFile div.finderIcon {

background-image: url('images/Safari Document.png');

}

div.finderIconSelected {

background-color: rgb(204, 204, 204);

border-radius: 5px;

}

div.finderDirectoryName,

div.finderFileName {

text-align: center;

}

span.finderDirectoryNameSelected,

span.finderFileNameSelected {

background: rgb(56, 117, 215);

border-radius: 8px;

color: white;

padding: 1px 7px;

}

div#finderActions {

position: absolute;

bottom: 1px;

right: 10px;

z-index: 2;

}

The following JavaScript demonstrates how to apply and remove event handlers at will:

$(document).on(

'DOMContentLoaded',

function()

{

var eventHandlerActive = false;

function applyEventHandler()

{

if (eventHandlerActive)

{

return;

}

$('div#finderFiles').on(

'click.finder',

'div.finderDirectory, div.finderFile',

function(event)

{

$('div.finderIconSelected')

.removeClass('finderIconSelected');

$('span.finderDirectoryNameSelected')

.removeClass('finderDirectoryNameSelected');

$('span.finderFileNameSelected')

.removeClass('finderFileNameSelected');

$(this).find('div.finderIcon')

.addClass('finderIconSelected');

$(this).find('div.finderDirectoryName span')

.addClass('finderDirectoryNameSelected');

$(this).find('div.finderFileName span')

.addClass('finderFileNameSelected');

}

);

eventHandlerActive = true;

}

function removeEventHandler()

{

$('div#finderFiles').off('click.finder');

eventHandlerActive = false;

}

$('div#finderFiles div.finderNode:first')

.trigger('click');

applyEventHandler();

$('button#finderApplyEventHandler').click(

function()

{

applyEventHandler();

}

);

$('button#finderRemoveEventHandler').click(

function()

{

removeEventHandler();

}

);

}

);

The preceding example adds two new buttons to the window, which you can see as shown in Figure 3.3.

image

Figure 3.3

In Example 3-4, the click event handler is applied using the applyEventHandler method, which uses jQuery's on() method with a named event handler click.finder. The event is specified as usual, and then a dot is inserted, and any name you like is added after the dot (any name following the same naming conventions as class or id names). You can also use multiple names if you like; in this example, you could have also used click.finder.selection.

The example also adds a button and a method to remove the click event handler. The off() method is called with the same event and event name as was used in the call to the on() method. $('div.finderFiles').off('click.finder') completely removes the event handler.

Creating Custom Events

Custom events are created using the same methods that you use to attach standard events: on(), off(), and trigger(). The only difference is that custom events require custom names. Custom names should simply require whatever you intend the event to provide.

Following are some examples of custom events from the context of a file manager application:

· An upload event can be created and used to execute a callback handler after a file upload has been completed.

· A folderUpdate event can be created and used to execute a callback handler when the files and folders displayed in the file manager are changed.

· A fileRename event can be created and used to execute a callback handler when a file is renamed.

Custom events exist to fulfill the need of providing more flexibility and extensibility in your applications. This, in turn, makes it possible to drop your application into the page where the user can attach custom event handlers to accommodate their imagined uses for your application. Custom events are demonstrated in the following example, which can be found as Example 3-5 in the source materials:

<!DOCTYPE HTML>

<html lang='en'>

<head>

<meta http-equiv='X-UA-Compatible' content='IE=Edge' />

<meta charset='utf-8' />

<title>Finder</title>

<script src='../jQuery.js'></script>

<script src='../jQueryUI.js'></script>

<script src='Example 3-5.js'></script>

<link href='Example 3-5.css' rel='stylesheet' />

</head>

<body>

<div id='finderFiles'>

<div class='finderDirectory finderNode' data-path='/Applications'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>Applications</span>

</div>

</div>

<div class='finderDirectory finderNode' data-path='/Library'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>Library</span>

</div>

</div>

<div class='finderDirectory finderNode' data-path='/Network'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>Network</span>

</div>

</div>

<div class='finderDirectory finderNode' data-path='/Sites'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>Sites</span>

</div>

</div>

<div class='finderDirectory finderNode' data-path='/System'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>System</span>

</div>

</div>

<div class='finderDirectory finderNode' data-path='/Users'>

<div class='finderIcon'></div>

<div class='finderDirectoryName'>

<span>Users</span>

</div>

</div>

</div>

<div id='finderAdditionalFiles'>

<div class='finderFile finderNode' data-path='/index.html'>

<div class='finderIcon'></div>

<div class='finderFileName'>

<span>index.html</span>

</div>

</div>

<div class='finderFile finderNode' data-path='/Departments.html'>

<div class='finderIcon'></div>

<div class='finderFileName'>

<span>Departments.html</span>

</div>

</div>

<div class='finderFile finderNode' data-path='/Documents.html'>

<div class='finderIcon'></div>

<div class='finderFileName'>

<span>Documents.html</span>

</div>

</div>

</div>

</body>

</html>

The preceding HTML is joined by the following style sheet:

html,

body {

width: 100%;

height: 100%;

}

body {

font: 12px "Lucida Grande", Arial, sans-serif;

background: rgb(189, 189, 189) url('images/Bottom.png') repeat-x bottom;

color: rgb(50, 50, 50);

margin: 0;

padding: 0;

}

div#finderFiles {

border-bottom: 1px solid rgb(64, 64, 64);

background: #fff;

position: absolute;

top: 0;

right: 0;

bottom: 23px;

left: 0;

overflow: auto;

user-select: none;

-webkit-user-select: none;

-moz-user-select: none;

-ms-user-select: none;

}

div#finderAdditionalFiles {

display: none;

}

div.finderDirectory,

div.finderFile {

float: left;

width: 150px;

height: 100px;

overflow: hidden;

}

div.finderIcon {

background: url('images/Folder 48x48.png') no-repeat center;

background-size: 48px 48px;

height: 56px;

width: 54px;

margin: 10px auto 3px auto;

}

div.finderFile div.finderIcon {

background-image: url('images/Safari Document.png');

}

div.finderIconSelected {

background-color: rgb(204, 204, 204);

border-radius: 5px;

}

div.finderDirectoryName,

div.finderFileName {

text-align: center;

}

span.finderDirectoryNameSelected,

span.finderFileNameSelected {

background: rgb(56, 117, 215);

border-radius: 8px;

color: white;

padding: 1px 7px;

}

And finally, this example is topped off with the following JavaScript, which implements a custom event handler and a trigger for that event handler.

$(document).on(

'DOMContentLoaded',

function()

{

$('div#finderFiles')

.on(

'click.finder',

'div.finderDirectory, div.finderFile',

function(event)

{

$('div.finderIconSelected')

.removeClass('finderIconSelected');

$('span.finderDirectoryNameSelected')

.removeClass('finderDirectoryNameSelected');

$('span.finderFileNameSelected')

.removeClass('finderFileNameSelected');

$(this).find('div.finderIcon')

.addClass('finderIconSelected');

$(this).find('div.finderDirectoryName span')

.addClass('finderDirectoryNameSelected');

$(this).find('div.finderFileName span')

.addClass('finderFileNameSelected');

}

)

.on(

'appendFile.finder',

'div.finderDirectory, div.finderFile',

function(event, file)

{

console.log(file.path);

console.log($(this));

}

);

$('div#finderFiles div.finderNode:first')

.trigger('click.finder');

var addedAdditionalFiles = false;

$('body').dblclick(

function()

{

if (addedAdditionalFiles)

{

return;

}

$('div#finderAdditionalFiles > div.finderFile').each(

function()

{

var file = $(this).clone();

$('div#finderFiles').append(file);

file.trigger(

'appendFile.finder', {

path : file.data('path')

}

);

}

);

addedAdditionalFiles = true;

}

);

}

);

The preceding example's results are shown in Figure 3.4.

image

Figure 3.4

In Example 3-5, you begin by adding a custom event handler. That custom event handler is reproduced here.

.on(

'appendFile.finder',

'div.finderDirectory, div.finderFile',

function(event, file)

{

console.log(file.path);

console.log($(this));

}

);

The new custom event handler creates the appendFile.finder event on <div> elements with class names finderDirectory or finderFile. The custom event is namespaced to finder so that the appendFile event name can be applied to other things, if necessary.

And then when a dblclick event is dispatched on the file management window, and the additional files are appended to the document, for each of those files or folders, the appendFile event is fired with a call to trigger().

$('div#finderAdditionalFiles > div.finderFile').each(

function()

{

var file = $(this).clone();

$('div#finderFiles').append(file);

file.trigger(

'appendFile.finder', {

path : file.data('path')

}

);

}

);

When the appendFile.finder event is fired off, you can pass data into the event by passing an object literal to the second argument. This data is then passed back to the event handler in its second argument. The contents of the second argument and this are printed to the JavaScript console so that you can observe that custom events work similarly to native ones and allow custom data to be passed back to the handler.

Summary

jQuery events are a flexible and simple way of using JavaScript events. jQuery's APIs provide both wrapper methods for common JavaScript events, as well as more detailed APIs in the on(), off(), and trigger() methods.

If you want to use a browser event that jQuery does not provide a wrapper for, you must use on(), off(), or trigger() to use one of those events, for example, the HTML5 drag-and-drop API (which is discussed in Chapter 11, “HTML5 Drag and Drop”).

If you provide a selector to the on() method, you can create persistent or live event handlers. It becomes possible to apply event handlers for elements that don't exist yet. It also becomes possible to greatly reduce the number of event handlers applied within an application because with live or persistent events, events can be applied to just a single element further up the DOM tree.

Event handlers can be namespaced by adding a dot and name to the name of the event. Events can be given multiple names, if you like, and this works similarly to how class names work in CSS selectors.

Event handlers can be absolutely controlled, added, and removed at will. The off() method provides the mechanism to remove an event. Removing an event requires calling the off() method with the name of the event or the event namespace, or both.

Calling an event wrapper method with no arguments, for example calling click() as well as calling trigger(), can trigger an event handler.

Custom events can be created and used with the standard jQuery event API. The on(), off(), and trigger() methods can all create custom events. You also saw some examples in this chapter of some custom events you might apply to a file manager application.

Exercises

1. Name all the methods you can use to attach a mouseover event using jQuery.

Extra Credit: How would you attach both a mouseover and mouseout event using the same method? Hint: This answer can be found in Appendix D.

2. What method would you use to attach any browser event not already provided as a wrapper method?

3. What event property is used as the basis for determining what element has received an event using jQuery's persistent or live events? Explain what happens.

4. How do you use a persistent or live event to create an event handler?

5. How do you name an instance of an event handler? How do you apply multiple names to an instance of an event handler?

6. What method is used to remove an event handler?

7. Can an event handler be removed by virtue of its named instance only?

8. Name two ways to fire a click event handler using script.

9. How do you create a custom event handler? How do you send data to a custom event handler?