Unobtrusive JavaScript With Jquery
Unobtrusive JavaScript With Jquery
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,
jQuery Practice
We will cover
• The what and why of unobtrusive JavaScript
• 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.
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...
• Unlike YUI...
• it’s succinct
• YAHOO.util.Dom.getElementsByClassName()
• Unlike Dojo...
jQuery('div#intro h2')
$('div#intro h2')
• $('div:first').attr('title')
• $('div:first').html()
• $('div:first').text()
• $('div:first').css('color')
• $('div:first').is('.entry')
Modifiers
• $('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')
• content.appendTo(els) • content.insertBefore(els)
• els.after(content) • els.empty()
• els.before(content) • els.remove()
DOM construction
$('.vcard').each(function() {
var hcard = $(this);
$('.vcard').each(function() {
var hcard = $(this);
$('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()
...
<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
<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
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()
$('h1').slideDown('fast');
$('h1').fadeOut(2000);
$("#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
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.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