Ruby on Rails
Sort Helper

By Stephen F. Booth
Download

Helper to sort tables or result sets using clickable column headers.

Usage

Most helpful when used in conjunction with the user-friendly PaginationHelper.

Add the following line to routes.rb:


map.connect ':controller/:action/:sort/:order'

The controller should look something like:


helper :sorting

def list
@sorter = SortingHelper::Sorter.new self, %w(icao host_id name), @params‘sort’, @params‘order’, ‘icao’, ‘ASC
@pages = Paginator.new self, Airport.count, 10, @params‘page’
@airports = Airport.find_all nil, @sorter.to_sql, @pages.current.to_sql

render_action ‘none’ if 0 == @airports.length

end

And the view akin to:


<table>
  <tr>
    <th><%= link_to 'ICAO', @sorter.to_link('icao') %>

<%= link_to ‘Host ID’, @sorter.to_link(’host_id’) %> <%= link_to ‘Name’, @sorter.to_link(’name’) %> Location <%= render_collection_of_partials “row”, @airports %>

category:Helper

—————
Would you post your _row.rhtml file? I’d like to see what it looks like.

—————


<tr class="centered data <%= row_counter.modulo(2) == 0 ? "row_light" : "row_dark" %>">
  <td onclick="window.location.href = '<%= url_for :action => 'detail', :id => row.id %>'"><%=h row.icao %>

<%=h row.host_id %> <%=h row.name %> <=h row.wgs_
lat %> <
=h row.wgs_long %>
<%= link_image_to ‘details’, { :action => ‘detail’, :id => row.id }, :title => ‘Details’ %>

—————-
How would I filter results by category, then implement sorting upon those?
—————-
Add those two lines to your list.rhtml for pagenation integration:


<%= link_to('< Previous', {:page => @pages.current.previous, :sort => params[:sort], :order => params[:order]}) if @pages.current.previous %>
<%= link_to('Next >', {:page => @pages.current.next, :sort => params[:sort], :order => params[:order]}) if @pages.current.next %>

—————-
I think I found a bug in the code. The line:

{ :params => { ‘sort’ => column, ‘order’ => order }.merge(params) }

This is trying to do a “new.merge(old)” however it seems that the merge works the other way round in terms of ensuring that the new settings override the old. I tried the following and it seemed to work:

{ :params => params.merge({ ‘sort’ => column, ‘order’ => order }) }

—————-

Little improvement to propose: visualy identify the currently sorted column.

Add the following to methods to the Sorter class:


    # if +column+ is the current selected order, return 'ASC' or 'DESC', else return nil
    def column_order(column)
      column = @default_sort unless @columns.include?(column)

      if column == @sort
        order = @order
      else
        order = nil
      end
      order
    end

    # little view helper to create the name of the class for the column
    # by default return tablesort_none, tablesort_asc, tablesort_desc depending on whether column is ordered or not
    def column_order_class(column, classname = 'tablesort', separator = '_', notordered='none')
      order = column_order(column)
      classname + separator + (order.nil? ? notordered : order.downcase)
    end

Then in your view:


  <tr>
    <th>

<%= link_to ‘Email’, @sorter.to_link(’name’) %> <%= link_to ‘Short’, @sorter.to_link(’short’) %>

and in a stylesheet:


th.tablesort_none {
}

th.tablesort_asc {
  background: url(/images/icon_sort_asc.gif) left center no-repeat;
  padding-left: 16px;
}

th.tablesort_desc {
  background: url(/images/icon_sort_desc.gif) left center no-repeat;
  padding-left: 16px;
}