Showing posts with label ajax. Show all posts
Showing posts with label ajax. Show all posts

Wednesday, February 29, 2012

Rails 3 ajax Autocomplete with jQuery and jQuery UI theme

So we want a form element with autocomplete. First thing to do is installing jquery-rails and jquery-ui-themes gems and require in Gemfile.

Then require one of the jQuery UI theme into app/assets/stylesheets/application.css
 *= require_self
 *= require_tree .
 *= require_style
 *= require jquery-ui/ui-lightness
*/

 app/assets/javascripts/application.js

//= require jquery
//= require jquery_ujs
//= require jquery-ui
//= require_tree .

Our controller responds json object in apps/controllers/accounts_controller.rb
    def search_account
        @accounts=Account.where("LOWER(name) LIKE ?", "%#{params[:name].downcase}%").limit(params[:maxRows]).order(:name)
        
        respond_to do |format|
            format.json {render :json => @accounts}
        end
    end


I've created a helper for input element app/helpers/application_helper.rb
(Take care of heredoc strings.)

    def autocomplete_input(attributes={})
        #*need* object: this form's object : "person"
        #*need* instance: searcheable object : 'account' 
        # instance_key: primary key of this instance : 'account_id'
        # value: original or default value for instance : 'Yahoo Corp.'
        # value_key: original or default value for instance_key: 252
        #*need* ajax_url: remote url for ajax call without format: url_for(:controller=> :accounts, :action=> :search, :id=>'all')
        # ajax_query_additional_params (Array): ["maxRows: 7", "anything :20"]
        #*need* ajax_query_searchable_param Object's string (String): "name" (@person.account.name)
        #*need* ajax_query_searchable_param_key Object's foreign key (String): "id" (@person.account.id)
        # min_length minimal chars to start searching: 2    
        
        if attributes.nil? then return false end
        if attributes.empty? then return false end
        object=attributes[:object]
        instance=attributes[:instance]
        if object.nil? or instance.nil? then return false end    
        object=object.to_s.downcase
        instance=instance.to_s.downcase
        instance_key=attributes[:instance_key] || instance + "_id"
        value=attributes[:value] || ""
        value_key=attributes[:value_key] || ""
        value_key_html=""
        unless value_key.to_s.empty? then
            value_key_html=" value=\""+value_key.to_s+"\"" 
        end
        ajax_url=attributes[:ajax_url]
        ajax_query_additional_params=attributes[:ajax_query_additional_params] || ""
        ajax_query_searchable_param=attributes[:ajax_query_searchable_param] || "name"
        ajax_query_searchable_param_key=attributes[:ajax_query_searchable_param_key] || "id"
        min_length=attributes[:min_length] || 2
        
        ajax_query_additional_params_formatted=""
        unless ajax_query_additional_params.nil? then
            case ajax_query_additional_params
                when Array then
                    i=0
                    ajax_query_additional_params.each do |aqap|
                        if i==0 then
                            ajax_query_additional_params_formatted=ajax_query_additional_params_formatted + aqap.to_s
                        else
                            ajax_query_additional_params_formatted=ajax_query_additional_params_formatted + ",\n" + aqap.to_s
                        end
                        i=i.next
                    end
                when String then
                    ajax_query_additional_params_formatted=ajax_query_additional_params
                when Hash then
                    i=0
                    ajax_query_additional_params.each do |aqap_key,aqap_value|
                        if i==0 then
                            ajax_query_additional_params_formatted=ajax_query_additional_params_formatted + aqap_key.to_s + ": " + aqap_value.to_s
                        else
                            ajax_query_additional_params_formatted=ajax_query_additional_params_formatted + ",\n" + aqap_key.to_s + ": " + aqap_value.to_s
                        end
                        i=i.next
                    end
            end
        end
        jquery_request_data_params="data: {\n"
        unless ajax_query_additional_params_formatted.empty? then
            jquery_request_data_params=jquery_request_data_params + ajax_query_additional_params_formatted + ",\n"
        end
        jquery_request_data_params=jquery_request_data_params + "#{ajax_query_searchable_param}: request.term\n},"
        search_field_id="search_#{object}_#{instance}"
        value_div_id="#{object}_#{instance}_log"
        hidden_field_id="#{object}_#{instance_key}"
        hidden_field_name="#{object}[#{instance_key}]"
        function_log_name="log_#{object}_#{instance}"
        
        html_text = <<HTML1
<table><tbody><tr>
    <td><input id="#{search_field_id}" class="ui-autocomplete-input"/></td>
    <td><div id="#{value_div_id}" class="ui-widget-content">#{value}</div></td>
</tr></tbody></table>
<input type="hidden" id="#{hidden_field_id}" name="#{hidden_field_name}" #{value_key_html} >

HTML1
        
         js_text = <<JS1
        
$(function() {

    function #{function_log_name}( label, id ) {
        $( "##{value_div_id}" ).html(label);
        $( "##{hidden_field_id}").val(id);
    }

    $( "##{search_field_id}" ).autocomplete({
        source: function( request, response ) {
            $.ajax({
                url: "#{ajax_url}.json",
                dataType: "json",
                #{jquery_request_data_params}
                success: function( data ) {
                    response( $.map( data, function( item ) {
                        return {
                            label: item.#{ajax_query_searchable_param},
                            value: item.#{ajax_query_searchable_param},
                            id: item.#{ajax_query_searchable_param_key}
                        }
                    }));
                }
            });
        },
        minLength: #{min_length},
        select: function( event, ui ) {
            if (ui.item) {
                #{function_log_name}( ui.item.value, ui.item.id );
            } else {
                #{function_log_name}( this.value, this.value );
            }
        },
        open: function() {
            $( this ).removeClass( "ui-corner-all" ).addClass( "ui-corner-top" );
        },
        close: function() {
            $( this ).removeClass( "ui-corner-top" ).addClass( "ui-corner-all" );
        }
    });
});
        
JS1
    concat(raw(javascript_tag(js_text)))
    concat(raw(html_text))
    end

Finally insert autocomplete element in _form.htm.erb

<div class="ui-widget">
<label for="search_account">Account:</label><br />
<% autocomplete_input(:object    => "person",
            :instance            => "account",
            :instance_key        => "account_id",
            :value               => (@person.account.name unless @person.account.nil?),
            :value_key           => @person.account_id,
            :ajax_url            => url_for(:controller => 'accounts',
                                            :action     => 'search_account',
                                            :id         => 'all'),
            :ajax_query_additional_params    => {:maxRows => 7},
            :ajax_query_searchable_param     => "name",
            :ajax_query_searchable_param_key => "id",
            :min_length          => 2
)%>
</div>
</div>

Works with Rails 3.2 and jQuery JavaScript Library v1.7.1. Good luck! Questions?

Tuesday, August 9, 2011

How to create scriptaculous ajax inplace editor fields in Rails3 with validation

First, we should do our html view in app/views/ips/index.html:

<div id="ips_table_div">
<%= render "ips_table" %>
</div>

Then, we create the table and the javascript code in our partial view (app/views/ips/_ips_table.html.erb).

<table cellpadding="10"><tbody>

<% unless @ips.nil? %>

		<tr>

			<td id="ip_new_ip_td_div">
				<%= form_tag url_for(:controller => :ips,
							:action	=> :create_ip),
						:remote => true,
						:id 	=> "new_ip" %>
					
						<%= text_field_tag "ip" %>
						<%= submit_tag t(:add_button, :scope => [:application]), :id => "new_ip_submit" %>
				</form>
			</td>

			<td>
			</td>

		</tr>

	<% @ips.each do |ip| %>
		<tr>

			<td id="ip_ip_td_div_<%=ip.id %>">
				<div id="ip_ip_text_div_<%=ip.id %>"><%= ip.ip %></div>
			</td>

			<td>
				<%= link_to t(:delete_link, :scope => [:application]),
 								url_for(:controller => :ips,
									:action	=> :delete_ip,
									:id 	=> ip.id),
								:remote => true,
								:confirm => t(:ip_delete_confirm, :scope => [:application])%>
			</td>

		</tr>
	<% end %>

<% end %>

</tbody></table>

<script type="text/javascript">
var ip_validate_background_color_error = "#fff999";
var ip_validate_border_color_error = "#f80000";
var ip_validate_background_color = "#ffffff";
var ip_validate_border_color = "#999999";

var ipModifyFormOptions= $H({
		cancelText:'<%=t :cancel_link, :scope => [:application] %>',
		okText: 'ok',
		savingText: '<%=t :saving_text, :scope => [:application] %>',
		loadingText: '<%=t :loading_text, :scope => [:application] %>',
		clickToEditText: '<%=t :click_to_edit_text, :scope => [:application] %>',
		
});

<% unless @ips.nil? %>

Form.findFirstElement('new_ip').style.borderColor = ip_validate_border_color;
Form.findFirstElement('new_ip').style.backgroundColor = ip_validate_background_color;
Form.findFirstElement('new_ip').setAttribute("onkeypress","return input_filter_exact(event, 'ip')");
$('new_ip_submit').disabled=true;
new Form.Observer('new_ip', 0.3, function(form, value) {
		if(validateIpAddress(Form.findFirstElement('new_ip').value, false) == true) {
				Form.findFirstElement('new_ip').style.borderColor = ip_validate_border_color;
				Form.findFirstElement('new_ip').style.backgroundColor = ip_validate_background_color;
				$('new_ip_submit').disabled=false;
		}	else {
				Form.findFirstElement('new_ip').style.borderColor = ip_validate_border_color_error;
				Form.findFirstElement('new_ip').style.backgroundColor = ip_validate_background_color_error;
				$('new_ip_submit').disabled=true;
		}
});


	<% @ips.each do |ip| %>

new Ajax.InPlaceEditor('ip_ip_text_div_<%=ip.id %>', '<%= url_for :controller => :ips, :action => :modify_ip, :id => ip.id	%>', ipModifyFormOptions.merge({
	formId: 'ip_ip_form_<%=ip.id %>',
	onEnterEditMode: function(form, value) {
			setTimeout(function() {
					Form.findFirstElement('ip_ip_form_<%=ip.id %>').style.borderColor = ip_validate_border_color;
					Form.findFirstElement('ip_ip_form_<%=ip.id %>').style.backgroundColor = ip_validate_background_color;
					Form.findFirstElement('ip_ip_form_<%=ip.id %>').setAttribute("onkeypress","return input_filter_exact(event, 'ip')");
					new Form.Observer('ip_ip_form_<%=ip.id %>', 0.3, function(form, value) {
						if(validateIpAddress(Form.findFirstElement('ip_ip_form_<%=ip.id %>').value, false) == true) {
							Form.findFirstElement('ip_ip_form_<%=ip.id %>').style.borderColor = ip_validate_border_color;
							Form.findFirstElement('ip_ip_form_<%=ip.id %>').style.backgroundColor = ip_validate_background_color;
							$('ip_ip_form_<%=ip.id %>').elements[1].disabled=false;
						}	else {
							Form.findFirstElement('ip_ip_form_<%=ip.id %>').style.borderColor = ip_validate_border_color_error;
							Form.findFirstElement('ip_ip_form_<%=ip.id %>').style.backgroundColor = ip_validate_background_color_error;
							$('ip_ip_form_<%=ip.id %>').elements[1].disabled=true;
						}
				});
			}, 1000);
	}
}).toObject());

	<% end %>

<% end %>
</script>

Then we should make the controller (app/controllers/ips_controller.rb):

respond_to :js, :only => [:delete_ip, :create_ip]
respond_to :html, :except => [:delete_ip, :create_ip]
layout :application, :except => [:delete_ip, :create:ip, :modify_ip]
layout false, :only => [:delete_ip, :create:ip, :modify_ip]

def index
  @ips=Ip.all
  respond_with(@ips)
end

def delete_ip
  Ip.destroy(params[:id])
  # it will returns a javascript code by default, so have to do the
  # app/views/ips/delete_ip.js.erb, what will loads the table completely
 @ips=Ip.all
  respond_with(@ips)
end

def create_ip
  ip=Ip.new(params[:ip]).save
  # it will returns a javascript code, so have to do the
  # app/views/ips/create_ip.js.erb, what will loads the table completely
  @ips=Ip.all
  respond_with(@ips)
end

def modify_ip
  ip=Ip.find(params[:id])
  ip.update_attribute(:ip, params[:value])
  # don't forget some validations here also
  # it will returns a html code, so have to do the
  # app/views/ips/modify_ip.html.erb, what will contains the new text only, or:
  respond_to do |format|
    format.html { render :text => ip.ip }
  end
end

Then we can create returned javascript codes app/views/ips/create_ip.js.erb and delete_ip.js.erb:

  $("ips_table_div").innerHTML='';
  $("ips_table_div").update("<%=escape_javascript(render(:template => "/ips/_ips_table.html.erb", :locals => {:ips => @ips})) %>"); 

Finally we must define javascipt validation functions in public/apllication.js:

function validateIpAddress(value, can_be_empty) {
    if (arguments.length == 1) {
        can_be_empty = false;
    }
    if (can_be_empty == true) {
        if (value == null) {
            return false;
        }
        if (value == "") {
            return true;
        }
    }
    if (value.match(/\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/))
    {
        if (value.length>0 && value.length<256)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    else
    {
        return false; 
    }
}

function input_filter_exact(e, type) {
    var enabled_chars;    
    switch (type)
        {
            case "ip": enabled_chars="0123456789.";
                                    break;
            default: enabled_chars="-/.,_?[]()!abcdefghijklmnopqrstvuwxyzABCDEFGHIJKLMNOPQRSTVUWXYZ0123456789éáőúűóüöíÉÁŐÚÓÜÖÍ ";
        }
    var key;
  var keychar;
    if (window.event)
        key = window.event.keyCode;
  else if (e)
        key = e.which;
    else
        return true;
    keychar = String.fromCharCode(key);
    if ((key==null) || (key==0) || (key==8) || (key==9) || (key==13) || (key==27) )
        return true;
    else if (((enabled_chars).indexOf(keychar) > -1))
        return true;
    else
    return false;
} 


That's all. Works with Rails 3.0.9, Prototype 1.7 and script.aculo.us v1.8.3

Thursday, March 3, 2011

How to submit an ajax form in javascript with Rails3 and Prototype

So there is a Rails 3 problem, when you want to submit a form in javascript (without a submit button), it is working on normal way, not ajax. But we can do creating a hidden submit button and then initializing a click event on it what is exactly doing ajax submission. The submit(); is not working anymore because it doesn't call the rails.js functions. It just works when you simple want to create a not ajax post without any confirmation. Let's see an ajax example in view's index.html.erb file:

<%= form_tag "some_url",
 :remote  => true,
 :method  => :post,
 :name  => "some_procedure",
 :id   => "some_procedure" %>
<%= submit_tag 'procedure_submit_button', :id =>"procedure_submit_button" , :style => "display: none" %>

You can add here your html code for form and create some element what is doing the submission inside a table, for a td:

<td onclick="javascript: procedure_init();" style="cursor: pointer;">

put here an image for example

</td>

</form>

<%= javascript_tag <<-RUBY
function procedure_init()
{
 var submit_button="procedure_submit_button";
 var form_name="some_procedure";
 // here you can build the form, or modify form parameters
 $(submit_button).click();
}
RUBY
%>

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. Questions?

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!