diff --git a/page/events/introduction-to-custom-events.md b/page/events/introduction-to-custom-events.md index 72287e43..c05ff497 100644 --- a/page/events/introduction-to-custom-events.md +++ b/page/events/introduction-to-custom-events.md @@ -7,7 +7,7 @@ ## Custom Events -We're all familiar with the basic events — click, mouseover, focus, blur, submit, etc. — that we can latch on to as a user interacts with the browser. Custom events open up a whole new world of event-driven programming. In this chapter, we'll use jQuery's custom events system to make a simple Twitter search application. +We're all familiar with the basic events — click, mouseover, focus, blur, submit, etc. — that we can latch on to as a user interacts with the browser. Custom events open up a whole new world of event-driven programming. It can be difficult at first to understand why you'd want to use custom events, when the built-in events seem to suit your needs just fine. It turns out that custom events offer a whole new way of thinking about event-driven JavaScript. Instead of focusing on the element that triggers an action, custom events put the spotlight on the element being acted upon. This brings a bevy of benefits, including: @@ -32,8 +32,8 @@ Without custom events, you might write some code like this: ``` $( ".switch, .clapper" ).click(function() { - var light = $( this ).parent().find( ".lightbulb" ); - if ( light.hasClass( "on" ) ) { + var light = $( this ).closest( ".room" ).find( ".lightbulb" ); + if ( light.is( ".on" ) ) { light.removeClass( "on" ).addClass( "off" ); } else { light.removeClass( "off" ).addClass( "on" ); @@ -44,9 +44,9 @@ $( ".switch, .clapper" ).click(function() { With custom events, your code might look more like this: ``` -$( ".lightbulb" ).on( "changeState", function( e ) { +$( ".lightbulb" ).on( "light:toggle", function( event ) { var light = $( this ); - if ( light.hasClass( "on" ) ) { + if ( light.is( ".on" ) ) { light.removeClass( "on" ).addClass( "off" ); } else { light.removeClass( "off" ).addClass( "on" ); @@ -54,7 +54,8 @@ $( ".lightbulb" ).on( "changeState", function( e ) { }); $( ".switch, .clapper" ).click(function() { - $( this ).parent().find( ".lightbulb" ).trigger( "changeState" ); + var room = $( this ).closest( ".room" ); + room.find( ".lightbulb" ).trigger( "light:toggle" ); }); ``` @@ -78,38 +79,44 @@ Let's make our example a little more interesting. We'll add another room to our
``` -If there are any lights on in the house, we want the master switch to turn all the lights off; otherwise, we want it to turn all lights on. To accomplish this, we'll add two more custom events to the lightbulbs: `turnOn` and `turnOff`. We'll make use of them in the `changeState` custom event, and use some logic to decide which one the master switch should trigger: +If there are any lights on in the house, we want the master switch to turn all the lights off; otherwise, we want it to turn all lights on. To accomplish this, we'll add two more custom events to the lightbulbs: `light:on` and `light:off`. We'll make use of them in the `light:toggle` custom event, and use some logic to decide which one the master switch should trigger: ``` -$( ".lightbulb" ).on( "changeState", function( e ) { +$( ".lightbulb" ).on( "light:toggle", function( event ) { var light = $( this ); - if ( light.hasClass( "on" ) ) { - light.trigger( "turnOff" ); + if ( light.is( ".on" ) ) { + light.trigger( "light:off" ); } else { - light.trigger( "turnOn" ); + light.trigger( "light:on" ); } -}).on( "turnOn", function( e ) { +}).on( "light:on", function( event ) { $( this ).removeClass( "off" ).addClass( "on" ); -}).on( "turnOff", function( e ) { +}).on( "light:off", function( event ) { $( this ).removeClass( "on" ).addClass( "off" ); }); $( ".switch, .clapper" ).click(function() { - $( this ).parent().find( ".lightbulb" ).trigger( "changeState" ); + var room = $( this ).closest( ".room" ); + room.find( ".lightbulb" ).trigger( "light:toggle" ); }); $( "#master_switch" ).click(function() { - if ( $( ".lightbulb.on" ).length ) { - $( ".lightbulb" ).trigger( "turnOff" ); + var lightbulbs = $( ".lightbulb" ); + + // Check if any lightbulbs are on + if ( lightbulbs.is( ".on" ) ) { + lightbulbs.trigger( "light:off" ); } else { - $( ".lightbulb" ).trigger( "turnOn" ); + lightbulbs.trigger( "light:on" ); } }); ``` Note how the behavior of the master switch is attached to the master switch; the behavior of a lightbulb belongs to the lightbulbs. -If you're accustomed to object-oriented programming, you may find it useful to think of custom events as methods of objects. Loosely speaking, the object to which the method belongs is created via the jQuery selector. Binding the `changeState` custom event to all `$( ".light" )` elements is akin to having a class called `Light` with a method of `changeState`, and then instantiating new `Light` objects for each element with a classname of `light`. +### Naming Custom Events + +You can use any name for a custom event, however you should beware of creating new events with names that might be used by future DOM events. For this reason, in this article we have chosen to use `light:` for all of our event names, as events with colons are unlikely to be used by a future DOM spec. ### Recap: `.on()` and `.trigger()` @@ -133,231 +140,6 @@ $( document ).on( "myCustomEvent", { $( document ).trigger( "myCustomEvent", [ "bim", "baz" ] ); ``` -### A Sample Application - -To demonstrate the power of custom events, we're going to create a simple tool for searching Twitter. The tool will offer several ways for a user to add search terms to the display: by entering a search term in a text box, by entering multiple search terms in the URL, and by querying Twitter for trending terms. - -The results for each term will be shown in a results container; these containers will be able to be expanded, collapsed, refreshed, and removed, either individually or all at once. - -When we're done, it will look like this: - -![Our finished application](http://gyazo.com/70415e9fffab1c47953f5264ecf722fe.png) - -``` -

Twitter Search

- - -
- - -
- -
-
-

Search Results for -

-
-
-``` - -This gives us a container (`#twitter`) for our widget, a template for our results containers (hidden via CSS), and a simple form where users can input a search term. (For the sake of simplicity, we're going to assume that our application is JavaScript-only and that our users will always have CSS.) - -There are two types of objects we'll want to act on: the results containers, and the Twitter container. - -The results containers are the heart of the application. We'll create a plugin that will prepare each results container once it's added to the Twitter container. Among other things, it will bind the custom events for each container and add the action buttons at the top right of each container. Each results container will have the following custom events: - -* `refresh` — Mark the container as being in the "refreshing" state, and fire the request to fetch the data for the search term. - -* `populate` — Receive the returned JSON data and use it to populate the container. - -* `remove` — Remove the container from the page after the user verifies the request to do so. Verification can be bypassed by passing `true` as the second argument to the event handler. The `remove` event also removes the term associated with the results container from the global object containing the search terms. - -* `collapse` — Add a class of collapsed to the container, which will hide the results via CSS. It will also turn the container's "Collapse" button into an "Expand" button. - -* `expand` — Remove the collapsed class from the container. It will also turn the container's "Expand" button into a "Collapse" button. - -The plugin is also responsible for adding the action buttons to the container. It binds a click event to each action's list item, and uses the list item's class to determine which custom event will be triggered on the corresponding results container. - -``` -$.fn.twitterResult = function( settings ) { - return this.each(function() { - var results = $( this ); - var actions = $.fn.twitterResult.actions = - $.fn.twitterResult.actions || $.fn.twitterResult.createActions(); - var a = actions.clone().prependTo( results ); - var term = settings.term; - - results.find( "span.search_term" ).text( term ); - $.each([ "refresh", "populate", "remove", "collapse", "expand" ], function( i, ev ) { - results.on( ev, { - term: term - }, $.fn.twitterResult.events[ ev ] ); - }); - - // Use the class of each action to figure out - // which event it will trigger on the results panel - a.find( "li" ).click(function() { - - // Pass the li that was clicked to the function - // so it can be manipulated if needed - results.trigger( $( this ).attr( "class" ), [ $( this ) ] ); - }); - }); -}; - -$.fn.twitterResult.createActions = function() { - return $( "