Ruby on Rails
How to make a real-time search box with the Ajax helpers (Version #126)

WikiGardening This page has comments on it that need to be merged into the article.

IMPORTANT You must have one of


<%= define_javascript_functions %> or 
<%= javascript_include_tag  :defaults %> 

on your headers for this to work!

Just have a div with id ‘target_id’ ready and add this to your view:


<input id="search" name="search" type="text" value="">
<%= observe_field 'search',  :frequency => 0.5, 
         :update => 'target_id', :url => 
         { :controller => '<controller>', :action=> 'list' }, 
         :with => "'search=' + escape(value)" %>

Next, add an action for this, like the following (which also uses the paginate function):


def list
  if @params['search']
    @items_pages, @items = paginate :items,
      :order_by => 'description',
      :conditions => [ 'LOWER(description) LIKE ?',
        '%' + @params['search'].downcase + '%' ], 
      :per_page => 20
    @mark_term = @params['search']
    render_without_layout
  else
    @items_pages, @items = paginate :items, 
      :order_by => 'description', :per_page => 20
  end
end

You can then use the @mark_term in your ‘list’ view like this:


<%= @mark_term ? highlight(item.description, @mark_term) : h(item.description) %>

To use the paginator with ajax, use something like this in your ‘list’ view:


<%= link_to_remote(h('< Previous'), 
         :update => 'target_id', 
         :url => { :page => paginator.current.previous }) %> 
-
<%= link_to_remote(h('Next >'), 
         :update => 'target_id', 
         :url => { :page => paginator.current.next }) %>

dgm I had to add a parameter to the paginate links:


<%= link_to_remote(h('< Previous'), 
         :update => 'target_id', 
         :url => { :page => paginator.current.previous, :search => @params['search'] }) %> 

paul It seems that if you type a ‘&’ in the search box, whatever follows will not be passed in the search text. That is, if you type Chip&Dale in the search box, only Chip will be passed. However, I experimented and this site implements it correctly. Maybe they should tell us the trick?

xenonite do not forget to escape the passed value like this, via javascript: escape(value)

kayess I had a difficult time deciding where to place the search input box if you are using a multi-purpose layout. If you put it in the layout, that layout can’t be used for any other pages. If you put it in the view and outside the div “target_id” then it renders over and over (because render_without_layout only applies to the layout, duh). If you put it in the view and also in the div “target_id” then it reloads with the div and any value you type while it is updating will disappear, giving a jittery user experience. My solution? Put it in the view, outside the div “target_id” and then just bracket anything in your view that shouldn’t repeat when the page reloads with:


<% if @mark_term==nil %>
    Hide this on the div update.
<% end %>

eyevee99 To avoid the repetition problem, you can create a partial and render that instead of “render_without_layout”. I created a _search.rhtml and simply called render :partial => ‘search’.

yellow How do you use pagination_links here?

Juba If you are interested, I have written a tutorial on how to paginate, sort and live search a table with Ajax and Rails. It is available at :

http://dev.nozav.org/rails_ajax_table.html

CK Is there a way to make it display all possible results when the page is loaded? (i.e. force the search to redraw when the page is first loaded). That way it would be more like a live filter, which has its applications when used with observe_form.

rbreve The only way to read the variable or params passed by the observe_field is to do this:


keyword = request.raw_post 

See also

WikiGardening This page has comments on it that need to be merged into the article.

IMPORTANT You must have one of


<%= define_javascript_functions %> or 
<%= javascript_include_tag  :defaults %> 

on your headers for this to work!

Just have a div with id ‘target_id’ ready and add this to your view:


<input id="search" name="search" type="text" value="">
<%= observe_field 'search',  :frequency => 0.5, 
         :update => 'target_id', :url => 
         { :controller => '<controller>', :action=> 'list' }, 
         :with => "'search=' + escape(value)" %>

Next, add an action for this, like the following (which also uses the paginate function):


def list
  if @params['search']
    @items_pages, @items = paginate :items,
      :order_by => 'description',
      :conditions => [ 'LOWER(description) LIKE ?',
        '%' + @params['search'].downcase + '%' ], 
      :per_page => 20
    @mark_term = @params['search']
    render_without_layout
  else
    @items_pages, @items = paginate :items, 
      :order_by => 'description', :per_page => 20
  end
end

You can then use the @mark_term in your ‘list’ view like this:


<%= @mark_term ? highlight(item.description, @mark_term) : h(item.description) %>

To use the paginator with ajax, use something like this in your ‘list’ view:


<%= link_to_remote(h('< Previous'), 
         :update => 'target_id', 
         :url => { :page => paginator.current.previous }) %> 
-
<%= link_to_remote(h('Next >'), 
         :update => 'target_id', 
         :url => { :page => paginator.current.next }) %>

dgm I had to add a parameter to the paginate links:


<%= link_to_remote(h('< Previous'), 
         :update => 'target_id', 
         :url => { :page => paginator.current.previous, :search => @params['search'] }) %> 

paul It seems that if you type a ‘&’ in the search box, whatever follows will not be passed in the search text. That is, if you type Chip&Dale in the search box, only Chip will be passed. However, I experimented and this site implements it correctly. Maybe they should tell us the trick?

xenonite do not forget to escape the passed value like this, via javascript: escape(value)

kayess I had a difficult time deciding where to place the search input box if you are using a multi-purpose layout. If you put it in the layout, that layout can’t be used for any other pages. If you put it in the view and outside the div “target_id” then it renders over and over (because render_without_layout only applies to the layout, duh). If you put it in the view and also in the div “target_id” then it reloads with the div and any value you type while it is updating will disappear, giving a jittery user experience. My solution? Put it in the view, outside the div “target_id” and then just bracket anything in your view that shouldn’t repeat when the page reloads with:


<% if @mark_term==nil %>
    Hide this on the div update.
<% end %>

eyevee99 To avoid the repetition problem, you can create a partial and render that instead of “render_without_layout”. I created a _search.rhtml and simply called render :partial => ‘search’.

yellow How do you use pagination_links here?

Juba If you are interested, I have written a tutorial on how to paginate, sort and live search a table with Ajax and Rails. It is available at :

http://dev.nozav.org/rails_ajax_table.html

CK Is there a way to make it display all possible results when the page is loaded? (i.e. force the search to redraw when the page is first loaded). That way it would be more like a live filter, which has its applications when used with observe_form.

rbreve The only way to read the variable or params passed by the observe_field is to do this:


keyword = request.raw_post 

See also