Accessible Ajax on Rails Jarkko Laine with Geoffrey Grosenbach - - PowerPoint PPT Presentation

accessible ajax on rails
SMART_READER_LITE
LIVE PREVIEW

Accessible Ajax on Rails Jarkko Laine with Geoffrey Grosenbach - - PowerPoint PPT Presentation

Accessible Ajax on Rails Jarkko Laine with Geoffrey Grosenbach r.resources :categories do |cat| cat.resources :products cat.resources :companies cat.resources :subcategories do |sub| sub.resources :products sub.resources :companies end


slide-1
SLIDE 1

Accessible Ajax on Rails

Jarkko Laine

with Geoffrey Grosenbach

slide-2
SLIDE 2
slide-3
SLIDE 3

r.resources :categories do |cat| cat.resources :products cat.resources :companies cat.resources :subcategories do |sub| sub.resources :products sub.resources :companies end end

slide-4
SLIDE 4 //<![CDATA[ new Form.Element.EventObserver(‘undone_box_1’, function(element, value) { new Ajax.Request(‘/items/1’, {asynchronous:true, evalScripts:true, method:‘put’, parameters:value + ‘&authenticity_token=’ + encodeURIComponent(‘8d829cfcccdf4d2b494891ef47cc95893faa361e’)})}) //]]> </script> </li> <li id=“undone_2”> <input id=“undone_box_2” name=“item[2][done]” type=“checkbox” value=“1” /> <input name=“item[2][done]” type=“hidden” value=“0” /> <label for=“undone_box_2”> Return bottles to recycling </label> <script type=“text/javascript”> //<![CDATA[ new Form.Element.EventObserver(‘undone_box_2’, function(element, value) { new Ajax.Request(‘/items/2’, {asynchronous:true, evalScripts:true, method:‘put’, parameters:value + ‘&authenticity_token=’ + encodeURIComponent(‘8d829cfcccdf4d2b494891ef47cc95893faa361e’)})}) //]]> </script> </li> <li id=“undone_3”> <input id=“undone_box_3” name=“item[3][done]” type=“checkbox” value=“1” /> <input name=“item[3][done]” type=“hidden” value=“0” /> <label for=“undone_box_3”> Return bottles to recycling </label> <script type=“text/javascript”> //<![CDATA[ new Form.Element.EventObserver(‘undone_box_3’, function(element, value) { new Ajax.Request(‘/items/3’, {asynchronous:true, evalScripts:true, method:‘put’, parameters:value + ‘&authenticity_token=’ + encodeURIComponent(‘8d829cfcccdf4d2b494891ef47cc95893faa361e’)})}) //]]>
slide-5
SLIDE 5

Accessibility

“the degree to which a product (e.g., device, service, environment) is accessible by as many people as possible.”

  • Wikipedia
slide-6
SLIDE 6

Accessibility

The ultimate goal

slide-7
SLIDE 7

Progressive Enhancement

A methodology for producing accessible web content

slide-8
SLIDE 8

Progressive Enhancement

Roots: graceful degradation hardly ever happened in real world

slide-9
SLIDE 9

Progressive enhancement turns graceful degradation on its head

build the essential, universal first

then gradually enhance the experience for those capable of digesting it

slide-10
SLIDE 10

Unobtrusive javascript

part of the progressive enhancement process

slide-11
SLIDE 11

Unobtrusive javascript

Separation of concerns

slide-12
SLIDE 12

<div id=“wrapper”> <ul id=“my_stuff”> <li id=“hide_me”> I am Iron Man </li> </ul> </div> #wrapper { width: 100%;

  • verflow: hidden;

} #my_stuff { list-style: none; } #my_stuff li { padding: 1.5em; }

Structure Presentation

slide-13
SLIDE 13

<div id=“wrapper”> <ul id=“my_stuff”> <li id=“hide_me”> I am Iron Man </li> </ul> </div> #wrapper { width: 100%;

  • verflow: hidden;

} #my_stuff { list-style: none; } #my_stuff li { padding: 1.5em; }

Structure Presentation

document.observe(‘dom:loaded’, function() { $(‘hide_me’).hide(); $(‘my_stuff’).observe(‘click’, function() { // do something }) });

Behaviour

slide-14
SLIDE 14

Unobtrusive javascript

clean and maintainable code —just like MVC in Rails

Benefits

slide-15
SLIDE 15

Unobtrusive javascript

fits naturally in the progressive enhancement process

Benefits

slide-16
SLIDE 16

Unobtrusive javascript

makes it easy for designers and programmers to work on the same code base

Benefits

slide-17
SLIDE 17

Only (part of) a means

BUT

slide-18
SLIDE 18

Does not guarantee accessibility

BUT

slide-19
SLIDE 19

<a href=“#”>Get bacon!</a> $(‘mylink’).observe(‘click’, function(e) { window.location = ‘http://google.fi‘; });

slide-20
SLIDE 20

<a href=“#”>Get bacon!</a> $(‘mylink’).observe(‘click’, function(e) { window.location = ‘http://google.fi‘; });

Fully unobtrusive

slide-21
SLIDE 21

Not accessible at all

slide-22
SLIDE 22

JavaScript on Rails

Brief History of

slide-23
SLIDE 23

JavaScript on Rails

Brief History of

link_to_remote form_remote_tag

slide-24
SLIDE 24

JavaScript on Rails

Brief History of

link_to_remote form_remote_tag

absolutely fabulous

slide-25
SLIDE 25

BUT...

slide-26
SLIDE 26

2 problems

slide-27
SLIDE 27

2 problems

<form action=“/items” id=“add_form” method=“post”

  • nsubmit=“new Ajax.Request(‘/items’,

{asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;” style=“display: none;”>

slide-28
SLIDE 28

2 problems

<a href=“#”

  • nclick=“$(‘add_form’).toggle(); return false;”>

Add new item </a>

slide-29
SLIDE 29

Dan Webb and Luke Redpath made Rails js helpers unobtrusive provided a method for attaching behaviours to elements

UJS4Rails

reaction:

slide-30
SLIDE 30

UJS4Rails

reaction:

heavy didn’t encourage people towards progressive enhancement

slide-31
SLIDE 31

Low Pro

take two:

underlying JS framework behind UJS4Rails better to just use Low Pro than to mess with the Rails internals

slide-32
SLIDE 32

Low Pro

Event.addBehaviour() Behaviour “classes” DOM builder

slide-33
SLIDE 33

Event.addBehavior({ ‘#add_form‘ : function() { this.hide(); }, ‘#add_new_link:click‘ : function(e) { $(‘add_form’).toggle(); e.stop(); } });

slide-34
SLIDE 34

Event.addBehavior({ ‘#add_form’ : Remote.Form });

Behaviours

slide-35
SLIDE 35

Event.addBehavior({ ‘#add_form’ : Remote.Form({

  • nComplete: doSomething

}) });

Behaviours

slide-36
SLIDE 36

DOM Builder

$div({ id: ‘run-1’}, $p(‘A’, $em(“marathon”), ‘!’) ); <div id=“run-1”> <p>A<em>marathon</em>!</p> </div>

slide-37
SLIDE 37

The bastard son

var item = DOM.Builder.fromHTML( ‘<li>Remember to recover!</li>’); list.append(item);

slide-38
SLIDE 38

CODE!

slide-39
SLIDE 39

Event Delegation

slide-40
SLIDE 40

The problems

event handlers aren’t automatically assigned to dynamically added elements performance inversely related to the amount of elements

slide-41
SLIDE 41

The problems

Event.addBehavior({ ‘td:click‘ : function(e) { // execute some code for table cells } });

What if there are thousands of cells?

slide-42
SLIDE 42

The solution lies in event propagation

slide-43
SLIDE 43

table tr td

*click* *click* *click*

slide-44
SLIDE 44

Two event propagation modes event capture event bubbling

slide-45
SLIDE 45

Event.addBehavior({ ‘table:click‘ : function(e) { // do something upon a table click }, ‘td:click‘ : function(e) { // execute some code for table cells } });

slide-46
SLIDE 46

table tr td

*click* *click* *click*

event capture

slide-47
SLIDE 47

event bubbling

table tr td

*click* *click* *click*

slide-48
SLIDE 48

current W3C DOM spec supports both modes

slide-49
SLIDE 49

as do all modern browsers

slide-50
SLIDE 50

alas, not Internet Explorer

slide-51
SLIDE 51

and thus, not Prototype either

slide-52
SLIDE 52

so we’ll rely on event bubbling

(which is totally fine)

slide-53
SLIDE 53

So What?

given that every event knows its target element (Event.element()) why not observe a higher-level element and only then work according to the original target?

slide-54
SLIDE 54

Event Delegation

enter

made "famous" by Christian Heilmann with Yahoo! UI now "natively" supported by Low Pro, with Event.delegate()

slide-55
SLIDE 55

Event.addBehavior({ ‘table:click’ : Event.delegate({ ‘td‘ : function(e) { var el = e.element(); // ... }, ‘a‘ : function(e) { e.stop(); // handle link clicks } }) });

slide-56
SLIDE 56

CODE!

slide-57
SLIDE 57

Caveats

with event delegation

not all events bubble up (most notably focus and blur) can kill performance in some cases (onmousemove)

slide-58
SLIDE 58

Low Pro Behaviours

slide-59
SLIDE 59

Low Pro Behaviours

“Behaviours are an object orientated mechanism by which you can handle events and maintain the state of an element”

  • Dan Webb
slide-60
SLIDE 60

var myForm = $(‘add_form’); var ajaxForm = new Remote.Form( myForm, { method: ‘post’ } );

slide-61
SLIDE 61

Event.addBehavior({ ‘#add_form’: Remote.Form, ‘#ajax_link’ : Remote.Link });

slide-62
SLIDE 62

Event.addBehavior({ ‘#add_form’: Remote.Form({ method : ‘put’ }) });

slide-63
SLIDE 63

Behaviours bundled with Low Pro

Remote.Form, Remote.Link Drag’n’drop Auto-completer In-place editor

slide-64
SLIDE 64

...but the real benefits come from

slide-65
SLIDE 65

...but the real benefits come from

writing your own Behaviours to structure your JavaScript code

slide-66
SLIDE 66

var Hover = Behavior.create();

slide-67
SLIDE 67

var Hover = Behavior.create(); Object.extend(Hover.prototype, { initialize: function(className) { this.className = className || ‘over’; },

  • nmouseover: function() {

this.element.addClassName(this.className); },

  • nmouseout: function() {

this.element.removeClassName(this.className); } });

slide-68
SLIDE 68

var Hover = Behavior.create({ initialize: function(className) { this.className = className || ‘over’; },

  • nmouseover: function() {

this.element.addClassName(this.className); },

  • nmouseout: function() {

this.element.removeClassName(this.className); } });

slide-69
SLIDE 69

Event.addBehavior({ ‘.dongle’: Hover(‘hover’) });

slide-70
SLIDE 70

Event delegation with Behaviours

slide-71
SLIDE 71

var Calendar = Behavior.create({ // other methods hidden for clarity

  • nclick : function(e) {

var source = e.element(); e.stop(); if ($(source.parentNode).hasClassName(‘day’)) return this._setDate(source); if ($(source.parentNode).hasClassName(‘back’)) return this._backMonth(); if ($(source.parentNode).hasClassName(‘forward’)) return this._forwardMonth(); } });

slide-72
SLIDE 72

var Calendar = Behavior.create({ // other methods hidden for clarity

  • nclick : Event.delegate({

‘.day a‘ : function(e) { // set date }, ‘back a‘ : function(e) { // go back a month }, ‘.forward a‘ : function(e) { // go forward } }) });

slide-73
SLIDE 73

Linking behaviours in more complex situations

slide-74
SLIDE 74

Draggable = Behavior.create({ initialize : function(options) { // code hidden for clarity this.handle = this.options.handle || this.element; Draggable.Handle.attach(this.handle, this); } // code hidden for clarity }); Draggable.Handle = Behavior.create({ initialize : function(draggable) { this.draggable = draggable; },

  • nmousedown : function(e) {

// code hidden for clarity } });

slide-75
SLIDE 75

CODE!

slide-76
SLIDE 76

var TodoList = Behavior.create({ initialize: function() { // initialization code },

  • nclick: function() {

// do something upon a click } });

slide-77
SLIDE 77

jQuery

slide-78
SLIDE 78

jQuery

$(document).ready(function() { $(“a”).click(function() { alert(“Hello world!”); }); });

slide-79
SLIDE 79

jQuery.fn.check = function() { return this.each(function() { this.checked = true; }); }; $(“input[@type=’checkbox’]”).check();

slide-80
SLIDE 80

Low Pro for jQuery

slide-81
SLIDE 81

Hover = $.klass({ initialize: function(hoverClass) { this.hoverClass = hoverClass; },

  • nmouseover: function() {

this.element.addClass(this.hoverClass); },

  • nmouseout: function() {

this.element.removeClass(this.hoverClass); } }); jQuery(function($) { $(‘span.name’).attach(Hover, ‘myClassName’); });

slide-82
SLIDE 82

Even event delegation!

slide-83
SLIDE 83

$(‘#thing’).click($.delegate({ ‘.quit‘: function() { /* do quit stuff */ }, ‘.edit‘: function() { /* do edit stuff */ } }));

slide-84
SLIDE 84

DateSelector = $.klass({

  • nclick: $.delegate({

‘.close‘: function() { this.close(); }, ‘.day‘: function(e) { this.selectDate(e.target); } }), selectDate: function(dayElement) { // code ... }, close: function() { // code ... } });

slide-85
SLIDE 85

?

slide-86
SLIDE 86

Thank You

slide-87
SLIDE 87

Unobtrusive Prototype

by Jarkko Laine

$9

Robust, Organized Javascripting

Use code “UNOBTRUSIVE” in Google Checkout to get 50% off of any ebook

  • r screencast