Ruby on Rails
Sort Helper (Version #110)

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') %></th>
    <th><%= link_to 'Host ID', @sorter.to_link('host_id') %></th>
    <th><%= link_to 'Name', @sorter.to_link('name') %></th>
    <th colspan="2">Location</th>
  </tr>

  <%= render_collection_of_partials "row", @airports %>

</table>

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 %></td>
  <td onclick="window.location.href = '<%= url_for :action => 'detail', :id => row.id %>'"><%=h row.host_id %></td>
  <td onclick="window.location.href = '<%= url_for :action => 'detail', :id => row.id %>'"><%=h row.name %></td>
  <td><a href="http://maps.google.com/maps?q=<%= row.wgs_dlat %>+<%= row.wgs_dlong %>&hl=en" target="_new"><%=h row.wgs_
lat %> <%=h row.wgs_long %></a></td>
  <td><%= link_image_to 'details', { :action => 'detail', :id => row.id }, :title => 'Details' %></td>
</tr>


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></th>
    <th align="left" class="<%= @sorter.column_order_class('name') %>"><%= link_to 'Email', @sorter.to_link('name') %></th>
    <th align="left" class="<%= @sorter.column_order_class('short') %>"><%= link_to 'Short', @sorter.to_link('short') %></th>
  </tr>

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;
}

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') %></th>
    <th><%= link_to 'Host ID', @sorter.to_link('host_id') %></th>
    <th><%= link_to 'Name', @sorter.to_link('name') %></th>
    <th colspan="2">Location</th>
  </tr>

  <%= render_collection_of_partials "row", @airports %>

</table>

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 %></td>
  <td onclick="window.location.href = '<%= url_for :action => 'detail', :id => row.id %>'"><%=h row.host_id %></td>
  <td onclick="window.location.href = '<%= url_for :action => 'detail', :id => row.id %>'"><%=h row.name %></td>
  <td><a href="http://maps.google.com/maps?q=<%= row.wgs_dlat %>+<%= row.wgs_dlong %>&hl=en" target="_new"><%=h row.wgs_
lat %> <%=h row.wgs_long %></a></td>
  <td><%= link_image_to 'details', { :action => 'detail', :id => row.id }, :title => 'Details' %></td>
</tr>


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></th>
    <th align="left" class="<%= @sorter.column_order_class('name') %>"><%= link_to 'Email', @sorter.to_link('name') %></th>
    <th align="left" class="<%= @sorter.column_order_class('short') %>"><%= link_to 'Short', @sorter.to_link('short') %></th>
  </tr>

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;
}