14. 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
15. 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
19. • 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
21. • 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
24. labels.js
• One of the earliest examples of this
technique, created by Aaron Boodman (now
of Greasemonkey and Google Gears fame)
25.
26.
27. How it works
<label for=quot;searchquot;>Search</label>
<input type=quot;textquot; id=quot;searchquot; name=quot;qquot;>
• 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
• Clean, simple, reusable
28. easytoggle.js
• An unobtrusive technique for revealing
panels when links are clicked
<ul>
<li><a href=quot;#panel1quot; class=quot;togglequot;>Panel 1</a></li>
<li><a href=quot;#panel2quot; class=quot;togglequot;>Panel 2</a></li>
<li><a href=quot;#panel3quot; class=quot;togglequot;>Panel 3</a></li>
</ul>
<div id=quot;panel1quot;>...</div>
<div id=quot;panel2quot;>...</div>
<div id=quot;panel3quot;>...</div>
29.
30.
31. How it works
• When the page has loaded...
• Find all links with class=quot;togglequot; that reference an
internal anchor
• Collect the elements that are referenced by those
anchors
• Hide all but the first
• Set up event handlers to reveal different panels when a
link is clicked
• Without JavaScript, links still jump to the right point
32. 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
33.
34.
35. Mapping Microformats
• The hCard microformat provides a standard
set of CSS classes for marking up an address
• Wouldn’t it be great if we could
unobtrusively plot them on a Google Map...
36.
37.
38. • When the page loads...
• Scan through for hCards (by looking for
class=quot;vCardquot;)
• Extract the street address and postcode for
each one
• Pass it to the Google Maps geocoder to get
the lat/long point
• Insert a div for the Google Map, instantiate it
and add the points as markers
39. How about Ajax?
• Ajax is frequently used to avoid page refreshes
• So...
• Write an app that uses full page refreshes
• Use unobtrusive JS to quot;hijackquot; links and form
buttons and use Ajax instead
• Jeremy Keith coined the term quot;Hijaxquot; to
describe this
42. Bad
Have you read our
<a href=quot;javascript:window.open(
'terms.html', 'popup',
'height=500,width=400,toolbar=no'
);quot;>terms and conditions</a>?
43. Also bad
Have you read our
<a href=quot;#quot; onclick=quot;window.open(
'terms.html', 'popup',
'height=500,width=400,toolbar=no'
); return false;quot;
>terms and conditions</a>?
44. Better
Have you read our
<a href=quot;terms.htmlquot;
onclick=quot;window.open(
'terms.html', 'popup',
'height=500,width=400,toolbar=no'
); return false;quot;
>terms and conditions</a>?
45. Better still
Have you read our
<a href=quot;terms.htmlquot;
onclick=quot;window.open(
this.href, 'popup',
'height=500,width=400,toolbar=no'
); return false;quot;
>terms and conditions</a>?
46. Best
Have you read our
<a href=quot;terms.htmlquot;
class=quot;sidenotequot;
>terms and conditions</a>?
47. 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
48. Handling events
function makeLinkPopup(link) {
if (link.addEventListener) { // W3C spec browsers
link.addEventListener('click', popupClicked, false);
} else if (link.attachEvent) { // Internet Explorer
link.attachEvent('onclick', popupClicked);
} else {
return; // Fail silently in ancient browsers
}
}
49. Handling events (2)
function popupClicked(ev) {
// Find the link that was clicked
ev = ev || window.event;
var link = ev.target || ev.srcElement;
if (link.nodeType == 3) { // Safari bug fix
link = link.parentNode;
}
window.open(link.href, 'popup',
'height=500,width=400,toolbar=no');
// Now prevent the default link action
if (ev.preventDefault) {
ev.preventDefault(); // W3C spec browsers
} else {
ev.returnValue = false; // Internet Explorer
}
}
50. Handling events (3)
function setupLinks() {
var links = document.getElementsByTagName('a');
for (var i = 0, link; link = links[i]; i++) {
if (link.className == 'sidenote') {
makeLinkPopup(link);
}
}
}
if (window.addEventListener) {
window.addEventListener('load', setupLinks, false);
} else if (window.attachEvent) {
window.attachEvent('onload', setupLinks);
}
52. Unobtrusive challenges
• Adding events from pure script (avoiding
inline script handlers) works differently
between IE and other browsers
• Cancelling default actions (link navigation,
form submission) also works differently
• Scripts that add behaviour need to execute
as soon as the DOM is available
53. The onload problem
• All of these examples use code that runs
when the window quot;loadquot; event is fired
• Wait until page is loaded, then manipulate
the DOM
• Problem: If the page takes a while to load
(large inline images) there will be a Flash Of
Unstyled Content (FOUC)
• Also, if the user clicks things before the
setup code has fired they won't get the
expected behaviour.
54. onDOMReady
• A number of attempts have been made to create
an onDOMReady event that fires when the
DOM has been constructed but before the page
has loaded
• dean.edwards.name/weblog/2006/06/again/
• Modern libraries all support this in some form
• One caveat: CSS may not have loaded, so
calculated dimensions may be incorrect
55. So we need libraries
• Writing this stuff by hand is madness
• Boilerplate code to deal with browser
inconsistencies is the number one enemy of
unobtrusive scripting
• Thankfully, today these are all solved
problems
57. Controversial statement
• A year ago, there was a sizable debate over
whether libraries were worth using at all -
many people preferred to just “roll their
own”
• Today, that argument is over. Libraries have
won.
• (some may disagree)
58. “The bad news:
JavaScript is broken.
The good news:
It can be fixed with
more JavaScript!”
Geek folk saying
59. JavaScript characteristics
• Highly dynamic language
• Most things can be introspected
• Most things can be modified at runtime
• Functional programming, anonymous
functions and closures
• Ability to modify behaviour of built-in types
• Prototypal inheritance - arguably more
powerful than class-based inheritance,
though much less widely understood
60. Modifying built-in types
var s = quot;This is a stringquot;;
alert(s.dasherize()); // Throws an error
String.prototype.dasherize = function() {
return this.toLowerCase().replace(/s+/g, '-');
}
alert(s.dasherize()); // this-is-a-string
61. Functional programming
function hello() {
alert(quot;helloquot;);
}
var hello = function() {
alert(quot;helloquot;);
}
(function() {
alert(quot;helloquot;);
})();
62. Functional programming
function hello() {
alert(quot;helloquot;);
}
var hello = function() {
alert(quot;helloquot;);
}
(function() {
alert(quot;helloquot;);
})();
63. Functional programming
function hello() {
alert(quot;helloquot;);
}
var hello = function() {
alert(quot;helloquot;);
}
(function() {
alert(quot;helloquot;);
})();
65. Closures
function outer() {
var s = quot;This is a stringquot;;
function inner() {
alert(s);
}
return inner;
}
var func = outer();
func(); // Alerts quot;This is a stringquot;;
66. Closures
function outer(s) {
function inner() {
alert(s);
}
return inner;
}
var sayHello = outer(quot;Helloquot;);
var sayGoodbye = outer(quot;Goodbyequot;);
sayHello();
sayGoodbye();
70. Event handling
• Cross-browser “run this function when this
event happens to this element”
• Cross-browser “on DOM ready” support
• Cross-browser event introspection: where
was the event targetted?
• Event triggering
• Custom events
72. Effects and animation
• Fades, wipes and transitions
• Custom animation of any CSS property
• Automatic frame-rate management
• Easing
73. Widgets
• Packaged widgets for things like...
• Sliders
• Calendar date pickers
• Fake “windows”
• Auto-completers
• Grids and treeviews
• Support for drag-and-drop
74. Language tools
• Iteration over objects and arrays
• Utilities for managing prototypal inheritance
• Traditional class-based inheritance
• Tools for managing scope and callbacks
• Dynamic code loading
75. Array manipulation
for (var i = 0; i < arr.length; i++) {
var item = arr[i];
// process item
}
forEach(arr, function(item) {
// process item
});
doubles = map(arr, function(i) { return i * 2 });
evens = filter(arr, function(i) { return i % 2 == 0 });
76. Other characteristics
• Do they modify built in types?
• Global namespace impact
• Library size and componentization
• Support for accessibility (esp. for widgets)
• Browser support
• Documentation
• Community
77. The big five
• The Yahoo! User Interface Library
• Prototype (and Script.aculo.us)
• The Dojo Toolkit
• mooTools
• jQuery
78. The Example
• A login form that shakes its head at you
• Standard POST login form, unobtrusively
upgraded to use Ajax and shake if the
password is incorrect
• Behind the scenes, JSON is used for the reply
to say if the password was correct or not
79. Simple login JSON API
• Responds to the same POST as the regular
form, but looks for X-Requested-With:
XMLHttpRequest header
• If login fails, returns
{quot;okquot;:false}
• If login succeeds, returns
{quot;okquot;: true, quot;redirectquot;: quot;loggedin.phpquot;}
82. YAHOO.util.Event.onDOMReady(function() {
var $E = YAHOO.util.Event;
var $D = YAHOO.util.Dom;
var $C = YAHOO.util.Connect;
var query = YAHOO.util.Selector.query;
$D.get('username').focus();
var form = query('form')[0];
83. YAHOO.util.Event.onDOMReady(function() {
var $E = YAHOO.util.Event;
var $D = YAHOO.util.Dom;
var $C = YAHOO.util.Connect;
var query = YAHOO.util.Selector.query;
$D.get('username').focus();
var form = query('form')[0];
84. YAHOO.util.Event.onDOMReady(function() {
var $E = YAHOO.util.Event;
var $D = YAHOO.util.Dom;
var $C = YAHOO.util.Connect;
var query = YAHOO.util.Selector.query;
$D.get('username').focus();
var form = query('form')[0];
85. YAHOO.util.Event.onDOMReady(function() {
var $E = YAHOO.util.Event;
var $D = YAHOO.util.Dom;
var $C = YAHOO.util.Connect;
var query = YAHOO.util.Selector.query;
$D.get('username').focus();
var form = query('form')[0];
87. success: function(response) {
var json = eval('(' +
response.responseText + ')');
if (json.ok) {
window.location = json.redirect;
} else {
$D.get('password').value = '';
$D.get('password').focus();
shake(form, function() {
if (!query('p.error').length) {
var p = document.createElement('p');
p.className = 'error';
p.innerHTML = 'Incorrect password.';
form.insertBefore(p, form.firstChild);
}
});
}
88. function shake(el, onComplete) {
var $D = YAHOO.util.Dom;
$D.setStyle(el, 'position', 'relative');
var anim = new YAHOO.util.Anim(el, {
left: {to: -10}
}, 0.1, YAHOO.util.Easing.easeOut);
anim.onComplete.subscribe(function() {
// ...
}
anim.animate();
}
89. function shake(el, onComplete) {
var $D = YAHOO.util.Dom;
$D.setStyle(el, 'position', 'relative');
var anim = new YAHOO.util.Anim(el, {
left: {to: -10}
}, 0.1, YAHOO.util.Easing.easeOut);
anim.onComplete.subscribe(function() {
var anim2 = new YAHOO.util.Anim(el, {
left: {to: 10}
}, 0.1, YAHOO.util.Easing.easeOut);
anim2.onComplete.subscribe(function() {
var anim3 = new YAHOO.util.Anim(el, {
left: {to: -10}
}, 0.1, YAHOO.util.Easing.easeOut);
anim3.onComplete.subscribe(function() {
var anim4 = new YAHOO.util.Anim(el, {
left: {to: 10}
}, 0.1, YAHOO.util.Easing.easeOut);
anim4.onComplete.subscribe(function() {
var anim5 = new YAHOO.util.Anim(el, {
left: {to: 0}
}, 0.1, YAHOO.util.Easing.easeOut);
if (onComplete) {
anim5.onComplete.subscribe(onComplete);
}
anim5.animate();
});
anim4.animate();
});
anim3.animate();
});
anim2.animate();
});
anim.animate();
}
90. • Nice animation library, but a better way of
chaining animations would be useful
• Severe namespacing (YAHOO was picked
because it was so ugly no one would ever
have used it for an existing variable)
• Not too many shortcuts outside of dealing
with browser difficulties
• Excellent documentation
91. Download
Get the latest version—1.5.1
Learn
Prototype is a JavaScript Framework that aims to Online documentation and resources.
ease development of dynamic web applications.
Featuring a unique, easy-to-use toolkit for class-driven Discuss
development and the nicest Ajax library around, Prototype Mailing list and IRC
is quickly becoming the codebase of choice for web
application developers everywhere.
Contribute
Prototype and Script.aculo.us
Prototype and script.aculo.us: The quot;Bungee
bookquot; has landed!
Submit patches and report bugs.
Who's using Prototype?
Meet the developers
Core team member Christophe
Porteneuve has been hard at work
for the past few months tracking
92. document.observe('dom:loaded', function() {
$('username').focus();
var form = $$('form')[0];
form.observe('submit', function(ev) {
ev.preventDefault();
var url = form.action;
new Ajax.Request(url, {
parameters: form.serialize(),
onSuccess: ...
});
});
});
93. onSuccess: function(response) {
var json = response.responseJSON;
if (json.ok) {
window.location = json.redirect;
} else {
form.shake();
// No apparent way of setting an
// quot;on finishedquot; callback
setTimeout(function() {
if (!$$('p.error').length) {
var p = new Element(
'p', {'class': 'error'}
).insert('Incorrect...');
form.insertBefore(p, form.firstChild);
}
}, 500);
$('password').value = '';
$('password').focus();
}
}
94. • Adds many functions to the global
namespace ($, $$ etc) and extensively
modifies built in types
• Heavily inspired by Ruby and Ruby on Rails
• Useful shortcuts and nice effects
• Documentation still leaves something to be
desired - API docs for Prototype, incomplete
wiki docs for Scriptaculous
98. dojo.connect(last, 'onEnd', function() {
if (!dojo.query('p.error').length) {
var p = document.createElement('p');
p.className = 'error';
p.innerHTML = 'Incorrect username or password.';
form.insertBefore(p, form.firstChild);
}
});
dojo.byId('password').value = '';
dojo.byId('password').focus();
99. • core, dijit and dojox:
• Small, powerful abstraction library
• Widget creation tools plus many widgets
• “The future today” genius extensions
• Documentation has improved with the Dojo
Book, but still patchy in places
101. window.addEvent('domready', function() {
$('username').focus();
$$('form').addEvent('submit', function(ev) {
new Event(ev).stop();
var form = this;
var url = form.action;
form.send({
onComplete: function(data) {
var json = Json.evaluate(data);
if (json.ok) {
window.location = json.redirect;
} else {
form.style.position = 'relative';
// ... animation here
$('password').value = '';
$('password').focus();
}
}
});
});
});
102. var fx = form.effects({
duration: 100,
transition: Fx.Transitions.Quart.easeOut
});
fx.start({left: -10}).chain(function(){
this.start({left: 10});
}).chain(function(){
this.start({left: -10});
}).chain(function(){
this.start({left: 10});
}).chain(function(){
this.start({left: 0});
}).chain(function() {
if (!$$('p.error').length) {
var p = new Element('p').
addClass('error').setHTML(
'Incorrect username or password.'
);
form.insertBefore(p, form.firstChild);
}
});
103. • Started as an effects library for Prototype, now its
own thing but Prototype influences are clear
• No intention at all of playing nicely with other
libraries
• Good API documentation (including a mootorial)
but not much else, and major changes between
library versions
• I found the API unintuitive compared to the others
107. • Powerful, concise API based around CSS
selector querying and chaining
• Excellent namespace management -
everything lives on the one jQuery symbol
which is aliased to $, with the option to
revert
• Excellent support for plugins
• The best documentation out of all of the
libraries
109. Why jQuery
• It has the best balance between simplicity,
productivity and good manners
• It has excellent documentation
• You can learn the whole library in less than
an hour
110. Why jQuery instead of...?
• Unlike Prototype and mooTools...
• ... it doesn’t populate your global namespace
• Unlike YUI...
• ... it’s extremely succinct
• Unlike Dojo...
• ... you can learn it in 45 minutes!
112. jQuery philosophy
• Focus on the interaction between JavaScript
and HTML
• (Almost) every operation boils down to:
• Find some stuff
• Do something to it
114. 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
117. CSS 2 and 3 selectors
a[rel]
a[rel=quot;friendquot;]
a[href^=quot;http://quot;]
ul#nav > li
li#current ~ li (li siblings that follow #current)
li:first-child, li:last-child, li:nth-child(3)
119. jQuery collections
• $('div.section') returns a jQuery collection
• You can 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]
120. jQuery collections
• $('div.section') returns a jQuery collection
• You can call methods on it
$('div.section').size() = no. of matched elements
$('div.section').each(function() {
console.log(this);
});
121. jQuery collections
• $('div.section') returns a jQuery collection
• You can call methods on it
$('div.section').size() = no. of matched elements
$('div.section').each(function(i) {
console.log(quot;Item quot; + i + quot; is quot;, this);
});
125. Grabbing values
• Some methods return results from the first
matched element
var height = $('div#intro').height();
var src = $('img.photo').attr('src');
var lastP = $('p:last').html()
var hasFoo = $('p').hasClass('foo');
var email = $('input#email').val();
126. Traversing the DOM
• jQuery provides enhanced methods for
traversing the DOM
$('div.section').parent()
$('div.section').next()
$('div.section').prev()
$('div.section').nextAll('div')
$('h1:first').parents()
132. Chaining
• Most jQuery methods return another
jQuery object - usually one representing the
same collection. This means you can chain
methods together:
$('div.section').hide().addClass('gone');
133. Advanced chaining
• Some methods return a different collection
• You can call .end() to revert to the previous
collection
134. Advanced chaining
• Some methods return a different collection
• You can call .end() to revert to the previous
collection
$('#intro').css('color', '#cccccc').
find('a').addClass('highlighted').end().
find('em').css('color', 'red').end()
135. Ajax
• jQuery has excellent support for Ajax
$('div#intro').load('/some/file.html');
• More advanced methods include:
$.get(url, params, callback)
$.post(url, params, callback)
$.getJSON(url, params, callback)
$.getScript(url, callback)
136. Animation
• jQuery has built in effects:
$('h1').hide('slow');
$('h1').slideDown('fast');
$('h1').fadeOut(2000);
• You can chain them:
$('h1').fadeOut(1000).slideDown()
137. Or roll your own...
$(quot;#blockquot;).animate({
width: quot;+=60pxquot;,
opacity: 0.4,
fontSize: quot;3emquot;,
borderWidth: quot;10pxquot;
}, 1500);
138. Plugins
• jQuery is extensible through plugins, which
can add new methods to the jQuery object
139. 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