This document was uploaded by user and they confirmed that they have the permission to share
it. If you are author or own the copyright of this book, please report to us by using this DMCA
report form. Report DMCA
Overview
Download & View Unobtrusive Javascript With Jquery as PDF for free.
Unobtrusive JavaScript with jQuery Simon Willison XTech , 6th May 2008
How I learned to stop worrying and love JavaScript
Unobtrusive JavaScript
A set of principles for writing accessible, maintainable JavaScript
A library that supports unobtrusive scripting
Unobtrusive JavaScript
jQuery
Theory
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
• 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
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 crossbrowser event handling for us
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)
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
Creating the map jQuery(function($) { // First create a div to host the map var themap = $('').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 = $('').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( '
' + hcard.html() + '
' ); 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( '
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
We promise not to spam you!
Form help JavaScript
jQuery(function($) { // Set up contextual help... var contextualHelp = $(''); contextualHelp.hide().appendTo(document.body);
Form help JavaScript // helpArrow is a div containing the ]- thing var helpArrow = $('').css({ 'position': 'absolute', 'left': '450px', 'top': '0px', // This changes 'height': '22px', // This changes 'width': '20px' }).hide().appendTo(document.body);
Form help JavaScript var helpBar = $('').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); }
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);
$('a.sidenote').one('click', function() { $('').load(this.href).appendTo(document.body); // Make the link stop being a link $(this).replaceWith($(this).contents()); return false; });
jQuery(xml) var signup = $('div#en_sidebar div#signup.rhsbox'); var news_box = $(''); news_box.html(signup.html()); news_box.find('div.box').empty(); var 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 = $('
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) { $('