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!

Tuesday, October 26, 2010

How to load i18N locale files in rails3 with our new gem

That is very simple. We'll create a gem with our own locale files, first, we create the locale files in our gem's lib/locales directory (en.yml and hu.yml for example). There would be our gem's .rb file in lib directory (example.rb). Don't forget to specify these files in gemspec. Now we can create a new method: translate_string in our 'lib/example.rb'.


class Example
require 'rubygems'
require 'active_support'
require 'i18n'


path=File.dirname(__FILE__)
I18n.load_path += Dir[ File.join(path, 'locales', '*.{rb,yml}') ]

def initialize
end

def translate_string(var)
  return I18n.t(var, :scope=>[:example, :vars])
end

end

Then, we can create our locale files 'lib/locales/en.yml' like this:


en:
  example:
    vars:
      title: example

And our lib/locales/hu.yml:


hu:
  example:
    vars:
      title: példa

Take care of spaces instead of tabs. After we build our gem and insert to Gemfile (what is in our Rails3 application) we can call our new class in our controller. Don't forget to restart your application before.



e=Example.new
e.translate_string("title")


This gives "example". That's all!

Monday, October 25, 2010

Create new class in Rails3 with hash parameters

This guide is about how to create new class with hash parameters, without ActiveRecord. If you don't want to use database to define a new object, this guide is for you. First, we create a new contact.rb file in "app/models" directory


class Contact
 include ActiveModel::Serialization
 attr_accessor :name, :company_name, :email, :phone, :comment

 def initialize(attributes = {})
  @name=attributes[:name]
  @company_name=attributes[:company_name]
  @email=attributes[:email]
  @phone=attributes[:phone]
  @comment=attributes[:comment]
 end
 
# persisted is important not to get "undefined method `to_key' for" error
 def persisted?
  false
 end 
 
end

And then you can use this model to create a new form, like ActiveRecord in controllers:

@contact=Contact.new

or

@contact=Contact.new(params[:contact])

Now we can use this object to send email to sales or save it to another object's text column with json. For example if there is a json_contact text column in User active_record object, users.rb:


Class User < ActiveRecord::Base
before_save :before_save_function

def before_save_function
  unless self.json_contact.is_a?(String) then
    # we need this condition because if we updating, must know if it is a String (json modelled class) or a Contact model class
    self.json_contact=self.json_contact.to_json
  end
  true
 end

 def contact
  unless self.json_contact.nil? then
   contact_hash = ActiveSupport::JSON.decode(self.json_contact).symbolize_keys.clone
   return Contact.new(contact_hash)
  else
   return nil
  end
 end

end

Then you can give a Contact object to User.json_contact like:

@user=... # your turn (find, or create) @contact=Contact.new(params[:contact]) @user.json_contact=@contact @user.save

Don't forget to restart your application after modifying anything in your models. Now you can get original User.contact as an object, and you can use it like Contact model:

contact_name=@user.contact.name

That's all

Wednesday, October 20, 2010

How to generate inline javascript by gem for form validation etc...

This tutorial is about generating an inline javascript by gem created by us. This can be usable for form validation or another javascript application. This tutorial is for Ruby on Rails 3. I don't write full code, because it could be too deep, I just try to show to you how to give inline javascript code by  js template to a single view page.

First, we need to create our own gem file structure:

- jsvalidator
- lib
- jsvalidator.js.erb
- jsvalidator.rb
- jsvalidator.gemspec


Then we edit the jsvalidator.gemspec file which is in jsvalidator directory have been created.

Gem::Specification.new do |spec|
   spec.name = 'jsvalidator'
   spec.version ='0.0.1'
   spec.files = ["lib/jsvalidator.js.erb", "lib/jsvalidator.rb"]
   spec.summary = "Javascript form validator for rails3 with javascript."
   spec.author = 'Programmer'
   spec.email = 'programmer@yourdomain.com'
   spec.homepage = 'http://www.yourdomain.com'
   spec.has_rdoc = false
 end 

We create our class structure in jsvalidator.rb file. You can install erubis gem with "gem install erubis" command.

require 'rubygems'
require 'active_support'
require 'erubis'

class Jsvalidator
# define a form object
attr_accessor :form

def form
 return @form
end


# now we don't initialize anything, but you can set initial values in this block
def initialize
end

# we create a function to define our form, what is now an another object class defined later
def define_form(attributes={})
 f=Form.new(attributes)
 @form=f if f
end

# jsvalidator_helper is a function, we will call using Jsvalidator class
 def jsvalidator_helper
# __FILE__ is the path where the this rb file is 
  path=File.dirname(__FILE__)
  template_path=File.join(path, 'jsvalidator.js.erb')
# we can give objects to javascript template like @jsvalidator, what is self in this case,
# beacuse now we work with Jsvalidator class only
  vars={ '@jsvalidator' => self }
# so read the javascript template
  template= File.read(template_path)
# Erubis can execute inline ruby expressions with objects (formed like: <%= @jsvalidator.any %>)
  javascript=Erubis::Eruby.new(template).result(vars)
# then we create return string
  javascript=""
  return javascript
 end

#end of Jsvalidator class
end

# then we must define our Form class
class Form < Jsvalidator
# You can create any object in it, now we use form.id only
attr_accessor :id
def initialize(attributes={})
  @id=attributes[:id]
end
end
In this file we should create our model, or data structure, we will use at jsvalidator.html.erb in another function of course. Then, we edit the jsvalidator.js.erb file. In this file we define our javascript code, but remember, this is a template, so you can use ruby expressions like: <%= @jsvalidator.anything %>, but first you must define objects in Form class. Now we use @form.id only.
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

}

}
} 

After then, you can build your gem file at jsvalidator directory at command line:

gem build jsvalidator.gemspec

and install:

sudo gem install jsvalidator

Then edit your Gemfile, what is located at your rails3 application's directory, and insert this line with your real path:
gem 'jsvalidator', :path = "/home/user/gems/jsvalidator", :require = "jsvalidator"
Then type command line:

bundler update

If you don't want to create a working gem because of developing, you can just simply add to your application_controller.rb file this line with your path:
require '/home/user/gems/jsvalidator/lib/jsvalidator.rb'

Now at your view file, you just simply call helper like this:
<% @jsvalidator=Jsvalidator.new %>
<% @jsvalidator.define_form( :id => "your_form_id" ) %>
<%= raw @jsvalidator.jsvalidator_helper %>
That's all.