File: app/helpers/sort_helper.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  module: SortHelper#55
has properties
method: sort_name #136
method: sort_init / 1 #148
method: sort_update / 2 #163
method: sort_clear #174
method: sort_clause #181
method: sort_link / 3 #191
method: sort_header_tag / 2 #227
  class: SortCriteria#56
inherits from
  Object ( Builtin-Module )
has properties
method: initialize #58
method: available_criteria= / 1 #62
method: from_param / 1 #69
method: criteria= / 1 #74
method: to_param #79
method: to_sql #83
method: add! / 2 #92
method: add / 1 #98
method: first_key #104
method: first_asc? #108
method: empty? #112
method: normalize! #118
method: append_desc / 1 #127

Class Hierarchy

Code

   1  # encoding: utf-8
   2  #
   3  # Helpers to sort tables using clickable column headers.
   4  #
   5  # Author:  Stuart Rackham <srackham@methods.co.nz>, March 2005.
   6  #          Jean-Philippe Lang, 2009
   7  # License: This source code is released under the MIT license.
   8  #
   9  # - Consecutive clicks toggle the column's sort order.
  10  # - Sort state is maintained by a session hash entry.
  11  # - CSS classes identify sort column and state.
  12  # - Typically used in conjunction with the Pagination module.
  13  #
  14  # Example code snippets:
  15  #
  16  # Controller:
  17  #
  18  #   helper :sort
  19  #   include SortHelper
  20  #
  21  #   def list
  22  #     sort_init 'last_name'
  23  #     sort_update %w(first_name last_name)
  24  #     @items = Contact.find_all nil, sort_clause
  25  #   end
  26  #
  27  # Controller (using Pagination module):
  28  #
  29  #   helper :sort
  30  #   include SortHelper
  31  #
  32  #   def list
  33  #     sort_init 'last_name'
  34  #     sort_update %w(first_name last_name)
  35  #     @contact_pages, @items = paginate :contacts,
  36  #       :order_by => sort_clause,
  37  #       :per_page => 10
  38  #   end
  39  #
  40  # View (table header in list.rhtml):
  41  #
  42  #   <thead>
  43  #     <tr>
  44  #       <%= sort_header_tag('id', :title => 'Sort by contact ID') %>
  45  #       <%= sort_header_tag('last_name', :caption => 'Name') %>
  46  #       <%= sort_header_tag('phone') %>
  47  #       <%= sort_header_tag('address', :width => 200) %>
  48  #     </tr>
  49  #   </thead>
  50  #
  51  # - Introduces instance variables: @sort_default, @sort_criteria
  52  # - Introduces param :sort
  53  #
  54 
  55  module SortHelper
  56    class SortCriteria
  57 
  58      def initialize
  59        @criteria = []
  60      end
  61 
  62      def available_criteria=(criteria)
  63        unless criteria.is_a?(Hash)
  64          criteria = criteria.inject({}) {|h,k| h[k] = k; h}
  65        end
  66        @available_criteria = criteria
  67      end
  68 
  69      def from_param(param)
  70        @criteria = param.to_s.split(',').collect {|s| s.split(':')[0..1]}
  71        normalize!
  72      end
  73 
  74      def criteria=(arg)
  75        @criteria = arg
  76        normalize!
  77      end
  78 
  79      def to_param
  80        @criteria.collect {|k,o| k + (o ? '' : ':desc')}.join(',')
  81      end
  82 
  83      def to_sql
  84        sql = @criteria.collect do |k,o|
  85          if s = @available_criteria[k]
  86            (o ? s.to_a : s.to_a.collect {|c| append_desc(c)}).join(', ')
  87          end
  88        end.compact.join(', ')
  89        sql.blank? ? nil : sql
  90      end
  91 
  92      def add!(key, asc)
  93        @criteria.delete_if {|k,o| k == key}
  94        @criteria = [[key, asc]] + @criteria
  95        normalize!
  96      end
  97 
  98      def add(*args)
  99        r = self.class.new.from_param(to_param)
 100        r.add!(*args)
 101        r
 102      end
 103 
 104      def first_key
 105        @criteria.first && @criteria.first.first
 106      end
 107 
 108      def first_asc?
 109        @criteria.first && @criteria.first.last
 110      end
 111 
 112      def empty?
 113        @criteria.empty?
 114      end
 115 
 116      private
 117 
 118      def normalize!
 119        @criteria ||= []
 120        @criteria = @criteria.collect {|s| s = s.to_a; [s.first, (s.last == false || s.last == 'desc') ? false : true]}
 121        @criteria = @criteria.select {|k,o| @available_criteria.has_key?(k)} if @available_criteria
 122        @criteria.slice!(3)
 123        self
 124      end
 125 
 126      # Appends DESC to the sort criterion unless it has a fixed order
 127      def append_desc(criterion)
 128        if criterion =~ / (asc|desc)$/i
 129          criterion
 130        else
 131          "#{criterion} DESC"
 132        end
 133      end
 134    end
 135 
 136    def sort_name
 137      controller_name + '_' + action_name + '_sort'
 138    end
 139 
 140    # Initializes the default sort.
 141    # Examples:
 142    #
 143    #   sort_init 'name'
 144    #   sort_init 'id', 'desc'
 145    #   sort_init ['name', ['id', 'desc']]
 146    #   sort_init [['name', 'desc'], ['id', 'desc']]
 147    #
 148    def sort_init(*args)
 149      case args.size
 150      when 1
 151        @sort_default = args.first.is_a?(Array) ? args.first : [[args.first]]
 152      when 2
 153        @sort_default = [[args.first, args.last]]
 154      else
 155        raise ArgumentError
 156      end
 157    end
 158 
 159    # Updates the sort state. Call this in the controller prior to calling
 160    # sort_clause.
 161    # - criteria can be either an array or a hash of allowed keys
 162    #
 163    def sort_update(criteria, sort_name=nil)
 164      sort_name ||= self.sort_name
 165      @sort_criteria = SortCriteria.new
 166      @sort_criteria.available_criteria = criteria
 167      @sort_criteria.from_param(params[:sort] || session[sort_name])
 168      @sort_criteria.criteria = @sort_default if @sort_criteria.empty?
 169      session[sort_name] = @sort_criteria.to_param
 170    end
 171 
 172    # Clears the sort criteria session data
 173    #
 174    def sort_clear
 175      session[sort_name] = nil
 176    end
 177 
 178    # Returns an SQL sort clause corresponding to the current sort state.
 179    # Use this to sort the controller's table items collection.
 180    #
 181    def sort_clause()
 182      @sort_criteria.to_sql
 183    end
 184 
 185    # Returns a link which sorts by the named column.
 186    #
 187    # - column is the name of an attribute in the sorted record collection.
 188    # - the optional caption explicitly specifies the displayed link text.
 189    # - 2 CSS classes reflect the state of the link: sort and asc or desc
 190    #
 191    def sort_link(column, caption, default_order)
 192      css, order = nil, default_order
 193 
 194      if column.to_s == @sort_criteria.first_key
 195        if @sort_criteria.first_asc?
 196          css = 'sort asc'
 197          order = 'desc'
 198        else
 199          css = 'sort desc'
 200          order = 'asc'
 201        end
 202      end
 203      caption = column.to_s.humanize unless caption
 204 
 205      sort_options = { :sort => @sort_criteria.add(column.to_s, order).to_param }
 206      url_options = params.merge(sort_options)
 207 
 208       # Add project_id to url_options
 209      url_options = url_options.merge(:project_id => params[:project_id]) if params.has_key?(:project_id)
 210 
 211      link_to_content_update(h(caption), url_options, :class => css)
 212    end
 213 
 214    # Returns a table header <th> tag with a sort link for the named column
 215    # attribute.
 216    #
 217    # Options:
 218    #   :caption     The displayed link name (defaults to titleized column name).
 219    #   :title       The tag's 'title' attribute (defaults to 'Sort by :caption').
 220    #
 221    # Other options hash entries generate additional table header tag attributes.
 222    #
 223    # Example:
 224    #
 225    #   <%= sort_header_tag('id', :title => 'Sort by contact ID', :width => 40) %>
 226    #
 227    def sort_header_tag(column, options = {})
 228      caption = options.delete(:caption) || column.to_s.humanize
 229      default_order = options.delete(:default_order) || 'asc'
 230      options[:title] = l(:label_sort_by, "\"#{caption}\"") unless options[:title]
 231      content_tag('th', sort_link(column, caption, default_order), options)
 232    end
 233  end
 234