Unobtrusive JavaScript
with jQuery
Simon Willison
XTech , 6th May 2008
How I learned to stop worrying
and love JavaScript
Unobtrusive A set of principles for
writing accessible,
JavaScript maintainable JavaScript
A library that supports
unobtrusive scripting
Unobtrusive JavaScript Theory
jQuery Practice
We will cover
• The what and why of unobtrusive JavaScript
• Why use a library at all?
• Why pick jQuery?
• How jQuery handles...
• DOM manipulation
• Event handling
• Animation
• Ajax
Unobtrusive JavaScript
Progressive enhancement
Rather than hoping for graceful degradation, PE
builds documents for the least capable or
differently capable devices first, then moves on to
enhance those documents with separate logic for
presentation, in ways that don't place an undue
burden on baseline devices but which allow a
richer experience for those users with modern
graphical browser software.
Steven Champeon and Nick Finck, 2003
Applied to JavaScript
• Build a site that works without JavaScript
• Use JavaScript to enhance that site to
provide a better user experience: easier to
interact with, faster, more fun
• Start with Plain Old Semantic HTML
• Layer on some CSS (in an external
stylesheet) to apply the site’s visual design
• Layer on some JavaScript (in an
external script file) to apply the
site’s enhanced behaviour
Surely everyone has
JavaScript these days?
• There are legitimate reasons to switch it off
• Some companies strip JavaScript at the
firewall
• Some people run the NoScript Firefox
extension to protect themselves from
common XSS and CSRF vulnerabilities
• Many mobile devices ignore JS entirely
• Screen readers DO execute JavaScript, but
accessibility issues mean that you may not
want them to
The NoScript extension
Unobtrusive examples
labels.js
• One of the earliest examples of this
technique, created by Aaron Boodman (now
of Greasemonkey and Google Gears fame)
How it works
<label for="search">Search</label>
<input type="text" id="search" name="q">
• Once the page has loaded, the JavaScript:
• Finds any label elements linked to a text field
• Moves their text in to the associated text field
• Removes them from the DOM
• Sets up the event handlers to remove the
descriptive text when the field is focused
Django filter lists
• Large multi-select boxes aren't much fun
• Painful to scroll through
• Easy to lose track of what you have
selected
• Django's admin interface uses unobtrusive
JavaScript to improve the usability here
http://www.neighbourhoodfixit.com/
Implementing Terms and Conditions
Bad
Have you read our
<a href="javascript:window.open(
'terms.html', 'popup',
'height=500,width=400,toolbar=no'
);">terms and conditions</a>?
Also bad
Have you read our
<a href="#" onclick="window.open(
'terms.html', 'popup',
'height=500,width=400,toolbar=no'
); return false;"
>terms and conditions</a>?
Better
Have you read our
<a href="terms.html"
onclick="window.open(
'terms.html', 'popup',
'height=500,width=400,toolbar=no'
); return false;"
>terms and conditions</a>?
Better
Have you read our
<a href="terms.html"
onclick="window.open(
this.href, 'popup',
'height=500,width=400,toolbar=no'
); return false;"
>terms and conditions</a>?
Best
Have you read our
<a href="terms.html"
class="sidenote"
>terms and conditions</a>?
Characteristics of
unobtrusive scripts
• No in-line event handlers
• All code is contained in external .js files
• The site remains usable without JavaScript
• Existing links and forms are repurposed
• JavaScript dependent elements are
dynamically added to the page
JavaScript for sidenotes
• When the page has finished loading...
• Find all links with class “sidenote”
• When they’re clicked:
• Launch a popup window containing the
linked page
• Don’t navigate to the page
With JavaScript
window.onload = function() {
var links = document.getElementsByTagName('a');
for (var i = 0, link; link = links[i]; i++) {
if (link.className == 'sidenote') {
link.onclick = function() {
var href = this.href;
window.open(this.href, 'popup',
'height=500,width=400,toolbar=no');
return false;
}
}
}
}
With JavaScript
window.onload = function() {
var links = document.getElementsByTagName('a');
for (var i = 0, link; link = links[i]; i++) {
if (link.className == 'sidenote') {
link.onclick = function() {
var href = this.href;
window.open(this.href, 'popup',
'height=500,width=400,toolbar=no');
return false;
}
}
}
}
With JavaScript
window.onload = function() {
var links = document.getElementsByTagName('a');
for (var i = 0, link; link = links[i]; i++) {
if (link.className == 'sidenote') {
link.onclick = function() {
var href = this.href;
window.open(this.href, 'popup',
'height=500,width=400,toolbar=no');
return false;
}
}
}
}
With JavaScript
window.onload = function() {
var links = document.getElementsByTagName('a');
for (var i = 0, link; link = links[i]; i++) {
if (link.className == 'sidenote') {
link.onclick = function() {
var href = this.href;
window.open(href, 'popup',
'height=500,width=400,toolbar=no');
return false;
}
}
}
}
Problems
Problems
• This only executes when the page has
completely loaded, including all images
• It over-writes existing load or click handlers
• It can’t handle class="sidenote external"
• It leaks memory in IE 6
Problems
• This only executes when the page has
completely loaded, including all images
• It over-writes existing load or click handlers
• It can’t handle class="sidenote external"
• It leaks memory in IE 6
• Solving these problems requires cross-
browser workarounds. That’s where libraries
come in
With jQuery
jQuery(document).ready(function() {
$('a.sidenote').click(function() {
var href = $(this).attr('href');
window.open(href, 'popup',
'height=500,width=400,toolbar=no');
return false;
});
});
With jQuery
jQuery(document).ready(function() {
jQuery('a.sidenote').click(function() {
var href = $(this).attr('href');
window.open(href, 'popup',
'height=500,width=400,toolbar=no');
return false;
});
});
With jQuery
jQuery(document).ready(function() {
jQuery('a.sidenote').click(function() {
var href = jQuery(this).attr('href');
window.open(href, 'popup',
'height=500,width=400,toolbar=no');
return false;
});
});
With jQuery
jQuery(document).ready(function() {
jQuery('a.sidenote').click(function() {
var href = jQuery(this).attr('href');
window.open(href, 'popup',
'height=500,width=400,toolbar=no');
return false;
});
});
With jQuery
jQuery(function() {
jQuery('a.sidenote').click(function() {
var href = jQuery(this).attr('href');
window.open(href, 'popup',
'height=500,width=400,toolbar=no');
return false;
});
});
With jQuery
$(function() {
$('a.sidenote').click(function() {
var href = $(this).attr('href');
window.open(href, 'popup',
'height=500,width=400,toolbar=no');
return false;
});
});
With jQuery
jQuery(function($) {
$('a.sidenote').click(function() {
var href = $(this).attr('href');
window.open(href, 'popup',
'height=500,width=400,toolbar=no');
return false;
});
});
Advantages
• jQuery(document).ready() executes as soon
as the DOM is ready
• $('a.sidenote') uses a CSS selector to
traverse the DOM
• .click(function() { ... }) deals with cross-
browser event handling for us
• It also avoids IE memory leaks
Why jQuery instead of $X?
• Unlike Prototype and mooTools...
• ... it doesn’t clutter your global namespace
• Unlike YUI...
• it’s succinct
• YAHOO.util.Dom.getElementsByClassName()
• Unlike Dojo...
• ... the learning curve is hours, not days
jQuery characteristics
jQuery characteristics
• Minimal namespace impact (one symbol)
jQuery characteristics
• Minimal namespace impact (one symbol)
• Focus on the interaction between JavaScript and HTML
jQuery characteristics
• Minimal namespace impact (one symbol)
• Focus on the interaction between JavaScript and HTML
• (Almost) every operation boils down to:
• Find some elements
• Do things with them
jQuery characteristics
• Minimal namespace impact (one symbol)
• Focus on the interaction between JavaScript and HTML
• (Almost) every operation boils down to:
• Find some elements
• Do things with them
• Method chaining for shorter code
jQuery characteristics
• Minimal namespace impact (one symbol)
• Focus on the interaction between JavaScript and HTML
• (Almost) every operation boils down to:
• Find some elements
• Do things with them
• Method chaining for shorter code
• Extensible with plugins
Essential tools
Firebug extension for Firefox
“Inject jQuery” bookmarklet
http://icanhaz.com/xtechjs/
jQuery API docs, inc. visualjquery.com
Only one function!
• Absolutely everything* starts with a call to
the jQuery() function
• Since it’s called so often, the $ variable is set
up as an alias to jQuery
• If you’re also using another library you can
revert to the previous $ function with
jQuery.noConflict();
* not entirely true
CSS selectors
jQuery('#nav')
jQuery('div#intro h2')
jQuery('#nav li.current a')
CSS selectors
$('#nav')
$('div#intro h2')
$('#nav li.current a')
CSS 2 and 3 selectors
a[rel]
a[rel="friend"]
a[href^="http://"]
ul#nav > li
li#current ~ li (li siblings that follow #current)
li:first-child, li:last-child, li:nth-child(3)
Custom jQuery selectors
:first, :last, :even, :odd
:header
:hidden, :visible
:even, :odd
:input, :text, :password, :radio, :submit...
:checked, :selected, :enabled, :disabled
div:has(a), div:contains(Hello), div:not(.entry)
:animated
jQuery collections
• $('div.section') returns a jQuery collection object
• You can call treat it like an array
$('div.section').length = no. of matched elements
$('div.section')[0] - the first div DOM element
$('div.section')[1]
$('div.section')[2]
jQuery collections
• $('div.section') returns a jQuery collection object
• You can call methods on it:
$('div.section').size() = no. of matched elements
$('div.section').each(function() {
console.log(this);
});
jQuery collections
• $('div.section') returns a jQuery collection object
• You can call methods on it:
$('div.section').size() = no. of matched elements
$('div.section').each(function(i) {
console.log("Item " + i + " is ", this);
});
jQuery collections
• $('div.section') returns a jQuery collection object
• You can chain method calls together:
$('div.section').addClass('foo').hide();
$('div.section').each(function(i) {
console.log("Item " + i + " is ", this);
});
The jQuery() function
• Overloaded: behaviour depends on the type
of the arguments
• Grab elements using a selector
• “Upgrade” existing DOM nodes
• Create a new node from an HTML string
• Schedule a function for onDomReady
• Usually returns a jQuery collection object
jQuery methods
• I’ve identified four key types of jQuery method:
• Introspectors - return data about the selected nodes
• Modifiers - alter the selected nodes in some way
• Navigators - traverse the DOM, change the selection
• DOM modifiers - move nodes within the DOM
Introspectors
• $('div:first').attr('title')
• $('div:first').html()
• $('div:first').text()
• $('div:first').css('color')
• $('div:first').is('.entry')
Modifiers
• $('div:first').attr('title', 'The first div')
• $('div:first').html('New <em>content</em>')
• $('div:first').text('New text content')
• $('div:first').css('color', 'red')
Bulk modifiers
• $('a:first').attr({
'title': 'First link on the page',
'href': 'http://2008.xtech.org/'
});
• $('a:first').css({
'color': 'red',
'backgroundColor': 'blue'
});
Notice a pattern?
• $(selector).attr(name) gets
• $(selector).css(name) gets
• $(selector).attr(name, value) sets
• $(selector).css(name, value) sets
• $(selector).attr({ object }) sets in bulk
• $(selector).css({ object }) sets in bulk
Style modifiers
• $(selector).css(...)
• $(selector).addClass(class)
• $(selector).removeClass(class)
• $(selector).hasClass(class)
• $(selector).toggleClass(class)
Dimensions
• $(selector).height()
• $(selector).height(200)
• $(selector).width()
• $(selector).width(200)
• var offset = $(selector).offset()
• offset.top, offset.left
Navigators - finding
• $('h1').add('h2') • $('a:first').siblings()
• $('div:first').find('a') • $('h3').next()
• $('a:first').children() • $('h3:first').nextAll()
• $('a:first').children('em') • $('h3').prev()
• $('a').parent() • $('h3').prevAll()
• $('a:first').parents() • $('a:first').contents()
Navigators - filtering
• $('div').eq(1) // gets second • $('div').not('.entry')
• $('div').filter('.entry') • $('div').slice(1, 3) // 2nd,3rd
• $('div').filter(function(i) { • $('div').slice(-1) // last
return this.className == 'foo'
}
DOM modifiers
• els.append(content) • content.insertAfter(els)
• content.appendTo(els) • content.insertBefore(els)
• els.prepend(content) • els.wrapAll('<div /'>)
• content.prependTo(els) • els.wrapInner('<div /'>)
• els.after(content) • els.empty()
• els.before(content) • els.remove()
DOM construction
• var p = $('<p id="foo" />').addClass('bar');
• p.text('This is some text').css('color', 'red');
• p.appendTo(document.body);
jQuery and
Microformats
Favourite restaurant list
With JavaScript enabled
jQuery and Microformats
<ul class="restaurants">
<li class="vcard">
<h3><a class="fn org url" href="...">
Riddle & Finns</a></h3>
<div class="adr">
<p class="street-address">12b Meeting House Lane</p>
<p><span class="locality">Brighton</span>, <abbr
class="country-name" title="United Kingdom">UK</abbr></p>
<p class="postal-code">BN1 1HB</p>
</div>
<p>Telephone: <span class="tel">+44 (0)1273 323 008</
span></p>
<p class="geo">Lat/Lon:
<span class="latitude">50.822563</span>,
<span class="longitude">-0.140457</span>
</p>
</li>
...
Creating the map
jQuery(function($) {
// First create a div to host the map
var themap = $('<div id="themap"></div>').css({
'width': '90%',
'height': '400px'
}).insertBefore('ul.restaurants');
// Now initialise the map
var mapstraction = new Mapstraction('themap','google');
mapstraction.addControls({
zoom: 'large',
map_type: true
});
Creating the map
jQuery(function($) {
// First create a div to host the map
var themap = $('<div id="themap"></div>').css({
'width': '90%',
'height': '400px'
}).insertBefore('ul.restaurants');
// Now initialise the map
var mapstraction = new Mapstraction('themap','google');
mapstraction.addControls({
zoom: 'large',
map_type: true
});
Displaying the map
// Show map centred on Brighton
mapstraction.setCenterAndZoom(
new LatLonPoint(50.8242, -0.14008),
15 // Zoom level
);
Extracting the microformats
$('.vcard').each(function() {
var hcard = $(this);
var latitude = hcard.find('.geo .latitude').text();
var longitude = hcard.find('.geo .longitude').text();
var marker = new Marker(new LatLonPoint(latitude, longitude));
marker.setInfoBubble(
'<div class="bubble">' + hcard.html() + '</div>'
);
mapstraction.addMarker(marker);
});
Extracting the microformats
$('.vcard').each(function() {
var hcard = $(this);
var latitude = hcard.find('.geo .latitude').text();
var longitude = hcard.find('.geo .longitude').text();
var marker = new Marker(new LatLonPoint(latitude, longitude));
marker.setInfoBubble(
'<div class="bubble">' + hcard.html() + '</div>'
);
mapstraction.addMarker(marker);
});
Events
$('a:first').bind('click', function() {
$(this).css('backgroundColor' ,'red');
return false;
});
Events
$('a:first').click(function() {
$(this).css('backgroundColor' ,'red');
return false;
});
Event objects
$('a:first').click(function(ev) {
$(this).css('backgroundColor' ,'red');
ev.preventDefault();
});
Triggering events
$('a:first').trigger('click');
Triggering events
$('a:first').click();
Events
• blur() • keydown() • mouseup()
• change() • keypress() • resize()
• click() • keyup() • scroll()
• dblclick() • load() • select()
• error() • mousedown() • submit()
• focus() • mouseover() • unload()
labels.js with jQuery
...
<label class="inputHint" for="email">E-mail:</label>
<input id="email" type="text">
...
labels.js with jQuery
jQuery(function($) {
$('label.inputHint').each(function() {
var label = $(this);
var input = $('#' + label.attr('for'));
var initial = label.hide().text().replace(':', '');
input.focus(function() {
input.css('color', '#000');
if (input.val() == initial) {
input.val('');
}
}).blur(function() {
if (input.val() == '') {
input.val(initial).css('color', '#aaa');
}
}).css('color', '#aaa').val(initial);
});
});
labels.js with jQuery
jQuery(function($) {
$('label.inputHint').each(function() {
var label = $(this);
var input = $('#' + label.attr('for'));
var initial = label.hide().text().replace(':', '');
input.focus(function() {
input.css('color', '#000');
if (input.val() == initial) {
input.val('');
}
}).blur(function() {
if (input.val() == '') {
input.val(initial).css('color', '#aaa');
}
}).css('color', '#aaa').val(initial);
});
});
labels.js with jQuery
jQuery(function($) {
$('label.inputHint').each(function() {
var label = $(this);
var input = $('#' + label.attr('for'));
var initial = label.hide().text().replace(':', '');
input.focus(function() {
input.css('color', '#000');
if (input.val() == initial) {
input.val('');
}
}).blur(function() {
if (input.val() == '') {
input.val(initial).css('color', '#aaa');
}
}).css('color', '#aaa').val(initial);
});
});
labels.js with jQuery
jQuery(function($) {
$('label.inputHint').each(function() {
var label = $(this);
var input = $('#' + label.attr('for'));
var initial = label.hide().text().replace(':', '');
input.focus(function() {
input.css('color', '#000');
if (input.val() == initial) {
input.val('');
}
}).blur(function() {
if (input.val() == '') {
input.val(initial).css('color', '#aaa');
}
}).css('color', '#aaa').val(initial);
});
});
labels.js with jQuery
jQuery(function($) {
$('label.inputHint').each(function() {
var label = $(this);
var input = $('#' + label.attr('for'));
var initial = label.hide().text().replace(':', '');
input.focus(function() {
input.css('color', '#000');
if (input.val() == initial) {
input.val('');
}
}).blur(function() {
if (input.val() == '') {
input.val(initial).css('color', '#aaa');
}
}).css('color', '#aaa').val(initial);
});
});
Advanced chaining
• Modified chains can be reverted using end()
$('div.entry').css('border', '1px solid black).
find('a').css('color', 'red').end().
find('p').addClass('p-inside-entry').end();
Inline form help
With JavaScript enabled
Form help HTML
<div class="helpWrapper">
<div class="fieldWrapper">
<label for="email">E-mail address</label>
<input id="email" type="text">
</div>
<p class="helpText">
We promise not to spam you!
</p>
</div>
Form help JavaScript
jQuery(function($) {
// Set up contextual help...
var contextualHelp = $('<div id="contextualHelp"></div>');
contextualHelp.hide().appendTo(document.body);
Form help JavaScript
// helpArrow is a div containing the ]- thing
var helpArrow = $('<div id="helpArrow"></div>').css({
'position': 'absolute',
'left': '450px',
'top': '0px', // This changes
'height': '22px', // This changes
'width': '20px'
}).hide().appendTo(document.body);
Form help JavaScript
var helpBrace = $('<div id="helpArrowBrace"></div>').css({
'width': '5px',
'height': '100%',
'border-right': '2px solid #ccc',
'border-bottom': '2px solid #ccc',
'border-top': '2px solid #ccc'
}).appendTo(helpArrow);
Form help JavaScript
var helpBrace = $('<div id="helpArrowBrace"></div>').css({
'width': '5px',
'height': '100%',
'border-right': '2px solid #ccc',
'border-bottom': '2px solid #ccc',
'border-top': '2px solid #ccc'
}).appendTo(helpArrow);
Form help JavaScript
var helpBar = $('<div id="helpArrowBar"></div>').css({
'width': '15px',
'height': '0px',
'border-top': '2px solid #ccc',
'position': 'absolute',
'top': '50%',
'left': '5px'
}).appendTo(helpArrow);
Form help JavaScript
function showHelp(helpWrapper, helpHtml) {
// Display contextual help next to helpWrapper div
var top = $(helpWrapper).offset().top;
helpArrow.css('top', top + 'px');
helpArrow.height($(helpWrapper).height()).show();
contextualHelp.css('top', top + 'px').show().html(helpHtml);
}
Form help JavaScript
function showHelpForField(field) {
var helpWrapper = input.parents('div.helpWrapper');
var helpHtml = helpWrapper.find('p.helpText').html();
showHelp(helpWrapper, helpHtml);
}
Form help JavaScript
$('div.helpWrapper').find(':input').focus(function() {
showHelpForField(this);
}).end().find('p.helpText').hide();
Advanced Events
$('a:first').unbind('click');
$('a:first').unbind();
$('a:first').one('click', function() {
// executes the first time the link is clicked
}
$('a:first').toggle(func1, func2);
$('a:first').hover(func1, func2);
Custom events
function updateInbox(event, mail) {
alert('New e-mail: ' + mail);
}
$(window).bind('mail-recieved', updateInbox)
$(window).bind('mail-recieved', soundPing)
$(window).trigger('mail-recieved', [mail])
Ajax
• Simple:
• jQuery('div#news').load('/news.html');
• Complex:
• jQuery.ajax(options) - low level control
• jQuery.get(url, [data], [callback])
• jQuery.post(url, [data], [callback], [type])
• jQuery.getJSON(url, [data], [callback])
• jQuery.getScript(url, [data], [callback])
Ajax global events
• .ajaxComplete(function() { })
• .ajaxError(function() { })
• .ajaxSend(function() { })
• .ajaxStart(function() { })
• .ajaxStop(function() { })
• .ajaxSuccess(function() { })
Ajax sidenote
$('a.sidenote').one('click', function() {
$('<p />').load(this.href).appendTo(document.body);
// Make the link stop being a link
$(this).replaceWith($(this).contents());
return false;
});
Loading...
var loading = $(
'<img alt="loading" src="loading.gif" />'
).appendTo(document.body).hide()
jQuery(window).ajaxStart(function() { loading.show() });
jQuery(window).ajaxStop(function() { loading.hide() });
jQuery(xml)
var signup = $('div#en_sidebar div#signup.rhsbox');
var news_box = $('<div id="news_feed" class="rhsbox"></div>');
news_box.html(signup.html());
news_box.find('div.box').empty();
var ul = $('<ul />');
var feed_url = jQuery('link[type=application/atom+xml]').attr('href');
jQuery.get(feed_url, function(xml) {
var feed = jQuery(xml);
feed.find('feed entry').each(function() {
var title = $(this).find('title').text();
var link = $(this).find('link').attr('href');
var li = $('<li><a href="' + link + '">' + title + '</a></li>');
li.appendTo(ul);
});
});
ul.css('text-align', 'left').appendTo(news_box.find('div.box'));
news_box.insertBefore(signup);
Paste the above in to Firebug on any 2008.xtech.org page
Animation
• jQuery has built in effects:
$('h1').hide('slow');
$('h1').slideDown('fast');
$('h1').fadeOut(2000);
• Chaining automatically queues the effects:
$('h1').fadeOut(1000).slideDown()
You can roll your own
$("#block").animate({
width: "+=60px",
opacity: 0.4,
fontSize: "3em",
borderWidth: "10px"
}, 1500);
A login form that shakes its head
The shake animation
function shake(el, callback) {
el.css({'position': 'relative'});
el.animate(
{left: '-10px'}, 100
).animate(
{left: '+10px'}, 100
).animate(
{left: '-10px'}, 100
).animate(
{left: '+10px'}, 100
).animate(
{left: '0px'}, 100, callback
);
};
The PHP
$username = isset($_POST['username']) ? $_POST['username'] : '';
$password = isset($_POST['password']) ? $_POST['password'] : '';
$msg = '';
if ($_POST) {
if ($username == 'simon' && $password == 'xtech') {
if (is_xhr()) {
json_response(array('ok' => true, 'redirect' =>
'loggedin.php'));
} else {
header('Location: loggedin.php');
}
exit;
}
if (is_xhr()) {
json_response(array('ok' => false));
}
$msg = '<p class="error">Incorrect username or password.</p>';
}
PHP utility functions
function is_xhr() {
return (isset($_SERVER["HTTP_X_REQUESTED_WITH"]) &&
$_SERVER["HTTP_X_REQUESTED_WITH"] == 'XMLHttpRequest');
}
function json_response($obj) {
header('Content-Type: application/json');
print json_encode($obj);
exit;
}
The JavaScript
jQuery(function($) {
$('#username').focus();
$('form').submit(function() {
var form = $(this);
var url = form.attr('action');
var data = form.serialize();
jQuery.post(url, data, function(json) {
if (json.ok) {
window.location = json.redirect;
} else {
...
The JavaScript
else {
$('#password').val('').focus();
shake(form, function() {
if (!form.find('p.error').length) {
$('<p class="error">Incorrect username or ' +
'password.</p>').prependTo(form).
hide().slideDown();
}
});
}
}, 'json'); // jQuery.post(url, data, callback, type);
return false;
});
});
shake() as a plugin
jQuery.fn.shake = function(callback) {
this.css({'position': 'relative'});
return this.animate(
{left: '-10px'}, 100
).animate(
{left: '+10px'}, 100
).animate(
{left: '-10px'}, 100
).animate(
{left: '+10px'}, 100
).animate(
{left: '0px'}, 100, callback
);
};
$('form').shake();
$('form').shake(function() { alert('shaken!') });
jQuery Plugins
Plugins
• jQuery is extensible through plugins, which
can add new methods to the jQuery object
• Form: better form manipulation
• UI: drag and drop and widgets
• $('img[@src$=.png]').ifixpng()
• ... many dozens more
Logging the chain
jQuery.fn.log = function(message) {
if (message) {
console.log(message, this);
} else {
console.log(this);
}
return this;
};
jQuery.fn.hideLinks = function() {
this.find('a[href]').hide();
return this;
}
$('p').hideLinks();
jQuery.fn.hideLinks = function() {
this.find('a[href]').hide();
return this;
}
$('p').hideLinks();
jQuery.fn.hideLinks = function() {
return this.find('a[href]').hide().end();
}
$('p').hideLinks();
Extending the selector engine
jQuery.expr[':'].second = function(a,i){return i==1;}
$('div:second') - the second div on the page
jQuery data()
• Attaching data directly to DOM nodes can
create circular references and cause
memory leaks
• jQuery provides a data() method for safely
attaching information
• $('div:first').data('key', 'value');
• var value = $('div:first').data('key');
jQuery utilities
• jQuery.each(object, callback) • jQuery.unique(array)
• jQuery.extend(target, object) • jQuery.makeArray(obj)
• jQuery.grep(array, callback) • jQuery.isFunction(obj)
• jQuery.map(array, callback) • jQuery.trim(string)
• jQuery.inArray(value, array)
Further reading
• http://jquery.com/
• http://docs.jquery.com/
• http://visualjquery.com/ - API reference
• http://simonwillison.net/tags/jquery/
• http://simonwillison.net/2007/Aug/15/jquery/
• http://24ways.org/2007/unobtrusively-
mapping-microformats-with-jquery