Posted over 2 years back at Luke Redpath - Home
I’m currently sitting in the main congress hall awaiting the start of Thomas Fuch’s presentation. As I’m sure many readers of my blog are aware, I’m a huge advocate of testing, or to be specific, good testing. However, one thing that I’ve never managed to get into is unit testing with JavaScript. I’ve never really been aware of the tools available or how well it works.
I’m going to write this post live; hopefully it won’t just come across as a mish-mash of incoherent ramblings.
- Traditional JavaScript “testing†generally revolves around <filter:jscode lang='javascript'>alert</filter>. it can be useful sometimes but it generally doesn’t offer anything interesting. Use it sparingly.
- There are many common misconceptions or attitudes towards JavaScript: “we don’t need tests for 10 lines of codeâ€; “but you can’t debug JavaScriptâ€. Wrong and wrong again. Enter unit testing for JavaScript.
- Some options exist already, such as jsunit. These have their problems, such as awkward syntax or problems with Prototype.
- Scriptaculous has its own unittest.js library. All you need to do is include the unittest.js file and an empty <filter:jscode lang='html'>div</filter> with a “testlog†HTML ID (defaults). Now you’re ready to roll with Test.Unit.Runner.
- Test.Unit.Runner looks quite similar to the standard xUnit family of testing libraries. It has the standard setup and teardown methods and a suite of assertions.
- It has some good testing functionality for dealing with effects, and time-sensitive behaviour (wait function).
- It has a basic benchmarking system too!
- Rake integration is provided through a the javascript_test plugin – it allows you to launch browsers and run your javascript unit tests through a single command: <filter:jscode lang='shell'>rake test:javascripts</filter>. It uses a WEBrick server and supports multiple browsers, including Camino, Firefox, Safari and Internet Explorer. It’s fairly trivial to add support for other browsers too. It seems to be missing the functionality to close the browser after the rake test run. Perhaps the browser definition classes could be extended to support a “close†method that is called after the test run?
- The plugin also provides a generator for generating test stub files (HTML format) in test/javascript.
- Did I mention the browser-based test runner looks pretty sweet?
- OK, here’s the killer feature so far: RSpec-style syntax, including should* style expectations and context/spec format. I’m convinced, sign me up!
- Some other hints and tips: If you aren’t using Firebug already, then start using it! It is awesome and makes debugging AJAX and general JavaScript problems a lot easier. (as an aside, its also great for debugging your behaviours when you are using UJS). There is also a tool for Safari called Drosera.
- Don’t forget your RJS templates either. RJS has some basic debugging support built-in (although this could really do with being expanded upon).
During the question and answer session, Simon Willison mentioned a tool that was part of the Dojo Toolkit that uses a command-line JavaScript interpreter to run its unit tests. It sounds interesting although I feel its a lot more useful to run the tests in the actual environment they are going to run in (i.e. the browsers). By introducing something like a separate interpreter that end-users aren’t going to be using, you are relying on that interpreter being correct.
All in all, it was a good talk and has really got me interested in unit testing with JavaScript. It’s definately something I should be doing, especially as I am so strict about testing with the rest of my code.
Finally, a word of warning for those who want to try a bit of live blogging: IRC is evil and distracting. Use with caution!
Links and useful information:
Posted over 2 years back at Luke Redpath - Home
The Decorator Pattern is a design pattern that enables you to dynamically wrap behaviour around an existing object at runtime. It is especially useful when an object can have many variables that can be combined in different ways, which in turn affect it’s behaviour.
This small, simple implementation of the decorator pattern in Ruby sums up what I love about the hottest dynamic language on the planet.
I’ll borrow a simple example from the excellent Head First Design Patterns book by Eric Freeman, Elisabeth Freeman, Kathy Sierra, and Bert Bates.
Let’s say you want to calculate the cost of a cup of coffee. You have Coffee class, which implements a cost() method. For the purposes of this example, let’s just hardcode a value:
class Coffee
def cost
2
end
end
Great. But what if we want the cost of a coffee with milk? We could have a new class:
class WhiteCoffee
def cost
2.4
end
end
OK. But now we want cream. And sprinkles. Clearly, creating new classes is going to lead to a huge number of classes in our application. It just isn’t realistic to create classes for different combinations of coffee and extras. It could get worse – what if we have different types of coffee? We would then have to have combinations of extras with each different type of coffee. It just isn’t going to work. Enter the decorator pattern. Here’s the the 8 lines of Ruby mentioned in this entry’s title:
module Decorator
def initialize(decorated)
@decorated = decorated
end
def method_missing(method, *args)
args.empty? ? @decorated.send(method) : @decorated.send(method, args)
end
end
And thats all you need. You can include this into any class you want to act as a decorator. You can then use that decorator as if it was the object it is decorating; by default all messages sent to the decorator are forwarded on to the decorated object. You can then decorate the methods you need to extend:
class Milk
include Decorator
def cost
@decorated.cost + 0.4
end
end
So how does this solve our original problem? The real power of decorators lies in the fact that they can act like the objects they are decorating. By taking this one step further, you can wrap decorators with other decorators as long as they share the same interface. By creating decorators for our different “extras”, we can create coffees using a combination of decorators and get the total cost of the coffee.
class Whip
include Decorator
def cost
@decorated.cost + 0.2
end
end
class Sprinkles
include Decorator
def cost
@decorated.cost + 0.3
end
end
Whip.new(Coffee.new).cost
#=> 2.2
Sprinkles.new(Whip.new(Milk.new(Coffee.new))).cost
#=> 2.9
Of course, there’s nothing stopping us from making life easier with a few factory methods:
class CoffeeFactory
def self.latte
SteamedMilk.new(Espresso.new)
end
def self.cappuccino
Sprinkles.new(Cream.new(Milk.new(Coffee.new)))
end
end
order = Order.new
order.add(Coffee.new)
order.add(CoffeeFactory.cappuccino)
puts order.total
Due to Ruby’s highly dynamic nature, the Decorator pattern isn’t the only way of extending class functionality at runtime, however I just love how simple it is to implement the pattern in Ruby. For more information on implementing decorators in Ruby, including generic decorators and alternatives to the traditional decorator pattern, see the DecoratorPattern page at the RubyGarden.
Finally, it would be nice to maintain some kind of identity when using decorators. The RubyGarden page above has one approach. Whilst we aren’t using inheritance, it would be nice to retain one of its features:
CoffeeFactory.cappucino.kind_of? Coffee
#=> true
I can think of a few ways of extending my Decorator class to retain this functionality, but I’m gonna leave this one to the reader. Over to you.
Update: In typical Ruby-fashion, my 8 lines of code has been beaten by, well, zero lines of code by Trevor Squires. Using modules, super and extend, Trevor came up with this alternative solution, which maintains identity as well. Bow down to his Ruby-fu.
Update 2: Never one to step back from a challenge, I see Trevor’s Ruby-fu and raise him with a bit of my own. I still feel my decorator implementation carries some weight, with a nice bit of syntatic sugar:
class Milk
include Decorator
end
class Whip
include Decorator
end
class Sprinkles
include Decorator
end
# normal coffee
Coffee.new
# coffee with milk, whip and sprinkles
Coffee.with :milk, :whip, :sprinkles
The self.with method could do with being extracted into a Decoratable module but here’s a concrete implementation. Trevor’s method still has an advantage in that it maintains object identity, but I have some ideas to solve that. But alas, it is past 1am and bed beckons.
Update 3: Trevor fights back with his own self.with() implementation. Personally I prefer the extend implementation, it eliminates the symbol to class trickery, and still maintains identity. His ’fu is still strong.
However, there is one downside to Trevor’s method: you can only apply a decorator to an object using <filterjscode lang="ruby" inline="true">extend once. But what if you wanted double sprinkles?
Sprinkles.new(Sprinkles.new(Coffee.new))
# or with a bit of sugar (no pun intended)
Coffee.with :sprinkles, :sprinkles
</code></pre>
Posted over 2 years back at Luke Redpath - Home
The Decorator Pattern is a design pattern that enables you to dynamically wrap behaviour around an existing object at runtime. It is especially useful when an object can have many variables that can be combined in different ways, which in turn affect it’s behaviour.
This small, simple implementation of the decorator pattern in Ruby sums up what I love about the hottest dynamic language on the planet.
I’ll borrow a simple example from the excellent Head First Design Patterns book by Eric Freeman, Elisabeth Freeman, Kathy Sierra, and Bert Bates.
Let’s say you want to calculate the cost of a cup of coffee. You have Coffee class, which implements a cost() method. For the purposes of this example, let’s just hardcode a value:
class Coffee
def cost
2
end
end
Great. But what if we want the cost of a coffee with milk? We could have a new class:
class WhiteCoffee
def cost
2.4
end
end
OK. But now we want cream. And sprinkles. Clearly, creating new classes is going to lead to a huge number of classes in our application. It just isn’t realistic to create classes for different combinations of coffee and extras. It could get worse – what if we have different types of coffee? We would then have to have combinations of extras with each different type of coffee. It just isn’t going to work. Enter the decorator pattern. Here’s the the 8 lines of Ruby mentioned in this entry’s title:
module Decorator
def initialize(decorated)
@decorated = decorated
end
def method_missing(method, *args)
args.empty? ? @decorated.send(method) : @decorated.send(method, args)
end
end
And thats all you need. You can include this into any class you want to act as a decorator. You can then use that decorator as if it was the object it is decorating; by default all messages sent to the decorator are forwarded on to the decorated object. You can then decorate the methods you need to extend:
class Milk
include Decorator
def cost
@decorated.cost + 0.4
end
end
So how does this solve our original problem? The real power of decorators lies in the fact that they can act like the objects they are decorating. By taking this one step further, you can wrap decorators with other decorators as long as they share the same interface. By creating decorators for our different “extrasâ€, we can create coffees using a combination of decorators and get the total cost of the coffee.
class Whip
include Decorator
def cost
@decorated.cost + 0.2
end
end
class Sprinkles
include Decorator
def cost
@decorated.cost + 0.3
end
end
Whip.new(Coffee.new).cost
#=> 2.2
Sprinkles.new(Whip.new(Milk.new(Coffee.new))).cost
#=> 2.9
Of course, there’s nothing stopping us from making life easier with a few factory methods:
class CoffeeFactory
def self.latte
SteamedMilk.new(Espresso.new)
end
def self.cappuccino
Sprinkles.new(Cream.new(Milk.new(Coffee.new)))
end
end
order = Order.new
order.add(Coffee.new)
order.add(CoffeeFactory.cappuccino)
puts order.total
Due to Ruby’s highly dynamic nature, the Decorator pattern isn’t the only way of extending class functionality at runtime, however I just love how simple it is to implement the pattern in Ruby. For more information on implementing decorators in Ruby, including generic decorators and alternatives to the traditional decorator pattern, see the DecoratorPattern page at the RubyGarden.
Finally, it would be nice to maintain some kind of identity when using decorators. The RubyGarden page above has one approach. Whilst we aren’t using inheritance, it would be nice to retain one of its features:
CoffeeFactory.cappucino.kind_of? Coffee
#=> true
I can think of a few ways of extending my Decorator class to retain this functionality, but I’m gonna leave this one to the reader. Over to you.
Update: In typical Ruby-fashion, my 8 lines of code has been beaten by, well, zero lines of code by Trevor Squires. Using modules, super and extend, Trevor came up with this alternative solution, which maintains identity as well. Bow down to his Ruby-fu.
Update 2: Never one to step back from a challenge, I see Trevor’s Ruby-fu and raise him with a bit of my own. I still feel my decorator implementation carries some weight, with a nice bit of syntatic sugar:
class Milk
include Decorator
end
class Whip
include Decorator
end
class Sprinkles
include Decorator
end
# normal coffee
Coffee.new
# coffee with milk, whip and sprinkles
Coffee.with :milk, :whip, :sprinkles
The self.with method could do with being extracted into a Decoratable module but here’s a concrete implementation. Trevor’s method still has an advantage in that it maintains object identity, but I have some ideas to solve that. But alas, it is past 1am and bed beckons.
Update 3: Trevor fights back with his own self.with() implementation. Personally I prefer the Coffee.with :sym, :sym syntax over Coffee.with Module, Module syntax, but thats just personal preference. Building on his extend implementation, it eliminates the symbol to class trickery, and still maintains identity. His ‘fu is still strong.
However, there is one downside to Trevor’s method: you can only apply a decorator to an object using <filterjscode inline='true' lang='ruby'>extend</filter:jscode> once. But what if you wanted double sprinkles?
Sprinkles.new(Sprinkles.new(Coffee.new))
# or with a bit of sugar (no pun intended)
Coffee.with :sprinkles, :sprinkles
Posted over 2 years back at Cody Fauser
Geoffrey Grosenbach a.k.a. Topfunky of nuby on rails fame has launched an excellent new Rails screencast series called PeepCode.
The first episode in the series is on RJS templates. I was able to watch the screencast before its release and I was mightily impressed.
Geoffrey gives RJS a very thorough treatment from the basics of getting started all the way up to advanced concepts like collection proxies and testing RJS with ARTS.
I would highly recommend this screencast even if you're already familiar with RJS because you'll definitely learn a new trick or two. Congratulations to Geoffrey on a job well done.
Posted over 2 years back at Ajax on Rails
Ajaxified Drag Drop Tree in RoR
CASE STUDY
I m providing a very generalized use case where the tree fits in a good position. Here it is…
Consider a model Item, a controller Items. Item model is using a fabulous acts_as_tree and we are going to put a seed for Item to grow it in an ajax tree
… Ok no more non-code talk. So, lets start the code now…
==========================================================
I have also incorporated the code into a sample application which you can directly check out and try the tree yourself if you find it a
headache to add the following code in a number of described files.
So, here is the Sample Tree Application
or you can try to code yourself as…
Create a sample rails application say treeapp by running
rails treeapp
from the command prompt.
Now simply change your directiry into just created treeapp and make sure that you are in the directory treeapp
Now configure the database settings for this application by modifying the file /config/database.yml as …
development:
adapter: mysql
database: tree_dev
username: root
password: root
host: localhost
Here it simply shows that you have a mysql database named tree_dev and a user root with password root can access this database. So make sure about these settings.
From the command prompt in application root(i.e. you are in the directory treeapp) run this command to generate the model Item…
treeapp> ruby script/generate model item
Add the following code to the file app/models/itme.rb
class Item < ActiveRecord::Base
acts_as_tree
validates_presence_of :name
attr_accessor :style
def self.roots
self.find(:all, :conditions=>["parent_id = ?", 0])
end
def level
self.ancestors.size
end
end
This simply shows that you should have a table named tems in your database…
so why we havnt mentioned it earlier ?
Thats the thing which will make you feel an agile web development.
Now look at the directory db/migrateand a you will find a file named as db/migrate/001_create_items.rb
Add the following code to this file 001_create_items.rb
Here we are creating our database table and also adding some initial data to work with.
class CreateItems < ActiveRecord::Migration
def self.up
create_table "items", :force => true do |t|
t.column "name", :string
t.column "created_at", :datetime
t.column "parent_id", :integer, :default => 0, :null => false
end
%w(item1 item2 item3 item4 item5).each do |name|
parent = Item.new(:name=>name)
parent.save
Item.create(:name=>name+".1", :parent_id=>parent.id)
Item.create(:name=>name+".2", :parent_id=>parent.id)
Item.create(:name=>name+".3", :parent_id=>parent.id)
end
end
def self.down
drop_table :items
end
end
Now from the command line from the root of your application run the following command to have a table named Item in your database with some initial data.
treeapp> rake db:migrate
Before we start handling our views and controller part just have a smart small image named as drag.gif in your public/images directory that we will use as a handle to drag the nodes. So, now you can see a small image at public/images/drag.gif, cool !.
Now from the command line from the root of your application run the following command to create a controller …
treeapp> ruby script/generate controller items show
Make sure that now you have the files app/controllers/items_controller.rb and app/views/items/show.rhtml.
Add the following code in the file app/controllers/items_controller.rb
class ItemsController < ApplicationController
def show
@items = Item.find(:all)
@item = Item.find(:first)
# select according to your choice...
#this item will be selected node by default in the tree when it will first be loaded.
end
def display_clicked_item
# this action will handle the two way syncronization...all the tree nodes(items) will be linked
# to this action to show the detailed item on the left of the tree when the item is clicked
# from the tree
if request.xhr?
@item = Item.find(params[:id]) rescue nil
if @item
# the code below will render all your RJS code inline and
# u need not to have any .rjs file, isnt this interesting
render :update do |page|
page.hide "selected_item"
page.replace_html "selected_item", :partial=>"items/item", :object=>@item
page.visual_effect 'toggle_appear', "selected_item"
end
else
return render :nothing => true
end
end
end
def sort_ajax_tree
if request.xhr?
if @item = Item.find(params[:id].split("_").first) rescue nil
parent_item = Item.find(params[:parent_id])
render :update do |page|
@item.parent_id = parent_item.id
@item.save
@items=Item.find(:all)
page.replace_html "ajaxtree", :partial=>"items/ajax_tree", :object=>[@item,@items]
page.hide "selected_item"
page.replace_html "selected_item", :partial=>"items/item", :object=>@item
page.visual_effect 'toggle_appear', "selected_item"
end
else
return render :nothing => true
end
end
end
end
Add the following code in the file app/views/items/show.rhtml
<h2>Ajax Tree Application</h2>
<div id=”ajaxtree” style=”width:40%;float:left;”>
<%= render :partial=>’items/ajax_tree’, :object=>[@item,@items] %>
</div>
<div id=”selected_item”>
<%= render :partial=>’items/item’, :object=>@item %>
</div>
Add the following code in the file app/views/items/_item.rhtml
<% if @item %>
<h2>Selected Item is <%=h @item.name%> </h2>
<% else %>
Item not found
<% end %>
Add the following code in the file app/views/items/_ajax_tree.rhtml
<script type="text/javascript">
function toggleDiv()
{
Element.toggle('mytree');
Element.toggle('expanded');
Element.toggle('collapsed');
return false;
}
function showDrag()
{
var drag_images = $$('img.drag_image');
drag_images.all(function(value,index){return value.style.display='inline';});
Element.toggle('done');
Element.toggle('reorder');
return false;
}
function hideDrag()
{
var drag_images = $$('img.drag_image');
drag_images.all(function(value,index){return value.style.display='none';});
Element.toggle('done');
Element.toggle('reorder');
return false;
}
</script>
<style>
.mytree{padding:0 0 0 0px;}
.mytree li {padding:2 0 0 3px;}
.outer_tree_element{margin:0 0 0 10px;}
.inner_tree_element{margin:5px 0 0 10px;}
.mytree a{text-decoration:none; font-size:13px; color:black;}
.mytree a:hover{background-color:lightblue;}
.mytree label{font-weight:normal;}
.highlighted{background-color:lightblue;}
.normal{background-color:white;}
.drag_image{border:0px;}
</style>
<div id="mytree" class="mytree">
<% @ancestors = @item.ancestors.collect{|parent| parent.id} if @item.has_parent? %>
<% @items = Item.find(:all) %>
<%= get_tree_data(@items, 0){|n|
link_to_remote(n.name,
:url=>{:controller=>'items', :action=>'display_clicked_item', :id=>n.id},
:loading=>"Element.show('tree_indicator')",
:complete=>"Element.hide('tree_indicator')"
)}
%>
<% @items.each do |node| %>
<%= draggable_element node.id.to_s+'_tree_div',:revert=>true,:snap=>false, :handle=>"'#{node.id.to_s}_drag_image'" %>
<%= drop_receiving_element node.id.to_s+'_tree_div',
:accept=>'inner_tree_element',
:url=>{:controller=>'items',:action=>'sort_ajax_tree', :parent_id=>node.id,:id=>nil},
:loading=>"Element.show('sort_tree_indicator')",
:complete=>"Element.hide('sort_tree_indicator')"
%>
<% end %>
<%= image_tag 'indicator.gif', :id=>'tree_indicator', :style=>'display:none' %>
<%= image_tag 'indicator.gif', :id=>'sort_tree_indicator', :style=>'display:none' %>
</div>
<script type="text/javascript">
var selected_el = document.getElementById('<%=@item.id%>_tree_item');
selected_el.className='highlighted';
function toggleMyTree(id)
{
Element.toggle(id+'collapsed');
Element.toggle(id+'expanded');
Element.toggle(id+'children');
return false;
}
function toggleBackground(el)
{
// using collection proxies to change the background
var highlighted_el = $$("span.highlighted");
highlighted_el.all(function(value,index){return value.className='normal'});
el.className='highlighted';
selected_el = el;
return false;
}
function openMyTree(id)
{
Element.hide(id+'collapsed');
Element.show(id+'expanded');
Element.show(id+'children');
return false;
}
</script>
As you can see in the above file we have used some indicator and toggle images. So you will be required to have three more images in the directory public/images/.
Here is the small description about these images…
- An indicator image that will be displayed at the bottom of the tree whenever a tree node is clicked. You can select from a number of Ajax Inidicatorsavailable on the web. Select one indicator image and save in your app with the name indicator.gif. So, now make sure that you can see the image at public/images/indicator.gif
- Second, we need to have a small image with + sign. That will be used to toggle the tree. save it as public/images/collapsed.gif
- Similarly, an image with - sign. Save it as public/images/expanded.gif
We have to include the prototype and scriptaculous javascript libraries in the application.
So just manually create a layout file app/views/layouts/application.rhtml and add the following code in the file application.rhtml
<html>
<head>
<%= javascript_include_tag :defaults %>
</head>
<body>
<%= @content_for_layout %>
</body>
</html>
Now the last but the most importatnt…The recursion to obtain the tree.
Add the following code in the file app/helpers/application_helper.rb
module ApplicationHelper
def get_tree_data(tree, parent_id)
ret = "<div class='outer_tree_element' >"
tree.each do |node|
if node.parent_id == parent_id
node.style = (@ancestors and @ancestors.include?(node.id))? 'display:inline' : 'display:none'
display_expanded = (@ancestors and @ancestors.include?(node.id))? 'inline' : 'none'
display_collapsed = (@ancestors and @ancestors.include?(node.id))? 'none' : 'inline'
ret += "<div class='inner_tree_element' id='#{node.id}_tree_div'>"
if node.has_children?
ret += "<img id='#{node.id.to_s}expanded' src='/images/expanded.gif' onclick='javascript: return toggleMyTree(\"#{node.id}\"); ' style='display:#{display_expanded}; cursor:pointer;' /> "
ret += "<img style='display:#{display_collapsed}; cursor:pointer;' id='#{node.id.to_s}collapsed' src='/images/collapsed.gif' onclick='javascript: return toggleMyTree(\"#{node.id.to_s}\"); ' /> "
end
ret += " <img src='/images/drag.gif' style='cursor:move' id='#{node.id}_drag_image' align='absmiddle' class='drag_image' /> "
ret += "<span id='#{node.id}_tree_item'>"
ret += yield node
ret += "</span>"
ret += "<span id='#{node.id}children' style='#{node.style}' >"
ret += get_tree_data(node.children, node.id){|n| yield n}
ret += "</span>"
ret += "</div>"
end
end
ret += "</div>"
return ret
end
end
Now you can check the tree functionality at http://localhost:3000/items/show.. assuming that you are running your server on port 3000. njoy!!
Posted over 2 years back at Cody Fauser
First I'd like to thank the 600 or so people who bought RJS Templates for Rails in the past week.
I'd also like to thank all of the people who contacted me with their feedback and errata.
On that note, I'd like to let you know that if you have already purchased the book that you can go and download an updated, hopefully errata free, version from O'Reilly.
Posted over 2 years back at Cody Fauser
I'm happy to announce the availability of RJS Templates for Rails published by O'Reilly. Here's the intro from the book:
RJS templates are an exciting and powerful new type of template added to Rails 1.1. Unlike conventional Rails templates that generate HTML or XML, RJS templates generate JavaScript code that is executed when it is returned to the browser. This JavaScript generation allows you to perform multiple page updates in-place without a page reload using Ajax. All the JavaScript you need is generated from simple templates written in Ruby. This document helps you get acquainted with how RJS templates fit into the Rails framework and gets you started with a few easy-to-follow examples.
The book covers all aspects and features of RJS that are included in Rails 1.1. It also walks through a few examples, debugging with FireBug, and finishes off with some reference material.
I'm really happy with how the book has turned out and I think you'll really enjoy it.
Posted over 2 years back at Luke Redpath - Home
Rails makes a lot of things easier for a developer. One of those things is AJAX. The built-in Javascript and AJAX helpers, such as form_remote_tag and link_to_remote, make developing AJAX apps a breeze. But if there has every been one major bone of contention with these helpers, it's the markup they produce. Developers have been well aware of the need to separate content from presentation with CSS for a while now - less prevalent is the recognition of the benefits in separating behaviour from content.
Update 21/08/2006: The latest version of this plugin is 0.3 - please see this post and the official UJS website for more information.
Rails makes a lot of things easier for a developer. One of those things is AJAX. The built-in Javascript and AJAX helpers, such as form_remote_tag and link_to_remote, make developing AJAX apps a breeze. But if there has every been one major bone of contention with these helpers, it's the markup they produce. Developers have been well aware of the need to separate content from presentation with CSS for a while now - less prevalent is the recognition of the benefits in separating behaviour from content.
I'm not going to cover the ins and outs of why you should separate behaviour from content here, but or those unfamiliar with the reasons and benefits, Peter-Paul Koch of QuirksMode published a great article in 2004 highlighting the benefits of separating behaviour from content. More recently, Dan Webb from Vivabit published an article about the problem with Rails AJAX helpers.
As Dan points out in his article, the subject has been mentioned on several occasions on the RubyOnRails mailing list but has often been shot down for a multitude of reasons ranging from "its not worth it" to "it would be too awkward". Invitations to submit patches were made but nothing came about.
So I'd like to present to you something I've been working on here at Agile Evolved - Unobtrusive Javascript for Rails. The plugin makes use of a Javascript library called event:Selectors by Justin Palmer at EncyteMedia. Similar to Ben Nolan's behaviour.js library but making full use of Prototype, it allows you to use CSS selectors to attach Javascript events to your page. This plugin allows you to make use of the event:Selectors library, but in Ruby, directly from your controller or view and have the resulting behaviour rules dynamically generated at runtime in an external javascript file. Let me demonstrate:
First of all, you'll need to grab the plugin from the Subversion repository. Please note - the plugin is in its early stages at the moment and is likely to be updated quite a bit over the coming weeks - I do not recommend using this in a production site just yet. In the meantime, if you want to have a play I recommend using svn:externals to make sure you keep your copy of the plugin up-to-date.
$ ./script/plugin install -x http://source.ujs4rails.com/current/unobtrusive_javascript
Next, you'll need to load in the Prototype javascript and the Unobtrusive Javascript scripts. Somewhere between your layout's head tags, add the following:
<%= javascript_include_tag :defaults %>
<%= unobtrusive_javascript_files %>
You can now attach events to elements in your page from either your controller or your view, using the register_js_behaviour() function. The function takes two parameters; the first is the CSS selector and event (for more details see the event:Selectors documentation) to attach your behaviour to and the second is a string of Javascript that you want to execute. Here's a small example:
<% register_js_behaviour "#my_funky_link:click", "alert('Hello World')" %>
Of course, writing out large strings of javascript could become cumbersome. The first solution is use the built-in Rails helpers to generate your Javascript strings. For instance, if we want to highlight a div when we hover over it with the mouse, you could do the following:
<% register_js_behaviour "#my_funky_div:mouseover",
visual_effect(:highlight, "my_other_div", :duration => 0.5) %>
Now, if you load up a page with one of the above calls and View Source, you might expect to see a whole load of generated Javascript. Except you won't - thats because the unobtrusive_javascript plugin makes use of a special controller and view to dynamically generate your behaviour rules at runtime, which are then linked to using a normal script tag. That means you can attach as many behaviours to your page as you like, from anywhere in your view or controller, without clogging up your rendered HTML with Javascript.
Next, there is the issue of graceful degradation. The two primary candidates for graceful degradation are the link_to_remote and form_remote_tag helpers - links and forms both have natural fallbacks - the HREF and ACTION respectively, and by using some unobtrusive Javascript we can make sure those fallbacks are used when somebody has Javascript disabled.
This initial release contains an updated version of the form_remote_tag helper - by default it does exactly the same thing as the built-in Rails helper but making it use unobtrusive Javascript is a case of one small addition to your code:
<%= form_remote_tag :url => { :action => 'foo' }, :unobtrusive => true %
Because the event:Selector library depends on an HTML ID to attach an event, your forms will need an ID but worry not - another modification to the form_remote_tag helper will mean that if you do not specify your own HTML ID, one will be automatically generated for you.
Finally, some of you may be aware that Dan Webb, who I mentioned earlier in this article, has been working on his own unobtrusive javascript plugin. I have been in touch with Dan and he preferred the simpler syntax that my plugin uses. That said, he's plugin contains some very cool stuff and we've agreed to merge the best of both of our plugins together over the coming weeks, including the ability to supply the register_\javascript_behaviour() function with a block which will let you write the Javascript functionality you want to attach using Dan's RJS-style JavascriptProxy classes.
Please do download the plugin and have a play around. You can report any bugs on the Agile Evolved Open Source Software website and any opinions and ideas are welcomed.
Posted over 2 years back at Cody Fauser
Are you tired of debugging your RJS calls ? If you answered "yes" then it is your lucky day. Kevin Clark has created a new plugin for Rails called ARTS: Another RJS Testing System.
ARTS allows you to write assertions for your RJS calls in your functional test cases. Kevin has also created an excellent tutorial Test Driven RJS with ARTS.
What are you waiting for? Get over to Kevin's site and get started.
Posted over 3 years back at Ruby on Rails Podcast
Rick Olson explains the Rails plugin system, RJS templates, and Rails-weenie.
1 ... 5 6 7 8