Wednesday, November 3, 2010

Rails3 ajax tricks with javascript erb templates

This guide is about rails3 ajax and javascript tricks. (Unobtrusive javascript, if it sounds better.) first, you must decide what do you want to do and how. Sometimes there are better solutions than we should think first. To call an ajax method, you must take the code in view header (or layout): <%= javascript_include_tag :defaults %>

Using js.erb templates.

If you decided to use some ajax calls, using server-side programming, recommend to create a new ajax controller (but it's not necessary). In command line:

rails g controller ajax

Then, if we make an ajax call, we will define procession in app/controllers/ajax_controller.rb. Next, we define the javascript output in ajax_controller.rb with respond_to, and define our methods (now: empty_cart):

class AjaxController < ApplicationController
respond_to :js

  def empty_cart
    Cart.where(:session_id => request.session_options[:id]).destroy_all
  end

end

Then we can create empty_cart.js.erb in app/views/ajax/ directory. In this file we define javascript what sends to page. This is javascrip template, but we can use ruby expressions in it. Of course, we can use prototype methods and script.aculo.us effects. The partial cart.html has been defined in layouts and if cart is empty, it will show one row: "the cart is empty". The element will be replaced by partial:


$("cart_div").update("<%= escape_javascript(render("layouts/partials/cart.html.erb")) %>");
new Effect.Highlight("cart_div", {duration: 1.5, startcolor: "#909090"});

After then, we can create this ajax link in our view, now: layouts/partials/cart.html.erb. We make it with a button defined in css.

<% if Cart.where(:session_id => request.session_options[:id]).empty? %>

Cart is empty.

<% else %>

..put here your cart html code..

<%=raw form_tag url_for(:action => "empty_cart",
  :controller => :ajax ),
{ :method => :post,
  :remote => true,
  'data-confirm'  => "Are you sure?" } %>
<%=raw submit_tag "Empty Cart",
  :id  => "empty_cart_submit",
  :class  => "empty_cart_button"
   %>
</form>

<% end %>

Don't forget to set link html options :remote => true, to call an ajax method.

The right syntax of the link_to function with i18n is:


<%= link_to [t :empty_cart, :scope  => [:application]],
  url_for(:controller => 'ajax',
          :action  => "empty_cart"),
  {:method => :post,
   :remote  => true,
   :confirm => "Are you sure?"}
%>



Using effects

You can use any effects in javascript erb template like in pure javascript. For example a toggle effect:


new Effect.toggle("some_div_<%= id %>", "slide", {duration: 0.2});

Don't forget to put ruby expressions between <%= %> tags like in html view.


Forgetting observe_form and observe_field

In rails 2 there were these functions observe_form and observe_field, now, in rails3 are missing. You can use Prototype Legacy Helper plugin, or, I recommend to create your own javascript code, what is faster than calling an ajax method. Put your javascript code into your form view (app/views/controller_name/form.html.erb):


<%=raw javascript_tag "
document.observe("dom:loaded", function()
{
new Form.Observer("<%= @jsvalidator.form.id %>", 0.3, function(form, value)
 {
// now we got form values in one single url-formed linein value javascript variable
// so we create an array of key and value pairs in textArray
  var textArray=unescape(value).split("&");
  var i=0;
  for(i=0; i<(textArray.length); i++)
  {
// then, you can see all elements within a for loop  
// now textArray[i] is one line like: "form_element_name=form_element_value"
 
   value_line_with_equal_sign=textArray[i];
   value_name="";
   value_value="";
   value_name=value_line_with_equal_sign.split("=",2)[0];
   value_value=value_line_with_equal_sign.split("=",2)[1];
// now you can check all of form values in this loop, value_name is the form element name
// value_value is form element's value
// probably you must give form elements to Form class
 
}
 
}
}" %>

@jsvalidator object has been created in form's controller with form datas. See more information: How to generate inline javascript

If you have problems with Internet Explorer, IE see and download newer versions of rails.js and prototype.js. Details here.


If you have problems with sessions or current_user quits within ajax rendering, put this code to public/javascripts/application.js. /thanks to this answer/

document.observe("dom:loaded", function() {
 Ajax.Responders.register({
  onCreate: function(request) {
   var csrf_meta_tag = $$('meta[name=csrf-token]')[0];
   if (csrf_meta_tag) {
    var header = 'X-CSRF-Token',
      token = csrf_meta_tag.readAttribute('content');

    if (!request.options.requestHeaders) {
     request.options.requestHeaders = {};
    }
    request.options.requestHeaders[header] = token;
   }
  }
 });
});


That's all!

5 comments:

  1. After many, many hours spent over weeks trying to fix the simplest of code that worked in 2.3.4, I cannot see why the core team wanted to break Rails so deeply and abruptly.

    It only makes sense if
    1) you're some kind of theocratic nut who came upon a new belief and wants to thrust it on everyone...yesterday, or
    2) They deliberately want to make it harder for the new and the less-blessed developers.

    ReplyDelete
  2. hi Ed,

    I agree, but we must follow rails developers. Using html attributes is simpler and much more transparent like onclick functions.

    Attila

    ReplyDelete
  3. how to use the observe_field in rails 3 please reply quick boss

    ReplyDelete
    Replies
    1. observe_field has been deprecated, must write your own javascript for this and use prototype and jquery functions. In prototype there is Form.Observer.

      Maybe this article could help: http://ruby-on-rails-tutorials.blogspot.hu/2011/08/how-to-create-scriptaculous-ajax.html

      In Jquery there is not observe, you must use onkey functions and call ajax inside.

      Attila

      Delete
  4. rails destroy controller ajax
    or:
    rails d controller ajax

    ReplyDelete

Note: Only a member of this blog may post a comment.