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