From 755da7f3a65b79618308afb0abdf9b2b3f218a7b Mon Sep 17 00:00:00 2001 From: Eric Ladd Date: Mon, 15 Oct 2012 17:30:02 -0400 Subject: [PATCH] refresh basic plugins; added utility plugin method example --- page/events/event-basics.md | 221 ++++++++++++++++++++++++++ page/plugins/basic-plugin-creation.md | 183 +++++++++++++-------- 2 files changed, 337 insertions(+), 67 deletions(-) create mode 100644 page/events/event-basics.md diff --git a/page/events/event-basics.md b/page/events/event-basics.md new file mode 100644 index 00000000..1ad64110 --- /dev/null +++ b/page/events/event-basics.md @@ -0,0 +1,221 @@ +--- +title: jQuery Event Basics +attribution: +status: needswork +editrequired: 2 +source: working-with-events-part-1.md, inside-event-handling-function.md, events-to-elements.md +--- + +## jQuery Event Basics + +### Setting Up Event Responses on DOM Elements + +jQuery makes it straightforward to set up event-driven responses on page elements. +These events are often triggered by the end user's interaction with the page, +such as when a text is entered into a form element or the mouse pointer is moved. +In some cases, such as the page load and unload events, the browser itself will +trigger the event. + +jQuery offers convenience methods for most native browser events. These methods — +including `$.fn.click`, `$.fn.focus`, `$.fn.blur`, `$.fn.change`, etc. — are shorthand +for jQuery's `$.fn.on` method. The on method is useful for binding the same handler +function to multiple events, when you want to provide data to the event hander, +when you are working with custom events, or when you want to pass an object of +multiple events and handlers. + +``` +// Event setup using a convenience method +$('p').click(function() { + console.log('You clicked a paragraph!'); +}); +``` + +``` +// Equivalent event setup using the `$.fn.on` method +$('p').on('click', function() { + console.log('click'); +}); +``` + +### Extending Events to New Page Elements + +It is important to note that `$.fn.on` can only create event listeners +on elements that exist *at the time you set up the listeners*. Similar elements created +after the event listeners are established will not automatically pick up event behaviors +you've set up previously. For example: + +``` + $(document).ready(function(){ + + // Sets up click behavior on all button elements with the alert class + // that exist in the DOM when the instruction was executed + $('button.alert').on('click', function(){ + console.log('A button with the alert class was clicked!'); + }); + + // Now create a new button element with the alert class. This button + // was created after the click listeners were applied above, so it + // will not have the same click behavior as its peers + $('button').addClass('alert').appendTo(document.body); + + }); + +``` + +Consult the article on event delegation to see how to use `$.fn.on` so that +event behaviors will be extended to new elements without having to rebind them. + +### Inside the Event Handler Function + +Every event handling function receives an event object, which contains many +properties and methods. The event object is most commonly used to prevent the +default action of the event via the preventDefault method. However, the event +object contains a number of other useful properties and methods, including: + +#### pageX, pageY + +The mouse position at the time the event occurred, relative to the top left corner of +the page display area (not the entire browser window). + +#### type + +The type of the event (e.g. "click"). + +#### which + +The button or key that was pressed. + +#### data + +Any data that was passed in when the event was bound. For example: + +``` +// Event setup using the `$.fn.on` method with data +$('input').on( + 'change', + {foo : 'bar'}, // associate data with event binding + function(eventObject) { + console.log('An input value has changed! ', eventObject.data.foo); + } +); +``` + +#### target + +The DOM element that initiated the event. + +#### namespace + +The namespace specified when the event was triggered. + +#### timeStamp + +The difference in milliseconds between the time the event occurred in the browser and January 1, 1970. + +#### preventDefault() + +Prevent the default action of the event (e.g. following a link). + +#### stopPropagation() + +Stop the event from bubbling up to other elements. + +In addition to the event object, the event handling function also has access to +the DOM element that the handler was bound to via the keyword `this`. To turn +the DOM element into a jQuery object that we can use jQuery methods on, we +simply do `$(this)`, often following this idiom: + +``` + var $this = $(this); +``` + +A fuller example would be: + +``` +// Preventing a link from being followed + $('a').click(function(eventObject) { + var $this = $(this); + if ($this.attr('href').match(/evil/)) { + eventObject.preventDefault(); + $this.addClass('evil'); + } + }); +``` + +### Setting Up Multiple Event Responses + +Quite often elements in your application will be bound to multiple events. If +multiple events are to share the same handling function, you can provide the event types +as a space-separated list to `$.fn.on`: + +``` +// Multiple events, same handler +$('input').on( + 'click change', // bind listeners for multiple events + function() { + console.log('An input was clicked or changed!') + } +); +``` + +When each event has its own handler, you can pass an object into `$.fn.on` with one or +more key/value pairs, with the key being the event name and the value being the function +to handle the event. + +``` +// Binding multiple events with different handlers +$('p').on({ + 'click': function() { console.log('clicked!'); }, + 'mouseover': function() { console.log('hovered!'); } +}); +``` + +### Namespacing Events + +For complex applications and for plugins you share with others, it can be +useful to namespace your events so you don't unintentionally disconnect events +that you didn't or couldn't know about. + +``` +// Namespacing events +$('p').on('click.myNamespace', function() { /* ... */ }); +$('p').off('click.myNamespace'); +$('p').off('.myNamespace'); // unbind all events in the namespace +``` + +### Tearing Down Event Listeners + +To remove an event listener, you use the `$.fn.off` method and pass in +the event type to off. If you attached a named function to the event, then +you can isolate the event tear down to just that named function by passing it as the +second argument. + +``` +// Tearing down all click handlers on a selection +$('p').off('click'); +``` + +``` +// Tearing down a particular click handler, using a reference to the function +var foo = function() { console.log('foo'); }; +var bar = function() { console.log('bar'); }; + +$('p').on('click', foo).on('click', bar); +$('p').off('click', bar); // foo is still bound to the click event +``` + +### Setting Up Events to Run Only Once + +Sometimes you need a particular handler to run only once — after that, you may +want no handler to run, or you may want a different handler to run. jQuery +provides the `$.fn.one` method for this purpose. + +``` +// Switching handlers using the `$.fn.one` method +$('p').one('click', function() { + console.log('You just clicked this for the first time!'); + // Now set up the new handler for subsequent clicks; + // omit this step if no further click responses are needed + $(this).click(function() { console.log('You have clicked this before!'); }); +}); +``` \ No newline at end of file diff --git a/page/plugins/basic-plugin-creation.md b/page/plugins/basic-plugin-creation.md index 70807dbb..fb01a1d3 100644 --- a/page/plugins/basic-plugin-creation.md +++ b/page/plugins/basic-plugin-creation.md @@ -1,24 +1,28 @@ --- -title : How to create a basic plugin +title : How to Create a Basic Plugin attribution: jQuery Fundamentals --- Sometimes you want to make a piece of functionality available throughout your code; for example, perhaps you want a single method you can call on a jQuery selection that performs a series of operations on the selection. Maybe you wrote a really useful utility function that you want to be able to move easily to other projects. In this case, you may want to write a plugin. -##How jQuery works 101 +##How jQuery Works 101: jQuery Object Methods and Utility Methods Before we write our own plugins, we must first understand a little about how jQuery works. Take a look at this code: ``` -$('a').css('color','red'); +var userColor = window.prompt('Please enter a color:', ''); +$('a').css('color', $.trim(userColor)); ``` -This is some pretty basic jQuery code, but do you know what's happening behind the scenes? Whenever you use the `$` function to select elements, it returns an object. This object contains all of the methods you've been using (`css()`, `click()`, etc.), and all of the elements that fit your selector. The `$` function gets the methods from the `$.fn` object. This object contains all of the jQuery methods, and If we want to write our own methods, it will need to contain those as well. +This is some pretty basic jQuery code, but do you know what's happening behind the scenes? Whenever you use the `$` function to select elements, it returns a jQuery object. This object contains all of the methods you've been using (`css()`, `click()`, etc.), and all of the elements that fit your selector. The jQuery object gets these methods from the `$.fn` object. This object contains all of the jQuery object methods, and if we want to write our own methods, it will need to contain those as well. -##Basic plugin authoring +Additionally the jQuery utility method `$.trim()` is used above to remove any leading or trailing empty space characters from the user input. Utility methods are functions that reside directly on +`$` function itself. You may occasionally want to write a utility method plugin when your extension to the jQuery API does not have to do something to a set of DOM elements you've retrieved. -Let's say we want to create a plugin that makes text green. All we have to do is add a function called `greenify` to `$.fn` and it will available just like any other method. +##Basic Plugin Authoring + +Let's say we want to create a plugin that makes text within a set of retrieved elements green. All we have to do is add a function called `greenify` to `$.fn` and it will available just like any other jQuery object method. ``` $.fn.greenify = function () { @@ -28,84 +32,123 @@ $.fn.greenify = function () { $('a').greenify(); // makes all the links green ``` -Notice that to use `css()`, another method, we use `this`, not `$(this)`. This is because our `greenify` function is a part of the same object as `css()`. +Notice that to use `css()`, another jQuery object method, we use `this`, not `$(this)`. This is because our `greenify` function is a part of the same jQuery object as `css()`. + +Additionally suppose we'd like to be more selective in our string trimming and provide for left trim (remove empty space characters at the beginning of the string) and right trim (remove empty space characters from the end of the string) methods. We can create `$.ltrim()` and `$.rtrim()` utility methods by attaching them directly to the `$` function: + +``` +// Attach ltrim and rtrim directly to the $ function +$.ltrim = function(str) {return str.replace(/^\s+/, '')} +$.rtrim = function(str) {return str.replace(/\s+$/, '')} + +var myString = ' jQuery plugins are fun and easy! '; +console.log($.ltrim(myString)); +console.log($.rtrim(myString)); +``` + +jQuery's `$.extend()` method provides another alternative for creating new utility methods. When `$.extend()` is passed a single object, the contents of that object are merged with the `$` function. + +``` +// Have $.extend() attach ltrim and rtrim to the $ function +$.extend({ + ltrim : function(str) {return str.replace(/^\s+/, '')}, + rtrim : function(str) {return str.replace(/\s+$/, '')} +}); + +// $.ltrim and $.rtrim defined in this snippet behave +// the same as in the snippet above +var myString = ' jQuery plugins are fun and easy! '; +console.log($.ltrim(myString)); +console.log($.rtrim(myString)); +``` ##Chaining -This works, but there's a couple of things we need to do for our plugin to survive in the real world. One of jQuery's features is chaining, when you link five or six actions onto one selector. This is accomplished by having all jQuery methods return the original jQuery object again (there are a few exeptions: `width()` called without parameters returns the width of the selected element, and is not chainable). Making our plugin chainable takes one line of code: +This works, but there's a couple of things we need to do for our plugin to survive in the real world. One of jQuery's features is chaining, when you link five or six actions onto one selector. This is accomplished by having all jQuery object methods return the original jQuery object again (there are a few exeptions: `width()` called without parameters returns the width of the selected element, and is not chainable). Making our plugin method chainable takes one line of code: ``` $.fn.greenify = function () { this.css('color','green'); - return this; + return this; // enables chaining with other jQuery object methods } $('a').greenify().addClass('greenified'); ``` -##Adding scope +Note that the notion of chaining is *not* applicable to jQuery utility methods like `$.trim()`. + -The `$` variable is very popular among javascript libraries, and if you're using one with jQuery, you will have to make jQuery not use the `$` with `jQuery.noConflict()`. However, this will break our plugin. To work well with other plugins, _and_ still use the jQuery `$` variable, we need to put all of our code inside of an [Immediately Invoked Function Expression](http://stage.learn.jquery.com/javascript-101/functions/#immediately-invoked-function-expression), and then pass the function `jQuery`, and name the parameter `$`: +##Protecting the $ Alias and Adding Scope + +The `$` variable is very popular among JavaScript libraries, and if you're using another library with jQuery, you will have to make jQuery not use the `$` with `jQuery.noConflict()`. However, this will break our plugin since it is written with the assumption that `$` is an alias to the `jQuery` function. To work well with other plugins, _and_ still use the jQuery `$` alias, we need to put all of our code inside of an [Immediately Invoked Function Expression](http://stage.learn.jquery.com/javascript-101/functions/#immediately-invoked-function-expression), and then pass the function `jQuery`, and name the parameter `$`: ``` (function ($) { + + // We can safely use the $ alias inside the Immediately Invoked Function $.fn.greenify = function () { this.css('color','green'); return this; } + + $.ltrim = function(str) {return str.replace(/^\s+/, '')} + $.rtrim = function(str) {return str.replace(/\s+$/, '')} + }(jQuery)); ``` In addition, the primary purpose of an Immediately Invoked Function is to allow us to have our own private variables. Pretend we want a different color green, and we want to store it in a variable. ``` -(function ($) { - var shade = '#556B2F'; - - $.fn.greenify = function () { - this.css('color',shade); - return this; - } -}(jQuery)); -``` - -##Minimizing Plugin Footprint - -It's good practice when writing plugins to only take up one slot within `$.fn`. This reduces both the chance that your plugin will be overriden, and the chance that your plugin will override other plugins. In other words, this is bad: - -``` -(function ($) { - $.fn.openPopup = function () { - // Open popup code - }; - - $.fn.closePopup = function () { - // Close popup code - }; - -}(jQuery)); -``` - -It would be much better to have one slot, and use parameters to control what action that one slot performs. - -``` -(function ($) { - $.fn.popup = function (action) { - if( action === 'open') { - // Open popup code - } if( action === 'close' ) { - // Close popup code - } - - }; -}(jQuery)); -``` - -##Using the each() method +(function ($) { + + var shade = '#556B2F'; // private variable + + $.fn.greenify = function () { + this.css('color', shade); + return this; + } + +}(jQuery)); +``` + +##Minimizing Plugin Footprint + +It's good practice when writing plugins to only take up one slot within `$.fn`. This reduces both the chance that your plugin will be overridden, and the chance that your plugin will override other plugins. In other words, this is bad: + +``` +(function ($) { + $.fn.openPopup = function () { + // Open popup code + }; + + $.fn.closePopup = function () { + // Close popup code + }; + +}(jQuery)); +``` + +It would be much better to have one slot, and use parameters to control what action that one slot performs. + +``` +(function ($) { + $.fn.popup = function (action) { + if( action === 'open') { + // Open popup code + } if( action === 'close' ) { + // Close popup code + } + + }; +}(jQuery)); +``` + +##Using the each() Method Your typical jQuery object will contain references to any number of DOM elements, and that's why jQuery objects are often referred to as collections. -If you want to do any manipulating with specific elements (eg: getting data an +If you want to do any manipulating with specific elements (eg: getting an attribute, calculating specific positions) then you need to use `each()` to loop through the elements. @@ -118,20 +161,24 @@ $.fn.myNewPlugin = function() { ``` Notice that we return the results of `each()` instead of returning `this`. -Since `each()` is already chainable, it returns `this`, which we then return. -This is a better way to maintain chainability than what we've been doing so far. +Since `each()` is already chainable, it returns `this`, which we then return as +the return value from our plugin method. This is a better way to maintain chainability +than what we've been doing so far. -##Accepting options +##Accepting Options -As your plugins get more and more complex, it's a good idea to make your plugin -customizable by accepting options. The easiest way do this, especially if there +As your plugins get more and more complex, it's a good idea to make them +customizable by having them accept options. The easiest way do this, especially if there are lots of options, is with an object literal. Let's change our greenify plugin to accept some options. ``` (function ($) { + $.greenify = function (options) { - // This is the easiest way to have default options. + + // This is the easiest way to have default options. $.extend + // will blend the options passed in with the defaults below var settings = $.extend( { 'color' : '#556B2F', // These are the defaults 'background-color' : 'white' @@ -142,7 +189,9 @@ accept some options. 'color': settings['color'], 'background-color': settings['background-color'] }); + }; + }(jQuery)); ``` @@ -154,9 +203,9 @@ $('div').greenify({ }); ``` -The default value for `color` of `#556B2F` gets overriden by `$.extend` to be orange. +The default value for `color` of `#556B2F` gets overriden by `$.extend()` to be orange. -##Putting it together +##Putting It Together Here's an example of a small plugin using some of the techniques we've discussed: @@ -165,7 +214,7 @@ we've discussed: (function($){ $.fn.showLinkLocation = function() { return this.filter('a').each(function(){ - $(this).append( ' (' + $(this).attr('href') + ')'); + $(this).append( ' [' + $(this).attr('href') + ']'); }); }; }(jQuery)); @@ -182,7 +231,7 @@ href attribute in brackets. Foo -Foo (page.html) +Foo [page.html] ``` Our plugin can be optimized though: @@ -191,13 +240,13 @@ Our plugin can be optimized though: (function($){ $.fn.showLinkLocation = function() { return this.filter('a').append(function(){ - return ' (' + this.href + ')'; + return ' [' + this.href + ']'; }); }; }(jQuery)); ``` -We're using the `append` method's capability to accept a callback, and the +We're using the `append()` method's capability to accept a callback, and the return value of that callback will determine what is appended to each element in the collection. Notice also that we're not using the `attr` method to retrieve the href attribute, because the native DOM API gives us easy access