AJAX in practice
- Adding comments to coffee houses
DB
script/generate migration create_comments-
class CreateComments < ActiveRecord::Migration def self.up create_table "comments", :force => true do |t| t.column "body", :text t.column "coffee_house_id", :integer t.column "author", :string t.column "created_at", :datetime end end def self.down drop_table "comments" end end rake db:migrate
Model
- script/generate model comment
- app/models/comment.rb
class Comment < ActiveRecord::Base belongs_to :coffee_house validates_presence_of :author, :body, :coffee_house_id end - app/models/coffee_house.rb
class CoffeeHouse < ActiveRecord::Base #... has_many :comments, :order => 'created_at DESC' #... end
Controller
- app/controllers/coffee_house_controller.rb
-
public def show_comments coffee_house = CoffeeHouse.find_by_id(params[:id]) if coffee_house @comments = coffee_house.comments #fetch list of all comments for this coffee house else render :text => 'Coffee house not found!' and return end render :partial => 'comments' #this partial shows a list of all comments end def new_comment @coffee_house = CoffeeHouse.find_by_id(params[:id]) if @coffee_house @comment = Comment.new(:coffee_house_id => @coffee_house.id) render :partial => 'new_comment' #this partial shows the add comment form else render :text => 'Coffee house not found!' end end def create_comment @comment = Comment.new(params[:comment]) if(@comment.save) @comments = @comment.coffee_house.comments render :partial => 'comments' else render :text => 'Could not save comment' end end
View
- app/views/layouts/coffeehouse.rhtml
<%= javascript_include_tag :defaults %> - app/views/coffee_house/_comments.rhtml
<% if @comments.size > 0 %>-
<% for comment in @comments %>
<%= comment.author %> said <%= time_ago_in_words(comment.created_at) %> ago:
<%= simple_format(comment.body) %>
<% end %>
- app/views/coffee_house/_new_comment.rhtml
<% form_remote_tag :url => {:action => 'create_comment'}, :update => 'comments', :complete => "Effect.SlideDown('comments')" do %> <%= hidden_field 'comment', 'coffee_house_id' %> <div><label for="comment_author">Your name: </label> <%= text_field 'comment', 'author' %></div> <div><label for="comment_body">Your comment:</label> <%= text_area 'comment', 'body' %></div> <%= submit_tag 'Add comment' %> <% end %> - app/views/coffee_house/show.rhtml
<%= link_to_remote 'Add comment', :url=>{:controller => 'coffee_house', :action => 'new_comment', :id => @coffee_house}, :update => 'comments' %> <%= link_to_remote 'Show comments', :url=>{:controller => 'coffee_house', :action => 'show_comments', :id => @coffee_house}, :update => 'comments' %> <div id="comments"></div>
- In place editing
Provide AJAX in place editing for coffee house details.
Model
app/models/coffee_house.rb -- add new virtual attribute which will be helpful for in place city editingdef city_name self.city ? self.city.name : '' endController
app/controllers/coffee_house_controller.rb -- at the topin_place_edit_for :coffee_house, :name in_place_edit_for :coffee_house, :street in_place_edit_for :coffee_house, :building_number in_place_edit_for :coffee_house, :apartment_number in_place_edit_for :coffee_house, :zipcode in_place_edit_for :coffee_house, :city_name def set_coffee_house_city_name @coffee_house = CoffeeHouse.find_by_id(params[:id]) if(@coffee_house) @coffee_house.update_attribute('city_id',params['value']) render :text => @coffee_house.city_name else render :text => 'Error updating city!' end endView
- app/helpers/application_helper.rb
solution taken from in_place_collection_editor_fielddef in_place_collection_editor_field(object,method,container, tag_options={}) tag = ::ActionView::Helpers::InstanceTag.new(object, method, self) tag_options = { :tag => "span", :id => "#{object}_#{method}_#{tag.object.id}_in_place_editor", :class => "in_place_editor_field" }.merge!(tag_options) url = url_for( :action => "set_#{object}_#{method}", :id => tag.object.id ) collection = container.inject([]) do |options, element| options << "[ '#{escape_javascript(element.last.to_s)}', '#{escape_javascript(element.first.to_s)}']" end function = "new Ajax.InPlaceCollectionEditor(" function << "'#{object}_#{method}_#{tag.object.id}_in_place_editor'," function << "'#{url}'," function << "{collection: [#{collection.join(',')}], id: '#{object}_#{method}'});" tag.to_content_tag(tag_options.delete(:tag), tag_options) + javascript_tag(function) end - app/views/coffee_house/show.rhtml -- at the top
<p><label>Name:</label> <%= in_place_editor_field 'coffee_house','name' %></p> <p><label>Address:</label> </p> <p><%= in_place_editor_field 'coffee_house', 'street' %> <%= in_place_editor_field 'coffee_house', 'building_number' %> / <%= in_place_editor_field 'coffee_house', 'apartment_number' %></p> <p><%= in_place_editor_field 'coffee_house', 'zipcode' %> <%= in_place_collection_editor_field 'coffee_house', 'city_name', City.find(:all).collect{|c| [c.name,c.id]} %></p>
- app/helpers/application_helper.rb
- Auto complete simple search
Controller
app/controllers/coffee_house_controller.rb- add auto complete for coffee house name:
auto_complete_for :coffee_house, :name - add search function:
def search @query = params[:coffee_house][:name] if params[:coffee_house] @query ||= params[:query] @query ||= '' @coffee_house_pages, @coffee_houses = paginate :coffee_houses, :conditions => ['name LIKE ?','%'+@query+'%'], :per_page => 5 render :partial => 'list' end
View
- public/stylesheets/coffee_houses.css
.coffee_house{ width: 400px; border: 1px solid #000000; margin: 10px; } .thumb{ float:left; } .info{ width: 300px; float:right; } .search-box{ width: 300px; padding: 10px; } - app/views/coffee_house/_list.rhtml -- new list partial
<% for coffee_house in @coffee_houses %> <%= render :partial => 'list_element', :locals => {'coffee_house' => coffee_house} %> <div> <%= link_to 'Show', :action => 'show', :id=>coffee_house %> <%= link_to 'Edit', :action => 'edit', :id => coffee_house %> <%= link_to 'Destroy', {:action => 'destroy', :id=>coffee_house}, :method => :post %> </div> <% end %> <% if @query %> <%= link_to_remote "Previous", :url => { :query => @query, :page => @coffee_house_pages.current.previous }, :update => 'coffee_houses' if @coffee_house_pages.current.previous %> <%= link_to_remote "Next", :url => { :query => @query, :page => @coffee_house_pages.current.next }, :update => 'coffee_houses' if @coffee_house_pages.current.next %> <% end %> - app/views/coffee_house/_list_element.rhtml -- new list element partial
<div class="coffee_house"> <% photo = coffee_house.photos.first %> <%= image_tag(photo.public_filename('thumb'),:class=>'thumb') if photo %> <div class="info"> <h3><%= link_to coffee_house.name, :controller => 'coffee_house', :action => 'show', :id => coffee_house %></h3> <p><%= link_to coffee_house.company.name, :controller => 'company', :action => 'show', :id=>coffee_house.company if coffee_house.company %></p> <p><%= "#{coffee_house.street} #{coffee_house.building_number}" %></p> <p><%= link_to coffee_house.city.name, :controller => 'city', :action => 'show', :id => coffee_house.city if coffee_house.city %></p> </div> <div style="clear:both"></div> </div> - app/views/coffee_house/list.rhtml -- add the search box and replace old listing
ajax-loader.gif<h1>Listing Coffee Houses</h1> <div class="search-box"> <% form_remote_tag :url => {:action => 'search'}, :update => 'coffee_houses', :loading => "Element.show('search-loader')", :complete => "Element.hide('search-loader')" do %> <%= text_field_with_auto_complete 'coffee_house','name' %> <%= submit_tag 'Search' %> <% end %> <%= image_tag 'ajax-loader.gif', :id => 'search-loader', :style => 'display:none' %> </div> <div id="coffee_houses"> <%= render :partial => 'list' %> <%= link_to 'Previous page', { :page => @coffee_house_pages.current.previous } if @coffee_house_pages.current.previous %> <%= link_to 'Next page', { :page => @coffee_house_pages.current.next } if @coffee_house_pages.current.next %> </div> <%= link_to 'New coffee_house', :action => 'new' %> <hr> <%= link_to 'New company', :controller => 'company', :action => 'new' %> <%= link_to 'New city', :controller => 'city', :action => 'new' %>
- add auto complete for coffee house name: