Thursday, August 6, 2009

Rails JQuery Ajax Tabs

How to implement horizontal tabs menu using JQuery UI tabs, fetching content through Ajax, from a Rails server.

The example code can be fetched from here:
http://github.com/tsenying/rails_jquery_ajax_tabs

Steps:
1. Create the Rails app, use the -d option to set the database adaptor.
rails -d postgresql rails_jquery_ajax_tabs

2. Setup controller and views to be tabbed.
script/generate controller about tab1 tab2 tab3 tab4

3. Download and add jquery.js, jquery-ui.js to the rails app directory public/javascripts and jquery-ui stylesheet to public/stylesheets,
from http://jqueryui.com/download

4. Create layout with jquery javascript files and stylesheet.
- add jquery stylesheet to layout

<%= stylesheet_link_tag "jquery-ui-1.7.2.custom", :cache => true %>

- add jquery javascript files to layout (both core and ui) and also application.js

<%= javascript_include_tag "jquery-1.3.2.min", "jquery-ui-1.7.2.custom.min", "application" %>


5. setup up tabs markup in layout,
match the link 'title' attribute and the content container's id, otherwise jquery dynamically creates the container.
Using links pointing to the server instead of named anchors inside the same document causes jquery ui tabs to ajaxify the tabs behavior.

<div id="tabs">
<ul>
<li><%= link_to content_tag(:span, "Tab1"),
{:controller => "about", :action => "tab1"},
{:title => "main_content"} %></li>
<li><%= link_to content_tag(:span, "Tab2"),
{:controller => "about", :action => "tab2"},
{:title => "main_content"} %></li>
<li><%= link_to content_tag(:span, "Tab3"),
{:controller => "about", :action => "tab3"},
{:title => "main_content"} %></li>
</ul>
</div>
<div id="main_content">
<%= yield %>
</div>

6. Create jquery tabs in application.js

$(document).ready(function(){
$("#tabs").tabs();
});

7. create tab content views in views/about/ e.g. tab1.html.erb :

<h1>About#tab1</h1>
<p>Find me in app/views/about/tab1.html.erb</p>

8. turn off layout in controller if request.xhr?

layout :determined_by_request

protected
def determined_by_request
if request.xhr?
return false
else
'application'
end
end

9. set up routes in config/routes.rb:

map.root :controller => "about", :action => "tab1"


References:


http://docs.jquery.com/UI/Tabs
http://jqueryui.com/demos/tabs/
Similar implementation using prototype: http://www.eduvoyage.com/2008/9/26/ajax-tabs-and-rails and http://www.eduvoyage.com/2009/3/14/ajax-tabs-and-rails-2

Tuesday, July 28, 2009

JQuery Ajax on Rails

Using JQuery instead of Prototype and RJS for Ajax with Rails


Basically notes taken from Ryan Bates' Railscast http://media.railscasts.com/videos/136_jquery.mov,
annotated with bits from the JQuery documentation http://docs.jquery.com

JQuery encourages unobstrusive javascript.
Using JQuery means not using Rails related helper methods (form_remote_for, etc) and RJS.

The JRails plugin (JQuery on Rails)
http://ennerchi.com/projects/jrails
is a drop-in jQuery replacement for Prototype/script.aculo.us on Rails.
JRails provides all of the same default Rails helpers for javascript functionality using JQuery.
The issue is that the JRails approach is not in line with the JQuery philosophy of unobstrusive javascript, because the Rails helper methods are obstrusive since they inject javascript into the generated html.

Example: Add review via form on Product page.

The product page template show.html.erb looks like:

<% title @product.name %>
<p>Price: <%= number_to_currency @product.price %></p>
<h3 id="reviews_count">
<%= pluralize(@product.reviews.size, "Review") %>
</h3>

<div id="reviews">
<%= render :partial => @product.reviews %>
</div>

<h3>Add a Review</h3>
<% form_for Review.new(:product => @product) do |f| %>
<%= f.hidden_field :product_id %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<p>
<%= f.label :content, "Review" %><br />
<%= f.text_area :content, :rows => 8 %>
</p>
<p><%= f.submit "Add Review" %></p>
<% end %>


The goal is to have 4 actions to be done via Ajax


1. add flash message
2. update review count
3. append review to reviews section
4. clear the form

Add JQuery javascript file include to head section of layout

If the jquery javascript library file is in public/javascripts/jquery.js,
then add the following to the head section of layout.

<%= javascript_include_tag 'jquery', 'application' %>

The jquery file needs to be included before the file where the jquery library functions are to be invoked, in this case "application.js".

Unobstrusive Javascript

The approach is to use standard html and add behavior dynamically through javascript.
Can use Rails helpers such as form_for that generates standard html but don't use remote_form_for and other Ajax helpers that generate javascript as well.
Add JQuery code to the included application.js file.

The above view template will generate a form tag like:

<form id="new_review" class="new_review" method="post" action="/reviews">


Take over the submit function on the form with

$("#new_review").submit(function() {
$.post($(this).attr("action"),
$(this).serialize(), null, "script");
return false; // so form doesn't get submitted normally
})


$.post is a simplified wrapper function for $.ajax and takes 4 parameters:
url - the URL of page to load, $(this).attr("action") is the form's "action" attribute, in this case: action="/reviews"
data - $(this).serialize() serializes form input elements into a string of data
callback - function to execute on success, in this case set to null because it uses the type option to handle returned javascript.
type - "script" option means that returned data will be processed as javascript and mimics behavior of Rails RJS.

Setting request type

JQuery needs to tell Rails that the request is an Ajax request not an HTML request (the default).
There are 2 ways to do this:
1. add .js extension to url, e.g. $(this).attr("action") + '.js'
2. change the request header attribute "Accept" to "text/javascript", using the jQuery.ajaxSetup method to setup global settings for AJAX requests.
So in application.js, before $(document).ready add:

jQuery.ajaxSetup({
'beforeSend': function(xhr) {
xhr.setRequestHeader("Accept", "text/javascript") }
})


Rails Server Side Handling


Controller


Respond differently depending on if it's normal html request or Ajax request.

class ReviewsController < ApplicationController
def create
@review = Review.create!(params[:review])
flash[:notice] = "Thank you for reviewing this product"
respond_to do |format|
format.html { redirect_to @review.product }
format.js
end
end
end


View


Use .erb template instead of .rjs because RJS is used with Prototype (unless using JRails plugin).
So create the template create.js.erb containing javascript to be executed on browser side.

1. add flash notice

$("#new_review").before('<div id="flash_notice">
<%= escape_javascript(flash.delete(:notice)) %></div>');

- escape_javascript escapes single and double quotes and carrier returns to prevent javascript getting messed up.
- flash.delete deletes the :notice key/value pair and returns the value for the erb tag. The :notice key/value pair needs to be deleted so that it does not carry over to the next request.

2. update reviews count


$("#reviews_count").html("<%= pluralize(@review.product.reviews.count, 'Review') %>");

- .html sets the html contents of the matched elements

3. append new review to reviews

$("#reviews").append("<%= escape_javascript(render(:partial => @review)) %>);

- .append appends content to matched elements.

4. reset the form
$("#new_review")[0].reset();
- reset: jQuery returns a collection of dom elements. To operate on the DOM element itself, it needs to be extracted from the collection.
jQuery('#myForm').get(0).reset();

or a little shorter

jQuery('#myForm')[0].reset();

Template code in entirety:

$("#new_review").before('<div id="flash_notice"><%= escape_javascript(flash.delete(:notice)) %></div>');
$("#reviews_count").html("<%= pluralize(@review.product.reviews.count, 'Review') %>");
$("#reviews").append("<%= escape_javascript(render(:partial => @review)) %>");
$("#new_review")[0].reset();


Extending JQuery and Adding Functions


Can create functions to be invoked from the collection of matching elements.
For example, the code that takes over the form submit function can be refactored as a function:

jQuery.fn.submitWithAjax = function() {
this.submit(function() {
$.post($(this).attr("action"), $(this).serialize(),
null, "script");
return false;
})
}

To be invoked like:
$("#new_review).submitWithAjax();

Rails Authenticity token with JQuery

Rails 2 protects against CSRF attacks.
Rails form helpers generates a hidden input field for the authenticity_token.
If the rails helpers are not used for non-get requests, then the authenticity_token has to be added as a parameter to the request.
See: http://henrik.nyh.se/2008/05/rails-authenticity-token-with-jquery

References


http://www.loudthinking.com/posts/32-myth-3-rails-forces-you-to-use-prototype outlines basically the same approach as the above.

http://codetunes.com/2008/12/08/rails-ajax-and-jquery/ describes a general approach for adding ajax behavior to links and posts. Be sure to read the comments, including the comment on no longer needing the livequery plugin because of the new live event binding in jquery 1.3
http://docs.jquery.com/Events/live

Thursday, July 16, 2009

Session Cache is not configured - Phusion Passenger

Got this error in the Apache error log while trying to setup Ruby on Rails app with Phusion Passenger on Mac OS X:

[Wed Jul 15 14:26:07 2009] [notice] caught SIGTERM, shutting down
[Wed Jul 15 14:26:07 2009] [warn] Init: Session Cache is not configured [hint: SSLSessionCache]


Apache errors seem obtusely opaque.
Found the fix in this blog entry:
http://benr75.com/2008/04/12/setup-mod_rails-phusion-mac-os-x-leopard
The RailsEnv entry needs to be set correctly in the Apache httpd.conf VirtualHost element, like so:


<VirtualHost *>
ServerName app.test
DocumentRoot /Users/benr/Rails/app/public
RailsEnv development
</VirtualHost>


ServerName can be 'localhost' if you are running locally for development/testing.

Ruby Proc vs Lambda Comparision

Here's a more comprehensible explanation than what is in the Pickaxe book:
http://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Method_Calls#Understanding_blocks.2C_Procs_and_methods

In short, the difference between lambda and Proc is in whether the number of parameters is checked and the behavior of "return"
The resulting Proc object created by lambda checks the number of arguments and throws an ArgumentError. A Proc object created by Proc.new does not check the number of arguments.

The example from the link is:

lamb = lambda {|x, y| puts x + y}
pnew = Proc.new {|x, y| puts x + y}

# works fine, printing 6
pnew.call(2, 4, 11)

# throws an ArgumentError
lamb.call(2, 4, 11)


Second, a return from Proc.new returns from it's enclosing method,
while a return from a lambda Proc acts more "conventionally", returning to it's caller.
Here's an interesting quote "Blocks, as I see them, are unborn Procs. Blocks are the larval, Procs are the insects."

And an explanation of closure, a closely related concept:
http://en.wikipedia.org/wiki/Closure_(computer_science)
It has a ruby example of the difference of "return" behavior between lambda and Proc.

Monday, March 9, 2009

Playing with Cloud Computing

Been playing with Amazon AWS, specifically EC2.
Used Paul Dowman's ec2onrails gem to deploy a rails app.
" It’s a Ruby on Rails virtual appliance."

Used Instiki ported to Rails 2.0 by Jacques Distler.
Toy app now running on http://www.omnivorously.com