This is our first presentation on NinjaScript, a tool by Logical Reality Design. Evan Dorn presented this to the LA Ruby Meetup on October 14, 2010.
NinjaScript: Javascript so unobtrusive, you'll never see it coming.
NinjaScript, which runs on top of jQuery, provides extremely easy-to-use UJS tools including instant conversion of regular forms and links into AJAX equivalents. NS provides for reusable behaviors and automatic rebinding of events in a way that is more flexible than event-delegation methods like jQuery live().
4. NinjaScript Provides
• A behavior layer built on jQuery
• Easy UJS AJAX that degrades gracefully
• Persistent behavior that just works
• CSS-like syntax for rich JS behavior
• Cool prepackaged behaviors
5. What do we want?
(In an AJAXy, RESTful Rails app)
6. What do we want?
(In an AJAXy, RESTful Rails app)
• JavaScript should be unobtrusive
7. What do we want?
(In an AJAXy, RESTful Rails app)
• JavaScript should be unobtrusive
• AJAX actions should degrade gracefully
8. What do we want?
(In an AJAXy, RESTful Rails app)
• JavaScript should be unobtrusive
• AJAX actions should degrade gracefully
• RESTful methods should degrade gracefully
9. What do we want?
(In an AJAXy, RESTful Rails app)
• JavaScript should be unobtrusive
• AJAX actions should degrade gracefully
• RESTful methods should degrade gracefully
• This shouldn’t take any extra work!
10. Rails 2 Approach
• AJAX helpers: link_to_remote and
form_remote_for
• REST methods (PUT and DELETE)
simulated via a hidden form field
• link_to_remote() arguments aren’t the
same format as link_to()
11. Rails 2 Approach: links
link_to_remote(‘click me’, :url => some_path)
<a href=”#” onclick=”bleaaaaaaaaaaaaaaaaaaaarghh oh please
just kill me now my eyes are bleeding”>
click me
</a>
12. Rails 2 Approach: links
link_to_remote(‘click me’, :url => some_path)
<a href=”#” onclick=”bleaaaaaaaaaaaaaaaaaaaarghh oh please
just kill me now my eyes are bleeding”>
click me
</a>
Graceful degradation requires an extra argument:
link_to_remote(‘click me’, { :url => some_path },
{ :href => some_path } )
13. Rails 2 Approach: forms
form_remote_for(@object)
<form action=”/objects/1” method=”post” onsubmit=”lots
more. Horrible_JavaScript_crap_here uglifying_my_html”>
<input name=”_method” type=”hidden” value=”put”>
... your fields here ...
</a>
Non - POST/GET requests simulated via hidden
input.
14. Rails 3 Approach: links
link_to with :remote => true
<%= link_to ‘click me’, ‘/foo’, :remote => true,
:method => :delete %>
Generates:
<a href=”/foo” data-method=”delete” data-remote=”true”>
click me
</a>
Unobtrusive invisible form created by rails.js
15. Rails 3 Approach: forms
form_for with :remote => true
<%= form_for @product, :remote => true do |form| %>
<%= ... your inputs here ... %>
Generates:
<form action=”/products/1” data-method=”put” data-
remote=”true”>
... your inputs here ...
</form>
Hidden _method input added by rails.js upon submit
18. Rails 3 Approach: forms
• So Rails 3 gives us UJS ...
• ... At the cost of making REST dependent
on JavaScript
19. Rails 3 Approach: forms
• So Rails 3 gives us UJS ...
• ... At the cost of making REST dependent
on JavaScript
• A fresh scaffold - non AJAX - won’t even
work without JS in Rails 3
20. Rails 3 Approach: forms
• So Rails 3 gives us UJS ...
• ... At the cost of making REST dependent
on JavaScript
• A fresh scaffold - non AJAX - won’t even
work without JS in Rails 3
• End-to-end integration testing can’t be
done without Selenium or similar
21. Rails 3 Approach: forms
• So Rails 3 gives us UJS ...
• ... At the cost of making REST dependent
on JavaScript
• A fresh scaffold - non AJAX - won’t even
work without JS in Rails 3
• End-to-end integration testing can’t be
done without Selenium or similar
• ... oops
22. Use jQuery?
• Bind event handler to each form that
submits via AJAX.
• ... kind of a pain, though ...
23. Use jQuery?
$(document.ready(function() {
$(‘form#make_me_ajax’).submit(function (){
$.post(
”/products”,
$(“form#make_me_ajax”).serialize(),
function(data) {
... some return behavior ...
}
);
});
});
( To be fair, there are plugins that make this easier,
like jQuery Form etc. )
26. The NinjaScript Way
Specify behaviors of many elements in a
CSS-like format.
$.behavior({
'form#product': submits_as_ajax(),
‘a.delete_link’: submits_as_ajax(),
‘flash.error’: decays()
})
32. Persistent Behavior
• We take CSS’s default behavior for granted.
• CSS styles apply to everything in the DOM,
even if it changes.
• Why aren’t JS behaviors the same way?
35. DOM Reconstruction and
Event Bindings
• Bind a tooltip behavior
div#content
to $(‘.has_tooltip’)
a.has_tooltip a.has_tooltip
36. DOM Reconstruction and
Event Bindings
• Bind a tooltip behavior
div#content
to $(‘.has_tooltip’)
• Dynamically add a
new .tooltip element a.has_tooltip a.has_tooltip
a.has_tooltip
37. DOM Reconstruction and
Event Bindings
• Bind a tooltip behavior
div#content
to $(‘.has_tooltip’)
• Dynamically add a
new .tooltip element a.has_tooltip a.has_tooltip
• New element has no
bound event handler!
a.has_tooltip
38. DOM Reconstruction and
Event Bindings
• Bind a tooltip behavior
div#content
to $(‘.has_tooltip’)
• Dynamically add a
new .tooltip element a.has_tooltip a.has_tooltip
• New element has no
bound event handler!
a.has_tooltip
• This is really lame ...
40. What we want
• Our JS should specify, via CSS selectors,
behaviors that apply to elements
41. What we want
• Our JS should specify, via CSS selectors,
behaviors that apply to elements
• Those behaviors should always apply in all
conditions
42. What we want
• Our JS should specify, via CSS selectors,
behaviors that apply to elements
• Those behaviors should always apply in all
conditions
• The coder shouldn’t have to waste
braincycles on it
44. Solution 1:
Event Delegation
• jQuery live() and similar
45. Solution 1:
Event Delegation
• jQuery live() and similar
• Bind handlers to root element of the DOM
46. Solution 1:
Event Delegation
• jQuery live() and similar
• Bind handlers to root element of the DOM
• Since browsers bubble unhandled events up
the DOM, the root handles all events
47. Solution 1:
Event Delegation
• jQuery live() and similar
• Bind handlers to root element of the DOM
• Since browsers bubble unhandled events up
the DOM, the root handles all events
This is pretty awesome.
49. The Problem with
Event Delegation
• live() can’t handle element transformations
50. The Problem with
Event Delegation
• live() can’t handle element transformations
• ... like giving <div>s rounded corners ...
51. The Problem with
Event Delegation
• live() can’t handle element transformations
• ... like giving <div>s rounded corners ...
• Because there’s no event to process when
we insert new elements into the DOM
52. The Problem with
Event Delegation
• live() can’t handle element transformations
• ... like giving <div>s rounded corners ...
• Because there’s no event to process when
we insert new elements into the DOM
• ECMA-compliant browsers are suppossed to send
DOMSubtreeModified and DOMNodeInserted
53. The Problem with
Event Delegation
• live() can’t handle element transformations
• ... like giving <div>s rounded corners ...
• Because there’s no event to process when
we insert new elements into the DOM
• ECMA-compliant browsers are suppossed to send
DOMSubtreeModified and DOMNodeInserted
(Bad IE! No Biscuit!)
56. Solution 1I:
Automatic Rebinding
• Bind to and/or transform the element, not
the root.
• NinjaScript examines new DOM elements
to see if any known behaviors match.
• NinjaScript fires its own event when the
DOM is reconstructed so it works in IE.
59. Defining a Behavior
• Make a mapping of selectors to behaviors
and pass it to $.behavior()
60. Defining a Behavior
• Make a mapping of selectors to behaviors
and pass it to $.behavior()
• Each behavior can include an events block
and a transform.
61. Defining a Behavior
• Make a mapping of selectors to behaviors
and pass it to $.behavior()
• Each behavior can include an events block
and a transform.
• Transform is applied as soon as the element
is in the DOM.
62. Defining a Behavior
• Make a mapping of selectors to behaviors
and pass it to $.behavior()
• Each behavior can include an events block
and a transform.
• Transform is applied as soon as the element
is in the DOM.
• Events blocks are a mapping of click:,
focus:, mouseover: etc. to behaviors.
63. Defining a Behavior
$.behavior({
'.awesome': {
transform: function(elem){... do something ...},
events: {
click: function(evnt,elem){... handle click ...},
mouseover: function(evnt,elem){... pop tooltip ...}
}
}
})
All awesome elements will get the transform
applied, no matter when they’re added.
64. Defining a Behavior
Shortcut: skip the ‘events:’ mapping if you’re just
handling an event.
$.behavior({
'.awesome': {
click: function(event,elem){ ... handle click ...},
}
})
66. Reusable Behaviors
You can easily define and name behaviors for
reuse.
function sings_and_dances(){
return new behavior({
transform: function(elem) { elem.add_a_tutu() },
click: function(evnt,elem){ elem.pirouette() },
mouseover: function(evnt,elem){ elem.sing_an_aria()}
})
}
67. Reusable Behaviors
You can easily define and name behaviors for
reuse.
function sings_and_dances(){
return new behavior({
transform: function(elem) { elem.add_a_tutu() },
click: function(evnt,elem){ elem.pirouette() },
mouseover: function(evnt,elem){ elem.sing_an_aria()}
})
}
$.behavior({
‘#dhh’: sings_and_dances(),
‘#katz’: sings_and_dances(),
‘.code_monkey’: signs_and_dances()
})
68. Pre-Defined Behaviors
These work today - built into NinjaScript
• submits_as_ajax
• submits_as_ajax_form
• submits_as_ajax_link
• becomes_ajax_link (for forms)
• decays (fades away after 10 sec)
71. Future Plans
• Complete replacement for rails.js
• NinjaHelper Rails plugin/gem to make links
and forms with non GET/POST methods
degrade gracefully
• Prototype / MooTools / YUI support
(maybe)
72. Come Help Us!
NinjaScript is at version 0.6
fork: http://github.com/lrdesign/NinjaScript
http://groups.google.com/group/lrd-ninjascript