With contributions by James Padolsey, Paul Irish, and others.
See the GitHub repository for a complete history of
contributions.
Copyright © 2010
Licensed by Rebecca Murphey under the Creative
Commons Attribution-Share Alike 3.0 United States license. You
are free to copy, distribute, transmit, and remix this work, provided
you attribute the work to Rebecca Murphey as the original author and
reference the GitHub
repository for the work. If you alter, transform, or build upon
this work, you may distribute the resulting work only under the same,
similar or a compatible license. Any of the above conditions can be
waived if you get permission from the copyright holder. For any reuse or
distribution, you must make clear to others the license terms of this
work. The best way to do this is with a link to
the license.
Table of Contents
- 1. Welcome
- I. JavaScript 101
- II. jQuery: Basic Concepts
-
- 3. jQuery Basics
- 4. jQuery Core
- 5. Events
- 6. Effects
- 7. Ajax
- 8. Plugins
- III. Advanced Topics
-
- This Section is a Work in Progress
- 9. Performance Best Practices
-
- Cache length during loops
- Append new content outside of a loop
- Keep things DRY
- Beware anonymous functions
- Optimize Selectors
- Use Event Delegation
- Detach Elements to Work With Them
- Use Stylesheets for Changing CSS on Many Elements
- Use
$.dataInstead of$.fn.data - Don’t Act on Absent Elements
- Variable Definition
- Conditionals
- Don’t Treat jQuery as a Black Box
- 10. Code Organization
- 11. Custom Events
List of Examples
- 1.1. An example of inline Javascript
- 1.2. An example of including external JavaScript
- 1.3. Example of an example
- 2.1. A simple variable declaration
- 2.2. Whitespace has no meaning outside of quotation marks
- 2.3. Parentheses indicate precedence
- 2.4. Tabs enhance readability, but have no special meaning
- 2.5. Concatenation
- 2.6. Multiplication and division
- 2.7. Incrementing and decrementing
- 2.8. Addition vs. concatenation
- 2.9. Forcing a string to act as a number
- 2.10. Forcing a string to act as a number (using the unary-plus
operator) - 2.11. Logical AND and OR operators
- 2.12. Comparison operators
- 2.13. Flow control
- 2.14. Values that evaluate to
true - 2.15. Values that evaluate to
false - 2.16. The ternary operator
- 2.17. A switch statement
- 2.18. Loops
- 2.19. A typical
forloop - 2.20. A typical
whileloop - 2.21. A
whileloop with a combined conditional and
incrementer - 2.22. A
do-whileloop - 2.23. Stopping a loop
- 2.24. Skipping to the next iteration of a loop
- 2.25. A simple array
- 2.26. Accessing array items by index
- 2.27. Testing the size of an array
- 2.28. Changing the value of an array item
- 2.29. Adding elements to an array
- 2.30. Working with arrays
- 2.31. Creating an “object literal”
- 2.32. Function Declaration
- 2.33. Named Function Expression
- 2.34. A simple function
- 2.35. A function that returns a value
- 2.36. A function that returns another function
- 2.37. A self-executing anonymous function
- 2.38. Passing an anonymous function as an argument
- 2.39. Passing a named function as an argument
- 2.40. Testing the type of various variables
- 2.41. Functions have access to variables defined in the same
scope - 2.42. Code outside the scope in which a variable was defined does not
have access to the variable - 2.43. Variables with the same name can exist in different scopes with
different values - 2.44. Functions can “see” changes in variable values after the function
is defined - 2.45. Scope insanity
- 2.46. How to lock in the value of
i? - 2.47. Locking in the value of
iwith a closure - 3.1. A $(document).ready() block
- 3.2. Shorthand for $(document).ready()
- 3.3. Passing a named function instead of an anonymous function
- 3.4. Selecting elements by ID
- 3.5. Selecting elements by class name
- 3.6. Selecting elements by attribute
- 3.7. Selecting elements by compound CSS selector
- 3.8. Pseudo-selectors
- 3.9. Testing whether a selection contains elements
- 3.10. Storing selections in a variable
- 3.11. Refining selections
- 3.12. Using form-related pseduo-selectors
- 3.13. Chaining
- 3.14. Formatting chained code
- 3.15. Restoring your original selection using
$.fn.end - 3.16. The
$.fn.htmlmethod used as a setter - 3.17. The html method used as a getter
- 3.18. Getting CSS properties
- 3.19. Setting CSS properties
- 3.20. Working with classes
- 3.21. Basic dimensions methods
- 3.22. Setting attributes
- 3.23. Getting attributes
- 3.24. Moving around the DOM using traversal methods
- 3.25. Iterating over a selection
- 3.26. Changing the HTML of an element
- 3.27. Moving elements using different approaches
- 3.28. Making a copy of an element
- 3.29. Creating new elements
- 3.30. Creating a new element with an attribute object
- 3.31. Getting a new element on to the page
- 3.32. Creating and adding an element to the page at the same
time - 3.33. Manipulating a single attribute
- 3.34. Manipulating multiple attributes
- 3.35. Using a function to determine an attribute’s new value
- 4.1. Checking the type of an arbitrary value
- 4.2. Storing and retrieving data related to an element
- 4.3. Storing a relationship between elements using
$.fn.data - 4.4. Putting jQuery into no-conflict mode
- 4.5. Using the $ inside a self-executing anonymous function
- 5.1. Event binding using a convenience method
- 5.2. Event biding using the
$.fn.bindmethod - 5.3. Event binding using the
$.fn.bindmethod with
data - 5.4. Switching handlers using the
$.fn.one
method - 5.5. Unbinding all click handlers on a selection
- 5.6. Unbinding a particular click handler
- 5.7. Namespacing events
- 5.8. Preventing a link from being followed
- 5.9. Triggering an event handler the right way
- 5.10. Event delegation using
$.fn.delegate - 5.11. Event delegation using
$.fn.live - 5.12. Unbinding delegated events
- 5.13. The hover helper function
- 5.14. The toggle helper function
- 6.1. A basic use of a built-in effect
- 6.2. Setting the duration of an effect
- 6.3. Augmenting
jQuery.fx.speedswith custom speed
definitions - 6.4. Running code when an animation is complete
- 6.5. Run a callback even if there were no elements to
animate - 6.6. Custom effects with
$.fn.animate - 6.7. Per-property easing
- 7.1. Using the core $.ajax method
- 7.2. Using jQuery’s Ajax convenience methods
- 7.3. Using
$.fn.loadto populate an element - 7.4. Using
$.fn.loadto populate an element based on a
selector - 7.5. Turning form data into a query string
- 7.6. Creating an array of objects containing form data
- 7.7. Using YQL and JSONP
- 7.8. Setting up a loading indicator using Ajax Events
- 8.1. Creating a plugin to add and remove a class on hover
- 8.2. The Mike Alsup jQuery Plugin Development Pattern
- 8.3. A simple, stateful plugin using the jQuery UI widget
factory - 8.4. Passing options to a widget
- 8.5. Setting default options for a widget
- 8.6. Creating widget methods
- 8.7. Calling methods on a plugin instance
- 8.8. Responding when an option is set
- 8.9. Providing callbacks for user extension
- 8.10. Binding to widget events
- 8.11. Adding a destroy method to a widget
- 10.1. An object literal
- 10.2. Using an object literal for a jQuery feature
- 10.3. The module pattern
- 10.4. Using the module pattern for a jQuery feature
- 10.5. Using RequireJS: A simple example
- 10.6. A simple JavaScript file with dependencies
- 10.7. Defining a RequireJS module that has no dependencies
- 10.8. Defining a RequireJS module with dependencies
- 10.9. Defining a RequireJS module that returns a function
- 10.10. A RequireJS build configuration file
The code we’ll be using in this book is hosted in a repository on
Github. You can download a .zip or .tar file of the code, then
uncompress it to use it on your server. If you’re git-inclined, you’re
welcome to clone or fork the repository.
Here are a few suggestions for tackling these problems:
- First, make sure you thoroughly understand the problem you’re
being asked to solve. - Next, figure out which elements you’ll need to access in order
to solve the problem, and determine how you’ll get those elements. Use
Firebug to verify that you’re getting the elements you’re
after. - Finally, figure out what you need to do with the elements to
solve the problem. It can be helpful to write comments explaining what
you’re going to do before you try to write the code to do it.
Understanding statements, variable naming, whitespace, and other
basic JavaScript syntax.
In JavaScript, numbers and strings will occasionally behave in
ways you might not expect.
Logical operators allow you to evaluate a series of operands using
AND and OR operations.
Be sure to consult the section called “Truthy and Falsy Things” for more details on
which values evaluate to true and which evaluate to
false.
Note
You’ll sometimes see developers use these logical operators for
flow control instead of using if statements. For
example:
// do something with foo if foo is truthy foo && doSomething(foo); // set bar to baz if baz is truthy; // otherwise, set it to the return // value of createBar() var bar = baz || createBar();
This style is quite elegant and pleasantly terse; that said, it
can be really hard to read, especially for beginners. I bring it up
here so you’ll recognize it in code you read, but I don’t recommend
using it until you’re extremely comfortable with what it means and how
you can expect it to behave.
Example 2.12. Comparison operators
var foo = 1; var bar = 0; var baz = '1'; var bim = 2; foo == bar; // returns false foo != bar; // returns true foo == baz; // returns true; careful! foo === baz; // returns false foo !== baz; // returns true foo === parseInt(baz); // returns true foo > bim; // returns false bim > baz; // returns true foo <= baz; // returns true
var stuffToDo = { 'bar' : function() { alert('the value was bar -- yay!'); }, 'baz' : function() { alert('boo baz :('); }, 'default' : function() { alert('everything else is just ok'); } }; if (stuffToDo[foo]) { stuffToDo[foo](); } else { stuffToDo['default'](); }
We’ll look at objects in greater depth later in this
chapter.
Loops let you run a block of code a certain number of times.
Note that in Example 2.18, “Loops” even though we use the
keyword var before the variable name i,
this does not “scope” the variable i to the loop block.
We’ll discuss scope in depth later in this chapter.
A for loop is made up of four statements and has the
following structure:
for ([initialisation]; [conditional]; [iteration]) [loopBody]
while ([conditional]) [loopBody]
Notice that we’re starting at -1 and using the prefix
incrementer (++i).
-
break -
case -
catch -
continue -
default -
delete -
do -
else -
finally -
for -
function -
if -
in -
instanceof -
new -
return -
switch -
this -
throw -
try -
typeof -
var -
void -
while -
with -
abstract -
boolean -
byte -
char -
class -
const -
debugger -
double -
enum -
export -
extends -
final -
float -
goto -
implements -
import -
int -
interface -
long -
native -
package -
private -
protected -
public -
short -
static -
super -
synchronized -
throws -
transient -
volatile
While it’s possible to change the value of an array item as shown
in Example 2.28, “Changing the value of an array item”, it’s
generally not advised.
[Definition: When one of these values is a function, it’s called a
method of the object.] Otherwise, they are
called properties.
Object literals can be extremely useful for code organization; for
more information, read Using
Objects to Organize Your Code by Rebecca Murphey.
Functions can be created in a variety of ways:
I prefer the named function expression method of setting a
function’s name, for some rather in-depth
and technical reasons. You are likely to see both methods used in
others’ JavaScript code.
Example 2.40. Testing the type of various variables
var myFunction = function() { console.log('hello'); }; var myObject = { foo : 'bar' }; var myArray = [ 'a', 'b', 'c' ]; var myString = 'hello'; var myNumber = 3; typeof myFunction; // returns 'function' typeof myObject; // returns 'object' typeof myArray; // returns 'object' -- careful! typeof myString; // returns 'string'; typeof myNumber; // returns 'number' typeof null; // returns 'object' -- careful! if (myArray.push && myArray.slice && myArray.join) { // probably an array // (this is called "duck typing") } if (Object.prototype.toString.call(myArray) === '[object Array]') { // Definitely an array! // This is widely considered as the most rebust way // to determine if a specific value is an Array. }
// a self-executing anonymous function (function() { var baz = 1; var bim = function() { alert(baz); }; bar = function() { alert(baz); }; })(); console.log(baz); // baz is not defined outside of the function bar(); // bar is defined outside of the anonymous function // because it wasn't declared with var; furthermore, // because it was defined in the same scope as baz, // it has access to baz even though other code // outside of the function does not bim(); // bim is not defined outside of the anonymous function, // so this will result in an error
In Example 2.44, “Functions can “see” changes in variable values after the function
is defined”
we saw how functions have access to changing variable values. The same
sort of behavior exists with functions defined within loops — the
function “sees” the change in the variable’s value even after the function
is defined, resulting in all clicks alerting 5.
You can also pass a named function to
$(document).ready() instead of passing an anonymous
function.
The most basic concept of jQuery is to “select some elements and do
something with them.” jQuery supports most CSS3 selectors, as well as some
non-standard selectors. For a complete selector reference, visit http://api.jquery.com/category/selectors/.
Following are a few examples of common selection techniques.
Note
jQuery.expr.filters.hidden = function( elem ) { var width = elem.offsetWidth, height = elem.offsetHeight, skip = elem.nodeName.toLowerCase() === "tr"; // does the element have 0 height, 0 width, // and it's not a <tr>? return width === 0 && height === 0 && !skip ? // then it must be hidden true : // but if it has width and height // and it's not a <tr> width > 0 && height > 0 && !skip ? // then it must be visible false : // if we get here, the element has width // and height, but it's also a <tr>, // so check its display property to // decide whether it's hidden jQuery.curCSS(elem, "display") === "none"; }; jQuery.expr.filters.visible = function( elem ) { return !jQuery.expr.filters.hidden( elem ); };
Note
In Example 3.10, “Storing selections in a variable”,
the variable name begins with a dollar sign. Unlike in other
languages, there’s nothing special about the dollar sign in JavaScript
– it’s just another character. We use it here to indicate that the
variable contains a jQuery object. This practice — a sort of Hungarian
notation — is merely convention, and is not mandatory.
Once you’ve stored your selection, you can call jQuery methods on
the variable you stored it in just like you would have called them on
the original selection.
Note
A selection only fetches the elements that are on the page when
you make the selection. If you add elements to the page later, you’ll
have to repeat the selection or otherwise add them to the selection
stored in the variable. Stored selections don’t magically update when
the DOM changes.
- :button
- Selects
<button>elements and elements
withtype="button" - :checkbox
- Selects inputs with
type="checkbox" - :checked
- Selects checked inputs
- :disabled
- Selects disabled form elements
- :enabled
- Selects enabled form elements
- :file
- Selects inputs with
type="file" - :image
- Selects inputs with
type="image" - :input
- Selects
<input>,
<textarea>, and<select>
elements - :password
- Selects inputs with
type="password" - :radio
- Selects inputs with
type="radio" - :reset
- Selects inputs with
type="reset" - :selected
- Selects options that are selected
- :submit
- Selects inputs with
type="submit" - :text
- Selects inputs with
type="text"
jQuery includes a handy way to get and set CSS properties of
elements.
The code in Example 3.21, “Basic dimensions methods”
is just a very brief overview of the dimensions functionality in jQuery;
for complete details about jQuery dimension methods, visit http://api.jquery.com/category/dimensions/.
Example 3.21. Basic dimensions methods
$('h1').width('50px'); // sets the width of all H1 elements $('h1').width(); // gets the width of the first H1 $('h1').height('50px'); // sets the height of all H1 elements $('h1').height(); // gets the height of the first H1 $('h1').position(); // returns an object containing position // information for the first H1 relative to // its "offset (positioned) parent"
For complete documentation of jQuery traversal methods, visit http://api.jquery.com/category/traversing/.
Note
Be cautious with traversing long distances in your documents –
complex traversal makes it imperative that your document’s structure
remain the same, something that’s difficult to guarantee even if you’re
the one creating the whole application from server to client. One- or
two-step traversal is fine, but you generally want to avoid traversals
that take you from one container to another.
For complete documentation of jQuery manipulation methods, visit
http://api.jquery.com/category/manipulation/.
- $.fn.html
- Get or set the html contents.
- $.fn.text
- Get or set the text contents; HTML will be stripped.
- $.fn.attr
- Get or set the value of the provided attribute.
- $.fn.width
- Get or set the width in pixels of the first element in the
selection as an integer. - $.fn.height
- Get or set the height in pixels of the first element in the
selection as an integer. - $.fn.position
- Get an object with position information for the first
element in the selection, relative to its first positioned
ancestor. This is a getter only. - $.fn.val
- Get or set the value of form elements.
There are a variety of ways to move elements around the DOM;
generally, there are two approaches:
- Select all of the div elements that have a class of
“module”. - Come up with three selectors that you could use to get the third
item in the #myList unordered list. Which is the best to use?
Why? - Select the label for the search input using an attribute
selector. - Figure out how many elements on the page are hidden (hint:
.length). - Figure out how many image elements on the page have an alt
attribute. - Select all of the odd table rows in the table body.
- Select all of the image elements on the page; log each image’s
alt attribute. - Select the search input text box, then traverse up to the form
and add a class to the form. - Select the list item inside #myList that has a class of
“current” and remove that class from it; add a class of “current” to
the next list item. - Select the select element inside #specials; traverse your way to
the submit button. - Select the first list item in the #slideshow element; add the
class “current” to it, and then add a class of “disabled” to its
sibling elements.
- Add five new list items to the end of the unordered list #myList.
Hint:for (var i = 0; i<5; i++) { ... }
- Remove the odd list items
- Add another h2 and another paragraph to the last div.module
- Add another option to the select element; give the option the
value “Wednesday” - Add a new div.module to the page after the last one; put a copy of
one of the existing images inside of it.
Until now, we’ve been dealing entirely with methods that are called
on a jQuery object. For example:
$('h1').remove();
This distinction can be incredibly confusing to new jQuery users.
Here’s what you need to remember:
- Methods called on jQuery selections are in the
$.fn
namespace, and automatically receive and return the selection as
this. - Methods in the
$namespace are generally
utility-type methods, and do not work with selections; they are not
automatically passed any arguments, and their return value will
vary.
jQuery offers several utility methods in the $
namespace. These methods are helpful for accomplishing routine programming
tasks. Below are examples of a few of the utility methods; for a complete
reference on jQuery utility methods, visit http://api.jquery.com/category/utilities/.
- $.trim
- Removes leading and trailing whitespace.
$.trim(' lots of extra whitespace '); // returns 'lots of extra whitespace' - $.each
- Iterates over arrays and objects.
$.each([ 'foo', 'bar', 'baz' ], function(idx, val) { console.log('element ' + idx + 'is ' + val); }); $.each({ foo : 'bar', baz : 'bim' }, function(k, v) { console.log(k + ' : ' + v); });Note
There is also a method
$.fn.each, which is used
for iterating over a selection of elements. - $.inArray
- Returns a value’s index in an array, or -1 if the value is not
in the array.var myArray = [ 1, 2, 3, 5 ]; if ($.inArray(4, myArray) !== -1) { console.log('found it!'); } - $.extend
- Changes the properties of the first object using the
properties of subsequent objects.var firstObject = { foo : 'bar', a : 'b' }; var secondObject = { foo : 'baz' }; var newObject = $.extend(firstObject, secondObject); console.log(firstObject.foo); // 'baz' console.log(newObject.foo); // 'baz'If you don’t want to change any of the objects you pass to
$.extend, pass an empty object as the first
argument.var firstObject = { foo : 'bar', a : 'b' }; var secondObject = { foo : 'baz' }; var newObject = $.extend({}, firstObject, secondObject); console.log(firstObject.foo); // 'bar' console.log(newObject.foo); // 'baz' - $.proxy
- Returns a function that will always run in the provided scope
— that is, sets the meaning ofthisinside the passed
function to the second argument.var myFunction = function() { console.log(this); }; var myObject = { foo : 'bar' }; myFunction(); // logs window object var myProxyFunction = $.proxy(myFunction, myObject); myProxyFunction(); // logs myObject objectIf you have an object with methods, you can pass the object
and the name of a method to return a function that will always run
in the scope of the object.var myObject = { myFn : function() { console.log(this); } }; $('#foo').click(myObject.myFn); // logs DOM element #foo $('#foo').click($.proxy(myObject, 'myFn')); // logs myObject
Example 4.1. Checking the type of an arbitrary value
var myValue = [1, 2, 3]; // Using JavaScript's typeof operator to test for primative types typeof myValue == 'string'; // false typeof myValue == 'number'; // false typeof myValue == 'undefined'; // false typeof myValue == 'boolean'; // false // Using strict equality operator to check for null myValue === null; // false // Using jQuery's methods to check for non-primative types jQuery.isFunction(myValue); // false jQuery.isPlainObject(myValue); // false jQuery.isArray(myValue); // true
Example 4.3. Storing a relationship between elements using
$.fn.data
$('#myList li').each(function() { var $li = $(this), $div = $li.find('div.content'); $li.data('contentDiv', $div); }); // later, we don't have to find the div again; // we can just read it from the list item's data var $firstLi = $('#myList li:first'); $firstLi.data('contentDiv').html('new content');
jQuery offers the $.support object, as well as the
deprecated $.browser object, for this purpose. For complete
documentation on these objects, visit http://api.jquery.com/jQuery.support/
and http://api.jquery.com/jQuery.browser/.
The $.support object is dedicated to determining what
features a browser supports; it is recommended as a more “future-proof”
method of customizing your JavaScript for different browser
environments.
The $.browser object was deprecated in favor of the
$.support object, but it will not be removed from jQuery
anytime soon. It provides direct detection of the browser brand and
version.
For details on jQuery events, visit http://api.jquery.com/category/events/.
The event handling function can receive an event object. This object
can be used to determine the nature of the event, and to prevent the
event’s default behavior.
For details on the event object, visit http://api.jquery.com/category/events/event-object/.
- pageX, pageY
- The mouse position at the time the event occurred, relative to
the top left of the page. - 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.
- target
- The DOM element that initiated the event.
- preventDefault()
- Prevent the default action of the event (e.g. following a
link). - stopPropagation()
- Stop the event from bubbling up to other elements.
var $this = $(this);
- Set the value of the search input to the text of the label
element - Add a class of “hint” to the search input
- Remove the label element
- Bind a focus event to the search input that removes the hint
text and the “hint” class - Bind a blur event to the search input that restores the hint
text and “hint” class if no search text was entered
What other considerations might there be if you were creating this
functionality for a real site?
- Hide all of the modules.
- Create an unordered list element before the first module.
- Iterate over the modules using
$.fn.each. For each
module, use the text of the h2 element as the text for a list item
that you add to the unordered list element. - Bind a click event to the list item that:
- Finally, show the first tab.
Frequently used effects are built into jQuery as methods:
- $.fn.show
- Show the selected element.
- $.fn.hide
- Hide the selected elements.
- $.fn.fadeIn
- Animate the opacity of the selected elements to 100%.
- $.fn.fadeOut
- Animate the opacity of the selected elements to 0%.
- $.fn.slideDown
- Display the selected elements with a vertical sliding
motion. - $.fn.slideUp
- Hide the selected elements with a vertical sliding
motion. - $.fn.slideToggle
- Show or hide the selected elements with a vertical sliding
motion, depending on whether the elements are currently
visible.
Note
Color-related properties cannot be animated with
$.fn.animate using jQuery out of the box. Color animations
can easily be accomplished by including the color
plugin. We’ll discuss using plugins later in the book.
[Definition: Easing describes the manner in
which an effect occurs -- whether the rate of change is steady, or
varies over the duration of the animation.] jQuery includes
only two methods of easing: swing and linear. If you want more natural
transitions in your animations, various easing plugins are
available.
As of jQuery 1.4, it is possible to do per-property easing when
using the $.fn.animate method.
For more details on easing options, see http://api.jquery.com/animate/.
jQuery provides several tools for managing animations.
- $.fn.stop
- Stop currently running animations on the selected
elements. - $.fn.delay
- Wait the specified number of milliseconds before running the
next animation.$('h1').show(300).delay(1000).hide(300);
- jQuery.fx.off
- If this value is true, there will be no transition for
animations; elements will immediately be set to the target final
state instead. This can be especially useful when dealing with older
browsers; you also may want to provide the option to your
users.
Proper use of Ajax-related jQuery methods requires understanding
some key concepts first.
- text
- For transporting simple strings
- html
- For transporting blocks of HTML to be placed on the
page - script
- For adding a new script to the page
- json
- For transporting JSON-formatted data, which can include
strings, arrays, and objectsNote
As of jQuery 1.4, if the JSON data sent by your server
isn’t properly formatted, the request may fail silently. See
http://json.org for
details on properly formatting JSON, but as a general rule,
use built-in language methods for generating JSON on the
server to avoid syntax issues. - jsonp
- For transporting JSON data from another domain
- xml
- For transporting data in a custom XML schema
I am a strong proponent of using the JSON format in most cases,
as it provides the most flexibility. It is especially useful for sending
both HTML and data at the same time.
var response; $.get('foo.php', function(r) { response = r; }); console.log(response); // undefined!
$.get('foo.php', function(response) { console.log(response); });
jQuery’s core $.ajax method is a powerful and
straightforward way of creating Ajax requests. It takes a configuration
object that contains all the instructions jQuery requires to complete
the request. The $.ajax method is particularly valuable
because it offers the ability to specify both success and failure
callbacks. Also, its ability to take a configuration object that can be
defined separately makes it easier to write reusable code. For complete
documentation of the configuration options, visit http://api.jquery.com/jQuery.ajax/.
Example 7.1. Using the core $.ajax method
$.ajax({ // the URL for the request url : 'post.php', // the data to send // (will be converted to a query string) data : { id : 123 }, // whether this is a POST or GET request type : 'GET', // the type of data we expect back dataType : 'json', // code to run if the request succeeds; // the response is passed to the function success : function(json) { $('<h1/>').text(json.title).appendTo('body'); $('<div class="content"/>') .html(json.html).appendTo('body'); }, // code to run if the request fails; // the raw request and status codes are // passed to the function error : function(xhr, status) { alert('Sorry, there was a problem!'); }, // code to run regardless of success or failure complete : function(xhr, status) { alert('The request is complete!'); } });
There are many, many options for the $.ajax method, which is
part of its power. For a complete list of options, visit http://api.jquery.com/jQuery.ajax/;
here are several that you will use frequently:
- async
- Set to
falseif the request should be sent
synchronously. Defaults totrue. Note that if you
set this option to false, your request will block execution of
other code until the response is received. - cache
- Whether to use a cached response if available. Defaults to
truefor all dataTypes except “script” and “jsonp”.
When set to false, the URL will simply have a cachebusting
parameter appended to it. - complete
- A callback function to run when the request is complete,
regardless of success or failure. The function receives the raw
request object and the text status of the request. - context
- The scope in which the callback function(s) should run
(i.e. whatthiswill mean inside the callback
function(s)). By default,thisinside the callback
function(s) refers to the object originally passed to
$.ajax. - data
- The data to be sent to the server. This can either be an
object or a query string, such as
foo=bar&baz=bim. - dataType
- The type of data you expect back from the server. By
default, jQuery will look at the MIME type of the response if no
dataType is specified. - error
- A callback function to run if the request results in an
error. The function receives the raw request object and the text
status of the request. - jsonp
- The callback name to send in a query string when making a
JSONP request. Defaults to “callback”. - success
- A callback function to run if the request succeeds. The
function receives the response data (converted to a JavaScript
object if the dataType was JSON), as well as the text status of
the request and the raw request object. - timeout
- The time in milliseconds to wait before considering the
request a failure. - traditional
- Set to true to use the param serialization style in use
prior to jQuery 1.4. For details, see http://api.jquery.com/jQuery.param/. - type
- The type of the request, “POST” or “GET”. Defaults to
“GET”. Other request types, such as “PUT” and “DELETE” can be
used, but they may not be supported by all browsers. - url
- The URL for the request.
The url option is the only required property of the
$.ajax configuration object; all other properties are
optional.
The convenience methods provided by jQuery are:
In each case, the methods take the following arguments, in
order:
- url
- The URL for the request. Required.
- data
- The data to be sent to the server. Optional. This can either
be an object or a query string, such as
foo=bar&baz=bim. - success callback
- A callback function to run if the request succeeds.
Optional. The function receives the response data (converted to a
JavaScript object if the data type was JSON), as well as the text
status of the request and the raw request object. - data type
- The type of data you expect back from the server.
Optional.
Example 7.2. Using jQuery’s Ajax convenience methods
// get plain text or html $.get('/users.php', { userId : 1234 }, function(resp) { console.log(resp); }); // add a script to the page, then run a function defined in it $.getScript('/static/js/myScript.js', function() { functionFromMyScript(); }); // get JSON-formatted data from the server $.getJSON('/details.php', function(resp) { $.each(resp, function(k, v) { console.log(k + ' : ' + v); }); });
jQuery’s ajax capabilities can be especially useful when dealing
with forms. The jQuery
Form Plugin is a well-tested tool for adding Ajax capabilities to
forms, and you should generally use it for handling forms with Ajax rather
than trying to roll your own solution for anything remotely complex. That
said, there are a two jQuery methods you should know that relate to form
processing in jQuery: $.fn.serialize and
$.fn.serializeArray.
The advent of JSONP — essentially a consensual cross-site scripting
hack — has opened the door to powerful mashups of content. Many prominent
sites provide JSONP services, allowing you access to their content via a
predefined API. A particularly great source of JSONP-formatted data is the
Yahoo! Query
Language, which we’ll use in the following example to fetch news
about cats.
Example 7.7. Using YQL and JSONP
$.ajax({ url : 'http://query.yahooapis.com/v1/public/yql', // the name of the callback parameter, // as specified by the YQL service jsonp : 'callback', // tell jQuery we're expecting JSONP dataType : 'jsonp', // tell YQL what we want and that we want JSON data : { q : 'select title,abstract,url from search.news where query="cat"', format : 'json' }, // work with the response success : function(response) { console.log(response); } });
Often, you’ll want to perform an operation whenever an Ajax requests
starts or stops, such as showing or hiding a loading indicator. Rather
than defining this behavior inside every Ajax request, you can bind Ajax
events to elements just like you’d bind other events. For a complete list
of Ajax events, visit http://docs.jquery.com/Ajax_Events.
- Create a target div after the headline for each blog post and
store a reference to it on the headline element using
$.fn.data. - Bind a click event to the headline that will use the
$.fn.loadmethod to load the appropriate content from
/exercises/data/blog.htmlinto the target div.
Don’t forget to prevent the default action of the click event.
var href = 'blog.html#post1'; var tempArray = href.split('#'); var id = '#' + tempArray[1];
Remember to make liberal use of console.log to make sure
you’re on the right path!
- Append a target div after the form that’s inside the #specials
element; this will be where you put information about the special once
you receive it. - Bind to the change event of the select element; when the user
changes the selection, send an Ajax request to
/exercises/data/specials.json. - When the request returns a response, use the value the user
selected in the select (hint:$.fn.val) to look up
information about the special in the JSON response. - Add some HTML about the special to the target div you
created. - Finally, because the form is now Ajax-enabled, remove the submit
button from the form.
The notation for creating a typical plugin is as follows:
(function($){ $.fn.myNewPlugin = function() { return this.each(function(){ // do something }); }; }(jQuery));
$.fn.myNewPlugin = function() { //...
We wrap this assignment in an immediately-invoked function:
(function($){ //... }(jQuery));
So our actual plugin, thus far, is this:
$.fn.myNewPlugin = function() { return this.each(function(){ // do something }); };
var somejQueryObject = $('#something'); $.fn.myNewPlugin = function() { alert(this === somejQueryObject); }; somejQueryObject.myNewPlugin(); // alerts 'true'
$.fn.myNewPlugin = function() { return this.each(function(){ }); };
(function($){ $.fn.showLinkLocation = function() { return this.filter('a').each(function(){ $(this).append( ' (' + $(this).attr('href') + ')' ); }); }; }(jQuery)); // Usage example: $('a').showLinkLocation();
<!-- Before plugin is called: --> <a href="page.html">Foo</a> <!-- After plugin is called: --> <a href="page.html">Foo (page.html)</a>
Our plugin can be optimised though:
(function($){ $.fn.showLinkLocation = function() { return this.filter('a').append(function(){ return ' (' + this.href + ')'; }); }; }(jQuery));
(function($){ $.fn.fadeInAndAddClass = function(duration, className) { return this.fadeIn(duration, function(){ $(this).addClass(className); }); }; }(jQuery)); // Usage example: $('a').fadeInAndAddClass(400, 'finishedFading');
When looking for a plugin to fill a need, do your homework. Ensure
that the plugin is well-documented, and look for the author to provide
lots of examples of its use. Be wary of plugins that do far more than you
need; they can end up adding substantial overhead to your page. For more
tips on spotting a subpar plugin, read Signs
of a poorly written jQuery plugin by Remy Sharp.
Once you choose a plugin, you’ll need to add it to your page.
Download the plugin, unzip it if necessary, place it your application’s
directory structure, then include the plugin in your page using a script
tag (after you include jQuery).
Here is an example of a simple plugin:
For more on plugin development, read Mike Alsup’s essential post,
A
Plugin Development Pattern. In it, he creates a plugin called
$.fn.hilight, which provides support for the metadata plugin
if it’s present, and provides a centralized method for setting global and
instance options for the plugin.
Example 8.2. The Mike Alsup jQuery Plugin Development Pattern
// // create closure // (function($) { // // plugin definition // $.fn.hilight = function(options) { debug(this); // build main options before element iteration var opts = $.extend({}, $.fn.hilight.defaults, options); // iterate and reformat each matched element return this.each(function() { $this = $(this); // build element specific options var o = $.meta ? $.extend({}, opts, $this.data()) : opts; // update element styles $this.css({ backgroundColor: o.background, color: o.foreground }); var markup = $this.html(); // call our format function markup = $.fn.hilight.format(markup); $this.html(markup); }); }; // // private function for debugging // function debug($obj) { if (window.console && window.console.log) window.console.log('hilight selection count: ' + $obj.size()); }; // // define and expose our format function // $.fn.hilight.format = function(txt) { return '<strong>' + txt + '</strong>'; }; // // plugin defaults // $.fn.hilight.defaults = { foreground: 'red', background: 'yellow' }; // // end of closure // })(jQuery);
Note
This section is based, with permission, on the blog post Building
Stateful jQuery Plugins by Scott Gonzalez.
While most existing jQuery plugins are stateless — that is, we
call them on an element and that is the extent of our interaction with the
plugin — there’s a large set of functionality that doesn’t fit into the
basic plugin pattern.
In order to fill this gap, jQuery UI has implemented a more advanced
plugin system. The new system manages state, allows multiple functions to
be exposed via a single plugin, and provides various extension points.
This system is called the widget factory and is exposed as
jQuery.widget as part of jQuery UI 1.8; however, it can be
used independently of jQuery UI.
To demonstrate the capabilities of the widget factory, we’ll build a
simple progress bar plugin.
To start, we’ll create a progress bar that just lets us set the
progress once. As we can see below, this is done by calling
jQuery.widget with two parameters: the name of the plugin to
create and an object literal containing functions to support our plugin.
When our plugin gets called, it will create a new plugin instance and all
functions will be executed within the context of that instance. This is
different from a standard jQuery plugin in two important ways. First, the
context is an object, not a DOM element. Second, the context is always a
single object, never a collection.
Example 8.6. Creating widget methods
$.widget("nmk.progressbar", { options: { value: 0 }, _create: function() { var progress = this.options.value + "%"; this.element .addClass("progressbar") .text(progress); }, // create a public method value: function(value) { // no value passed, act as a getter if (value === undefined) { return this.options.value; // value passed, act as a setter } else { this.options.value = this._constrain(value); var progress = this.options.value + "%"; this.element.text(progress); } }, // create a private method _constrain: function(value) { if (value > 100) { value = 100; } if (value < 0) { value = 0; } return value; } });
Example 8.8. Responding when an option is set
$.widget("nmk.progressbar", { options: { value: 0 }, _create: function() { this.element.addClass("progressbar"); this._update(); }, _setOption: function(key, value) { this.options[key] = value; this._update(); }, _update: function() { var progress = this.options.value + "%"; this.element.text(progress); } });
Example 8.9. Providing callbacks for user extension
$.widget("nmk.progressbar", { options: { value: 0 }, _create: function() { this.element.addClass("progressbar"); this._update(); }, _setOption: function(key, value) { this.options[key] = value; this._update(); }, _update: function() { var progress = this.options.value + "%"; this.element.text(progress); if (this.options.value == 100) { this._trigger("complete", null, { value: 100 }); } } });
Example 8.10. Binding to widget events
var bar = $("<div></div>") .appendTo("body") .progressbar({ complete: function(event, data) { alert( "Callbacks are great!" ); } }) .bind("progressbarcomplete", function(event, data) { alert("Events bubble and support many handlers for extreme flexibility."); alert("The progress bar value is " + data.value); }); bar.progressbar("option", "value", 100);
Example 8.11. Adding a destroy method to a widget
$.widget( "nmk.progressbar", { options: { value: 0 }, _create: function() { this.element.addClass("progressbar"); this._update(); }, _setOption: function(key, value) { this.options[key] = value; this._update(); }, _update: function() { var progress = this.options.value + "%"; this.element.text(progress); if (this.options.value == 100 ) { this._trigger("complete", null, { value: 100 }); } }, destroy: function() { this.element .removeClass("progressbar") .text(""); // call the base destroy function $.Widget.prototype.destroy.call(this); } });
Please visit http://github.com/rmurphey/jqfundamentals
to contribute!
This chapter covers a number of jQuery and JavaScript best practices,
in no particular order. Many of the best practices in this chapter are based
on the jQuery Anti-Patterns for
Performance presentation by Paul Irish.
In a for loop, don’t access the length property of an array every
time; cache it beforehand.
var myLength = myArray.length; for (var i = 0; i < myLength; i++) { // do stuff }
Don’t repeat yourself; if you’re repeating yourself, you’re doing it
wrong.
// BAD if ($eventfade.data('currently') != 'showing') { $eventfade.stop(); } if ($eventhover.data('currently') != 'showing') { $eventhover.stop(); } if ($spans.data('currently') != 'showing') { $spans.stop(); } // GOOD!! var $elems = [$eventfade, $eventhover, $spans]; $.each($elems, function(i,elem) { if (elem.data('currently') != 'showing') { elem.stop(); } });
Beginning your selector with an ID is always best.
// fast $('#container div.robotarm'); // super-fast $('#container').find('div.robotarm');
Be specific on the right-hand side of your selector, and less
specific on the left.
// unoptimized $('div.data .gonzalez'); // optimized $('.data td.gonzalez');
Use tag.class if possible on your right-most
selector, and just tag or just .class on the
left.
$('.data table.attendees td.gonzalez'); // better: drop the middle if possible $('.data td.gonzalez');
Selections that specify or imply that a match could be found
anywhere can be very slow.
$('.buttons > *'); // extremely expensive $('.buttons').children(); // much better $('.gender :radio'); // implied universal selection $('.gender *:radio'); // same thing, explicit now $('.gender input:radio'); // much better
Variables can be defined in one statement instead of several.
// old & busted var test = 1; var test2 = function() { ... }; var test3 = test2(test); // new hotness var test = 1, test2 = function() { ... }, test3 = test2(test);
In self-executing functions, variable definition can be skipped all
together.
(function(foo, bar) { ... })(1, 2);
- Your code should be divided into units of functionality —
modules, services, etc. Avoid the temptation to have all of your
code in one huge$(document).ready()block. This
concept, loosely, is known as encapsulation. - Don’t repeat yourself. Identify similarities among pieces of
functionality, and use inheritance techniques to avoid repetitive
code. - Despite jQuery’s DOM-centric nature, JavaScript applications
are not all about the DOM. Remember that not all pieces of
functionality need to — or should — have a DOM
representation. - Units of functionality should be loosely
coupled — a unit of functionality should be able to exist on
its own, and communication between units should be handled via a
messaging system such as custom events or pub/sub. Stay away from
direct communication between units of functionality whenever
possible.
The concept of loose coupling can be especially troublesome to
developers making their first foray into complex applications, so be
mindful of this as you’re getting started.
Example 10.1. An object literal
var myFeature = { myProperty : 'hello', myMethod : function() { console.log(myFeature.myProperty); }, init : function(settings) { myFeature.settings = settings; }, readSettings : function() { console.log(myFeature.settings); } }; myFeature.myProperty; // 'hello' myFeature.myMethod(); // logs 'hello' myFeature.init({ foo : 'bar' }); myFeature.readSettings(); // logs { foo : 'bar' }
// clicking on a list item loads some content // using the list item's ID and hides content // in sibling list items $(document).ready(function() { $('#myFeature li') .append('<div/>') .click(function() { var $this = $(this); var $div = $this.find('div'); $div.load('foo.php?item=' + $this.attr('id'), function() { $div.show(); $this.siblings() .find('div').hide(); } ); }); });
Example 10.2. Using an object literal for a jQuery feature
var myFeature = { init : function(settings) { myFeature.config = { $items : $('#myFeature li'), $container : $('<div class="container"></div>'), urlBase : '/foo.php?item=' }; // allow overriding the default config $.extend(myFeature.config, settings); myFeature.setup(); }, setup : function() { myFeature.config.$items .each(myFeature.createContainer) .click(myFeature.showItem); }, createContainer : function() { var $i = $(this), $c = myFeature.config.$container.clone() .appendTo($i); $i.data('container', $c); }, buildUrl : function() { return myFeature.config.urlBase + myFeature.$currentItem.attr('id'); }, showItem : function() { var myFeature.$currentItem = $(this); myFeature.getContent(myFeature.showContent); }, getContent : function(callback) { var url = myFeature.buildUrl(); myFeature.$currentItem .data('container').load(url, callback); }, showContent : function() { myFeature.$currentItem .data('container').show(); myFeature.hideContent(); }, hideContent : function() { myFeature.$currentItem.siblings() .each(function() { $(this).data('container').hide(); }); } }; $(document).ready(myFeature.init);
- We’ve broken our feature up into tiny methods. In the future,
if we want to change how content is shown, it’s clear where to
change it. In the original code, this step is much harder to
locate. - We’ve eliminated the use of anonymous functions.
- We’ve moved configuration options out of the body of the code
and put them in a central location. - We’ve eliminated the constraints of the chain, making the code
easier to refactor, remix, and rearrange.
Example 10.3. The module pattern
var feature =(function() { // private variables and functions var privateThing = 'secret', publicThing = 'not secret', changePrivateThing = function() { privateThing = 'super secret'; }, sayPrivateThing = function() { console.log(privateThing); changePrivateThing(); }; // public API return { publicThing : publicThing, sayPrivateThing : sayPrivateThing } })(); feature.publicThing; // 'not secret' feature.sayPrivateThing(); // logs 'secret' and changes the value // of privateThing
Example 10.4. Using the module pattern for a jQuery feature
$(document).ready(function() { var feature = (function() { var $items = $('#myFeature li'), $container = $('<div class="container"></div>'), $currentItem, urlBase = '/foo.php?item=', createContainer = function() { var $i = $(this), $c = $container.clone().appendTo($i); $i.data('container', $c); }, buildUrl = function() { return urlBase + $currentItem.attr('id'); }, showItem = function() { var $currentItem = $(this); getContent(showContent); }, showItemByIndex = function(idx) { $.proxy(showItem, $items.get(idx)); }, getContent = function(callback) { $currentItem.data('container').load(buildUrl(), callback); }, showContent = function() { $currentItem.data('container').show(); hideContent(); }, hideContent = function() { $currentItem.siblings() .each(function() { $(this).data('container').hide(); }); }; $items .each(createContainer) .click(showItem); return { showItemByIndex : showItemByIndex }; })(); feature.showItemByIndex(0); });
Note
This section is based heavily on the excellent RequireJS
documentation at http://requirejs.org/docs/jquery.html,
and is used with the permission of RequireJS author James Burke.
When a project reaches a certain size, managing the script modules
for a project starts to get tricky. You need to be sure to sequence the
scripts in the right order, and you need to start seriously thinking about
combining scripts together into a bundle for deployment, so that only one
or a very small number of requests are made to load the scripts. You may
also want to load code on the fly, after page load.
RequireJS, a dependency management tool by James Burke, can help you
manage the script modules, load them in the right order, and make it easy
to combine the scripts later via the RequireJS optimization tool without
needing to change your markup. It also gives you an easy way to load
scripts after the page has loaded, allowing you to spread out the download
size over time.
RequireJS has a module system that lets you define well-scoped
modules, but you do not have to follow that system to get the benefits of
dependency management and build-time optimizations. Over time, if you
start to create more modular code that needs to be reused in a few places,
the module format for RequireJS makes it easy to write encapsulated code
that can be loaded on the fly. It can grow with you, particularly if you
want to incorporate internationalization (i18n) string bundles, to
localize your project for different languages, or load some HTML strings
and make sure those strings are available before executing code, or even
use JSONP services as dependencies.
The easiest way to use RequireJS with jQuery is to download a build of
jQuery that has RequireJS built in. This build excludes portions
of RequireJS that duplicate jQuery functionality. You may also find it
useful to download a
sample jQuery project that uses RequireJS.
Example 10.7. Defining a RequireJS module that has no dependencies
require.def("my/simpleshirt", { color: "black", size: "unisize" } );
This example would be stored in a my/simpleshirt.js file.
my/cart.js my/inventory.js my/shirt.js
Modules do not have to return objects; any valid return value
from a function is allowed.
requirejs/ (used for the build tools) webapp/app.html webapp/scripts/app.js webapp/scripts/require-jquery.js webapp/scripts/jquery.alpha.js webapp/scripts/jquery.beta.js
To start the build, go to the webapp/scripts directory, execute
the following command:
# non-windows systems ../../requirejs/build/build.sh app.build.js # windows systems ..\..\requirejs\build\build.bat app.build.js
var myPortlet = Portlet({ title : 'Curry', source : 'data/html/curry.html', initialState : 'open' // or 'closed' }); myPortlet.$element.appendTo('body');
myPortlet.open(); // force open state myPortlet.close(); // force close state myPortlet.toggle(); // toggle open/close state myPortlet.refresh(); // refresh the content myPortlet.destroy(); // remove the portlet from the page myPortlet.setSource('data/html/onions.html'); // change the source
<div class="room" id="kitchen"> <div class="lightbulb on"></div> <div class="switch"></div> <div class="switch"></div> <div class="clapper"></div> </div>
Without custom events, you might write some code like this:
$('.switch, .clapper').click(function() { var $light = $(this).parent().find('.lightbulb'); if ($light.hasClass('on')) { $light.removeClass('on').addClass('off'); } else { $light.removeClass('off').addClass('on'); } });
With custom events, your code might look more like this:
$('.lightbulb').bind('changeState', function(e) { var $light = $(this); if ($light.hasClass('on')) { $light.removeClass('on').addClass('off'); } else { $light.removeClass('off').addClass('on'); } }); $('.switch, .clapper').click(function() { $(this).parent().find('.lightbulb').trigger('changeState'); });
<div class="room" id="kitchen"> <div class="lightbulb on"></div> <div class="switch"></div> <div class="switch"></div> <div class="clapper"></div> </div> <div class="room" id="bedroom"> <div class="lightbulb on"></div> <div class="switch"></div> <div class="switch"></div> <div class="clapper"></div> </div> <div id="master_switch"></div>
$('.lightbulb') .bind('changeState', function(e) { var $light = $(this); if ($light.hasClass('on')) { $light.trigger('turnOff'); } else { $light.trigger('turnOn'); } }) .bind('turnOn', function(e) { $(this).removeClass('off').addClass('on'); }) .bind('turnOff', function(e) { $(this).removeClass('off').addClass('on'); }); $('.switch, .clapper').click(function() { $(this).parent().find('.lightbulb').trigger('changeState'); }); $('#master_switch').click(function() { if ($('.lightbulb.on').length) { $('.lightbulb').trigger('turnOff'); } else { $('.lightbulb').trigger('turnOn'); } });
When we’re done, it will look like this:
We’ll start with some basic HTML:
<h1>Twitter Search</h1> <input type="button" id="get_trends" value="Load Trending Terms" /> <form> <input type="text" class="input_text" id="search_term" /> <input type="submit" class="input_submit" value="Add Search Term" /> </form> <div id="twitter"> <div class="template results"> <h2>Search Results for <span class="search_term"></span></h2> </div> </div>
- 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.
$.fn.twitterResult = function(settings) { return $(this).each(function() { var $results = $(this), $actions = $.fn.twitterResult.actions = $.fn.twitterResult.actions || $.fn.twitterResult.createActions(), $a = $actions.clone().prependTo($results), term = settings.term; $results.find('span.search_term').text(term); $.each( ['refresh', 'populate', 'remove', 'collapse', 'expand'], function(i, ev) { $results.bind( 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 $('<ul class="actions" />').append( '<li class="refresh">Refresh</li>' + '<li class="remove">Remove</li>' + '<li class="collapse">Collapse</li>' ); }; $.fn.twitterResult.events = { refresh : function(e) { // indicate that the results are refreshing var $this = $(this).addClass('refreshing'); $this.find('p.tweet').remove(); $results.append('<p class="loading">Loading ...</p>'); // get the twitter data using jsonp $.getJSON( 'http://search.twitter.com/search.json?q=' + escape(e.data.term) + '&rpp=5&callback=?', function(json) { $this.trigger('populate', [ json ]); } ); }, populate : function(e, json) { var results = json.results; var $this = $(this); $this.find('p.loading').remove(); $.each(results, function(i,result) { var tweet = '<p class="tweet">' + '<a href="http://twitter.com/' + result.from_user + '">' + result.from_user + '</a>: ' + result.text + ' <span class="date">' + result.created_at + '</span>' + '</p>'; $this.append(tweet); }); // indicate that the results // are done refreshing $this.removeClass('refreshing'); }, remove : function(e, force) { if ( !force && !confirm('Remove panel for term ' + e.data.term + '?') ) { return; } $(this).remove(); // indicate that we no longer // have a panel for the term search_terms[e.data.term] = 0; }, collapse : function(e) { $(this).find('li.collapse').removeClass('collapse') .addClass('expand').text('Expand'); $(this).addClass('collapsed'); }, expand : function(e) { $(this).find('li.expand').removeClass('expand') .addClass('collapse').text('Collapse'); $(this).removeClass('collapsed'); } };
The Twitter container itself will have just two custom events:
- getResults
- Receives a search term and checks to determine whether
there’s already a results container for the term; if not, adds a
results container using the results template, set up the results
container using the$.fn.twitterResultplugin
discussed above, and then triggers therefresh
event on the results container in order to actually load the
results. Finally, it will store the search term so the
application knows not to re-fetch the term. - getTrends
- Queries Twitter for the top 10 trending terms, then
iterates over them and triggers thegetResults
event for each of them, thereby adding a results container for
each term.
Here’s how the Twitter container bindings look:
$('#twitter') .bind('getResults', function(e, term) { // make sure we don't have a box for this term already if (!search_terms[term]) { var $this = $(this); var $template = $this.find('div.template'); // make a copy of the template div // and insert it as the first results box $results = $template.clone(). removeClass('template'). insertBefore($this.find('div:first')). twitterResult({ 'term' : term }); // load the content using the "refresh" // custom event that we bound to the results container $results.trigger('refresh'); search_terms[term] = 1; } }) .bind('getTrends', function(e) { var $this = $(this); $.getJSON('http://search.twitter.com/trends.json?callback=?', function(json) { var trends = json.trends; $.each(trends, function(i, trend) { $this.trigger('getResults', [ trend.name ]); }); }); });
$('form').submit(function(e) { e.preventDefault(); var term = $('#search_term').val(); $('#twitter').trigger('getResults', [ term ]); }); $('#get_trends').click(function() { $('#twitter').trigger('getTrends'); });
$.each(['refresh', 'expand', 'collapse'], function(i, ev) { $('#' + ev).click(function(e) { $('#twitter div.results').trigger(ev); }); }); $('#remove').click(function(e) { if (confirm('Remove all results?')) { $('#twitter div.results').trigger('remove', [ true ]); } });
Copyright
Rebecca Murphey, released under the
Creative Commons Attribution-Share Alike 3.0 United States license.

Hi I like this article and it was so fabulous and I am definetly going to save it. I Have to say the Superb analysis this article has is trully remarkable.Who goes that extra mile these days? Bravo :) Just another suggestion you caninstall a Translator for your Worldwide Readers !!!
good call – I think I will
Sometimes I lie awake at night, and I ask, ‘Where have I gone wrong?’ Then a voice says to me, ‘This is going to take more than one night.’ !l13w!
Way to focus and straight to your point, i love it. Keep up the work people. Dont let anyone stop us bloggers.
Thank you for give very nice info What a cool site.
Oh, this is quite interesting. Actulally, I found your blog on google search. I will tell my friend about your blog later.
Can I make a suggestion? I think youve got something good here. But what if you added a couple links to a page that backs up what youre saying? Or maybe you could give us something to look at, something that would connect what youre saying to something tangible? Just a suggestion.
Feel free to Submit stuff you find – I will be slowing down with new posts due to a heavier work load
The new Zune browser is surprisingly good, but not as good as the iPod’s. It works well, but isn’t as fast as Safari, and has a clunkier interface. If you occasionally plan on using the web browser that’s not an issue, but if you’re planning to browse the web alot from your PMP then the iPod’s larger screen and better browser may be important.
Cool post, thank you for the info – I dont really ever post on these thingy’s but enjoyed the info. Awesome stuff!, I bookmarked your blog!
Greetings, I I stumbled upon your site on yahoo and browse pretty much all your other pages. I just added you to my RSS feeder. Keep up the amazing job. Looking forward to reading more from you in the future. You know, I must say, I really enjoy this site and the insight from everyone who participates. I find it to be refreshing and very informative. I wish there were more blogs like it. Anyway, I felt it was about time I posted, I?ve spent most of my time here just lurking and reading, but today for some reason I just felt compelled to say this.
Hello there, You’ve done an incredible job. I’ll certainly digg it and personally suggest to my friends. I am sure they will be benefited from this website.
Your post fashion and blog page template are not harmonious, transform a good blog’s template.
I am constantly looking online for posts that can facilitate me. Thank you!
Hi there, You have done an excellent job. I’ll definitely digg it and personally suggest to my friends. I’m sure they’ll be benefited from this site.
Interesting thoughts here. I appreciate you taking the time to share them with us all. It’s people like you that make my day :)
“~. I am very thankful to this topic because it really gives up to date information “`.
:”‘ that seems to be a great topic, i really love it .~”
Great beat ! I would like to apprentice at the same time as you amend your website, how could i subscribe for a weblog site? The account helped me a applicable deal. I were tiny bit acquainted of this your broadcast offered vibrant transparent idea