Rails is not just Ruby
Marco Otte-Witte Software Engineer, Consultant, Trainer http://simplabs.com Open Source http://github.com/marcoow http://github.com/simplabs
JavaScript is serious Business!
It‘s not just adding lib after lib after lib!
Give your JavaScript the same love your Ruby code gets!
Know your tools!
Know your tools! var inputs = $$('input'); for (var i = 0; i < inputs.length; i++) { alert(inputs[i].name); }
Know your tools! X var inputs = $$('input'); for (var i = 0; i < inputs.length; i++) { } alert(inputs[i].name);
Know your tools! Protoype.js $$('input').each(function(input) { alert(input.name); }); jQuery $.each('input', function() { alert(this.name); });
Know your tools! function setupPage() { $('userName').update( readUserName(); ); } function readUserName() { //read user's name from cookie }
Know your tools! X function setupPage() { $('userName').update( readUserName(); ); } function readUserName() { //read user's name from cookie }
Know your tools! var Application = (function() { //private var readUserName = function() { //read user's name from cookie }; return { //public setupPage: function() { $('userName').update( readUserName(); ); } } })();
Know your tools! var timeout = window.setTimeout( "element.update(" + someContent + ");", 1000 );
Know your tools! X var timeout = window.setTimeout( "element.update(" + someContent + ");", 1000 );
Know your tools! Protoype.js element.update.bind(element).curry( someContent ).delay(1)
Know your tools! var loginField = document.getElementById('#user_login'); alert(loginField.value);
Know your tools! X var loginField = document.getElementById('#user_login'); alert(loginField.value);
Know your tools! Protoype.js alert($F('user_login')); jQuery alert($('#user_login').val());
Know your tools! var loginField = document.getElementById('#user_login'); loginField.style.display = 'none';
Know your tools! X var loginField = document.getElementById('#user_login'); loginField.style.display = 'none';
Know your tools! Protoype.js $('user_login').hide(); jQuery $('#user_login').hide();
Know your tools! var loginField = document.getElementById('user_login'); function loginChanged() { alert(loginField.value); } if (loginField.addEventListener) { loginField.addEventListener( 'change', loginChanged, false); } else if (obj.attachEvent) { obj.attachEvent('onchange', loginChanged); }
Know your tools! X var loginField = document.getElementById('user_login'); function loginChanged() { alert(loginField.value); } if (loginField.addEventListener) { loginField.addEventListener( 'change', loginChanged, false); } else if (obj.attachEvent) { obj.attachEvent('onchange', loginChanged); }
Know your tools! Protoype.js $('user_login').observe( 'change', function(event) { alert($F('user_login')); }); jQuery $('#user_login').change(function() { alert(this.val()); });
Write valid JavaScript!
Write valid JavaScript! someValue = 0; anotherValue = 1; function fun(param) { alert(param) }
Write valid JavaScript! X someValue = 0; anotherValue = 1; function fun(param) { alert(param) }
Write valid JavaScript! var someValue = 0; var anotherValue = 1; function fun(param) { alert(param); }
Write valid JavaScript! someValue = 0; anotherValue = 1; function fun(param) { alert(param) }
Write valid JavaScript! someValue = 0; anotherValue = 1; function fun(param) { alert(param) }
Write valid JavaScript! someValue = 0; Missing semicolon. anotherValue = 1; Implied globals: function fun(param) { someValue, anotherValue alert(param) }
JavaScript and Rails
JavaScript and Rails •RESTful actions (delete, put, post) •AJAX •Effects •etc.
the Demo App
the Demo App POST/replace
the Demo App POST/replace Code is at http://github.com/marcoow/js-and-rails
3 possible Solutions
the classic Solution
the classic Solution •Helpers (remote_form_for, link_to_remote etc.) •RJS •onclick=“... •href=“javascript:...
the classic Solution index.html.erb <div id="someElement"> some text that's replaced later </div> <%= link_to_remote 'Replace', :url => classic_solution_replace_path, :method => :post %>
the classic Solution class ClassicSolutionController < ApplicationController def index end def replace end end
the classic Solution replace.rjs page.replace_html 'someElement', :partial => 'new_content'
the classic Solution _new_content.html.erb <b>Fresh new content rendered at <%= Time.now %></b> <%= link_to_remote 'Replace again', :url => classic_solution_replace_path, :method => :post %>
the classic Solution •strong coupling •hard to maintain •no/ little code reuse •bloated HTML •code that actually belongs together is distributed over several places •easy to write in the first place
Full Separation
Full Separation •define JavaScript controls that encapsulate all frontend logic •mark elements with class, rel or HTML5‘s data-* attributes •full separation of HTML and JavaScript •Initialization of controls on dom:loaded event
Full Separation •define JavaScript controls that encapsulate all frontend logic •mark elements with class, rel or HTML5‘s data-* attributes •full separation of HTML and JavaScript •Initialization of controls on dom:loaded event
Full Separation replacer.js var Replacer = Class.create({ initialize: function(container, target) { this.container = $(container); this.target = $(target); this.container.observe('click', this.onClick.bindAsEventListener(this)); }, onClick: function(event) { event.stop(); new Ajax.Updater( this.target, this.container.href, { method: 'post', evalScripts: true } ); } });
Full Separation application.js var Application = (function() { var initializeReplacers = function() { $$('a[data-replaces]').each(function(replacingLink) { if (!replacingLink._initializedReplacer) { new Replacer(replacingLink, replacingLink.readAttribute('data-replaces')); replacingLink._initializedReplacer = true; } }); }; return { setupOnLoad: function() { initializeReplacers(); }, setupOnPageUpdate: function() { initializeReplacers(); } } })();
Full Separation application.js document.observe('dom:loaded', function() { Application.setupOnLoad(); Ajax.Responders.register({ onComplete: Application.setupOnPageUpdate }); });
Full Separation application.js document.observe('dom:loaded', function() { Application.setupOnLoad(); Ajax.Responders.register({ onComplete: Application.setupOnPageUpdate }); }); Replacer controls are initialized on page load and after every AJAX request
Full Separation index.html.erb <div id="someElement"> some text that's replaced later </div> <%= link_to 'Replace', full_separation_replace_path, :'data-replaces' => 'someElement' %>
Full Separation class FullSeparationController < ApplicationController def index end def replace respond_to do |format| format.js { render :partial => 'new_content' } end end end
Full Separation _new_content.html.erb <b>Fresh new content rendered at <%= Time.now %></b> <%= link_to 'Replace again', full_separation_replace_path, :'data-replaces' => 'someElement' %>
Full Separation •clean, semantic HTML •full separation of concerns •clean HTML/CSS/JS is crucial •Behaviour is (kind of) implicit •discipline required
explicit Controls
explicit Controls •like Full Separation •but controls are initialized in the templates •more explicit/ obvious what‘s going on
explicit Controls index.html.erb <div id="someElement"> some text that's replaced later </div> <%= link_to 'Replace', controls_replace_path, :id => 'replacerLink' %> <script type="text/javascript" charset="utf-8"> document.observe('dom:loaded', function() { var replacer = new Replacer('replacerLink', 'someElement'); }); </script>
explicit Controls class ControlsController < ApplicationController def index end def replace respond_to do |format| format.js { render :partial => 'new_content' } end end end
explicit Controls _new_content.html.erb <b>Fresh new content rendered at <%= Time.now %></b> <%= link_to 'Replace again', controls_replace_path, :id => 'secondReplacerLink' %> <script type="text/javascript" charset="utf-8"> var newReplacer = new Replacer( 'secondReplacerLink', 'someElement' ); </script>
explicit Controls _new_content.html.erb <b>Fresh new content rendered at <%= Time.now %></b> <%= link_to 'Replace again', controls_replace_path, :id => 'secondReplacerLink' %> <script type="text/javascript" charset="utf-8"> var newReplacer = new Replacer( 'secondReplacerLink', 'someElement' ); </script> No initialization on dom:loaded here as this is the result of an AJAX request (dom:loaded not fired)
explicit Controls •HTML is (mostly) clean and semantic •Behaviour is explicit in the templates •easier to grasp what‘s going on than with Full Separation •though not as nice as full separation
Either go with Full Separation or explicit Controls!
Avoid the classic Solution when you can!
Resources
Resources •http://ejohn.org/apps/learn/ •http://www.jslint.com/ •http://api.prototypejs.org/ •http://docs.jquery.com •http://github.com/marcoow/js-and-rails •http://javascriptrocks.com
Q&A

Rails is not just Ruby