You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

sort_helper.rb 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. # Helpers to sort tables using clickable column headers.
  2. #
  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.
  6. #
  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.
  11. #
  12. # Example code snippets:
  13. #
  14. # Controller:
  15. #
  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):
  26. #
  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>
  48. #
  49. # - Introduces instance variables: @sort_default, @sort_criteria
  50. # - Introduces param :sort
  51. #
  52. module SortHelper
  53. class SortCriteria
  54. def initialize
  55. @criteria = []
  56. end
  57. def available_criteria=(criteria)
  58. unless criteria.is_a?(Hash)
  59. criteria = criteria.inject({}) {|h,k| h[k] = k; h}
  60. end
  61. @available_criteria = criteria
  62. end
  63. def from_param(param)
  64. @criteria = param.to_s.split(',').collect {|s| s.split(':')[0..1]}
  65. normalize!
  66. end
  67. def criteria=(arg)
  68. @criteria = arg
  69. normalize!
  70. end
  71. def to_param
  72. @criteria.collect {|k,o| k + (o ? '' : ':desc')}.join(',')
  73. end
  74. def to_sql
  75. sql = @criteria.collect do |k,o|
  76. if s = @available_criteria[k]
  77. (o ? s.to_a : s.to_a.collect {|c| append_desc(c)}).join(', ')
  78. end
  79. end.compact.join(', ')
  80. sql.blank? ? nil : sql
  81. end
  82. def add!(key, asc)
  83. @criteria.delete_if {|k,o| k == key}
  84. @criteria = [[key, asc]] + @criteria
  85. normalize!
  86. end
  87. def add(*args)
  88. r = self.class.new.from_param(to_param)
  89. r.add!(*args)
  90. r
  91. end
  92. def first_key
  93. @criteria.first && @criteria.first.first
  94. end
  95. def first_asc?
  96. @criteria.first && @criteria.first.last
  97. end
  98. def empty?
  99. @criteria.empty?
  100. end
  101. private
  102. def normalize!
  103. @criteria ||= []
  104. @criteria = @criteria.collect {|s| s = s.to_a; [s.first, (s.last == false || s.last == 'desc') ? false : true]}
  105. @criteria = @criteria.select {|k,o| @available_criteria.has_key?(k)} if @available_criteria
  106. @criteria.slice!(3)
  107. self
  108. end
  109. # Appends DESC to the sort criterion unless it has a fixed order
  110. def append_desc(criterion)
  111. if criterion =~ / (asc|desc)$/i
  112. criterion
  113. else
  114. "#{criterion} DESC"
  115. end
  116. end
  117. end
  118. def sort_name
  119. controller_name + '_' + action_name + '_sort'
  120. end
  121. # Initializes the default sort.
  122. # Examples:
  123. #
  124. # sort_init 'name'
  125. # sort_init 'id', 'desc'
  126. # sort_init ['name', ['id', 'desc']]
  127. # sort_init [['name', 'desc'], ['id', 'desc']]
  128. #
  129. def sort_init(*args)
  130. case args.size
  131. when 1
  132. @sort_default = args.first.is_a?(Array) ? args.first : [[args.first]]
  133. when 2
  134. @sort_default = [[args.first, args.last]]
  135. else
  136. raise ArgumentError
  137. end
  138. end
  139. # Updates the sort state. Call this in the controller prior to calling
  140. # sort_clause.
  141. # - criteria can be either an array or a hash of allowed keys
  142. #
  143. def sort_update(criteria)
  144. @sort_criteria = SortCriteria.new
  145. @sort_criteria.available_criteria = criteria
  146. @sort_criteria.from_param(params[:sort] || session[sort_name])
  147. @sort_criteria.criteria = @sort_default if @sort_criteria.empty?
  148. session[sort_name] = @sort_criteria.to_param
  149. end
  150. # Clears the sort criteria session data
  151. #
  152. def sort_clear
  153. session[sort_name] = nil
  154. end
  155. # Returns an SQL sort clause corresponding to the current sort state.
  156. # Use this to sort the controller's table items collection.
  157. #
  158. def sort_clause()
  159. @sort_criteria.to_sql
  160. end
  161. # Returns a link which sorts by the named column.
  162. #
  163. # - column is the name of an attribute in the sorted record collection.
  164. # - the optional caption explicitly specifies the displayed link text.
  165. # - 2 CSS classes reflect the state of the link: sort and asc or desc
  166. #
  167. def sort_link(column, caption, default_order)
  168. css, order = nil, default_order
  169. if column.to_s == @sort_criteria.first_key
  170. if @sort_criteria.first_asc?
  171. css = 'sort asc'
  172. order = 'desc'
  173. else
  174. css = 'sort desc'
  175. order = 'asc'
  176. end
  177. end
  178. caption = column.to_s.humanize unless caption
  179. sort_options = { :sort => @sort_criteria.add(column.to_s, order).to_param }
  180. url_options = params.merge(sort_options)
  181. # Add project_id to url_options
  182. url_options = url_options.merge(:project_id => params[:project_id]) if params.has_key?(:project_id)
  183. link_to_content_update(h(caption), url_options, :class => css)
  184. end
  185. # Returns a table header <th> tag with a sort link for the named column
  186. # attribute.
  187. #
  188. # Options:
  189. # :caption The displayed link name (defaults to titleized column name).
  190. # :title The tag's 'title' attribute (defaults to 'Sort by :caption').
  191. #
  192. # Other options hash entries generate additional table header tag attributes.
  193. #
  194. # Example:
  195. #
  196. # <%= sort_header_tag('id', :title => 'Sort by contact ID', :width => 40) %>
  197. #
  198. def sort_header_tag(column, options = {})
  199. caption = options.delete(:caption) || column.to_s.humanize
  200. default_order = options.delete(:default_order) || 'asc'
  201. options[:title] = l(:label_sort_by, "\"#{caption}\"") unless options[:title]
  202. content_tag('th', sort_link(column, caption, default_order), options)
  203. end
  204. end