searchlogic 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. data/CHANGELOG.rdoc +228 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Manifest +123 -0
  4. data/README.rdoc +383 -0
  5. data/Rakefile +15 -0
  6. data/TODO.rdoc +6 -0
  7. data/examples/README.rdoc +4 -0
  8. data/init.rb +1 -0
  9. data/lib/searchlogic.rb +89 -0
  10. data/lib/searchlogic/active_record/associations.rb +52 -0
  11. data/lib/searchlogic/active_record/base.rb +218 -0
  12. data/lib/searchlogic/active_record/connection_adapters/mysql_adapter.rb +172 -0
  13. data/lib/searchlogic/active_record/connection_adapters/postgresql_adapter.rb +168 -0
  14. data/lib/searchlogic/active_record/connection_adapters/sqlite_adapter.rb +75 -0
  15. data/lib/searchlogic/condition/base.rb +159 -0
  16. data/lib/searchlogic/condition/begins_with.rb +17 -0
  17. data/lib/searchlogic/condition/blank.rb +21 -0
  18. data/lib/searchlogic/condition/child_of.rb +11 -0
  19. data/lib/searchlogic/condition/descendant_of.rb +24 -0
  20. data/lib/searchlogic/condition/ends_with.rb +17 -0
  21. data/lib/searchlogic/condition/equals.rb +27 -0
  22. data/lib/searchlogic/condition/greater_than.rb +15 -0
  23. data/lib/searchlogic/condition/greater_than_or_equal_to.rb +15 -0
  24. data/lib/searchlogic/condition/inclusive_descendant_of.rb +11 -0
  25. data/lib/searchlogic/condition/keywords.rb +47 -0
  26. data/lib/searchlogic/condition/less_than.rb +15 -0
  27. data/lib/searchlogic/condition/less_than_or_equal_to.rb +15 -0
  28. data/lib/searchlogic/condition/like.rb +15 -0
  29. data/lib/searchlogic/condition/nil.rb +21 -0
  30. data/lib/searchlogic/condition/not_begin_with.rb +20 -0
  31. data/lib/searchlogic/condition/not_blank.rb +19 -0
  32. data/lib/searchlogic/condition/not_end_with.rb +20 -0
  33. data/lib/searchlogic/condition/not_equal.rb +26 -0
  34. data/lib/searchlogic/condition/not_have_keywords.rb +20 -0
  35. data/lib/searchlogic/condition/not_like.rb +20 -0
  36. data/lib/searchlogic/condition/not_nil.rb +19 -0
  37. data/lib/searchlogic/condition/sibling_of.rb +14 -0
  38. data/lib/searchlogic/condition/tree.rb +17 -0
  39. data/lib/searchlogic/conditions/base.rb +484 -0
  40. data/lib/searchlogic/conditions/protection.rb +36 -0
  41. data/lib/searchlogic/config.rb +31 -0
  42. data/lib/searchlogic/config/helpers.rb +289 -0
  43. data/lib/searchlogic/config/search.rb +53 -0
  44. data/lib/searchlogic/core_ext/hash.rb +75 -0
  45. data/lib/searchlogic/helpers/control_types/link.rb +310 -0
  46. data/lib/searchlogic/helpers/control_types/links.rb +241 -0
  47. data/lib/searchlogic/helpers/control_types/remote_link.rb +87 -0
  48. data/lib/searchlogic/helpers/control_types/remote_links.rb +72 -0
  49. data/lib/searchlogic/helpers/control_types/remote_select.rb +36 -0
  50. data/lib/searchlogic/helpers/control_types/select.rb +82 -0
  51. data/lib/searchlogic/helpers/form.rb +208 -0
  52. data/lib/searchlogic/helpers/utilities.rb +197 -0
  53. data/lib/searchlogic/modifiers/absolute.rb +15 -0
  54. data/lib/searchlogic/modifiers/acos.rb +11 -0
  55. data/lib/searchlogic/modifiers/asin.rb +11 -0
  56. data/lib/searchlogic/modifiers/atan.rb +11 -0
  57. data/lib/searchlogic/modifiers/base.rb +27 -0
  58. data/lib/searchlogic/modifiers/ceil.rb +15 -0
  59. data/lib/searchlogic/modifiers/char_length.rb +15 -0
  60. data/lib/searchlogic/modifiers/cos.rb +15 -0
  61. data/lib/searchlogic/modifiers/cot.rb +15 -0
  62. data/lib/searchlogic/modifiers/day_of_month.rb +15 -0
  63. data/lib/searchlogic/modifiers/day_of_week.rb +15 -0
  64. data/lib/searchlogic/modifiers/day_of_year.rb +15 -0
  65. data/lib/searchlogic/modifiers/degrees.rb +11 -0
  66. data/lib/searchlogic/modifiers/exp.rb +15 -0
  67. data/lib/searchlogic/modifiers/floor.rb +15 -0
  68. data/lib/searchlogic/modifiers/hex.rb +11 -0
  69. data/lib/searchlogic/modifiers/hour.rb +11 -0
  70. data/lib/searchlogic/modifiers/log.rb +15 -0
  71. data/lib/searchlogic/modifiers/log10.rb +11 -0
  72. data/lib/searchlogic/modifiers/log2.rb +11 -0
  73. data/lib/searchlogic/modifiers/lower.rb +15 -0
  74. data/lib/searchlogic/modifiers/ltrim.rb +15 -0
  75. data/lib/searchlogic/modifiers/md5.rb +11 -0
  76. data/lib/searchlogic/modifiers/microseconds.rb +11 -0
  77. data/lib/searchlogic/modifiers/milliseconds.rb +11 -0
  78. data/lib/searchlogic/modifiers/minute.rb +15 -0
  79. data/lib/searchlogic/modifiers/month.rb +15 -0
  80. data/lib/searchlogic/modifiers/octal.rb +15 -0
  81. data/lib/searchlogic/modifiers/radians.rb +11 -0
  82. data/lib/searchlogic/modifiers/round.rb +11 -0
  83. data/lib/searchlogic/modifiers/rtrim.rb +15 -0
  84. data/lib/searchlogic/modifiers/second.rb +15 -0
  85. data/lib/searchlogic/modifiers/sign.rb +11 -0
  86. data/lib/searchlogic/modifiers/sin.rb +11 -0
  87. data/lib/searchlogic/modifiers/square_root.rb +15 -0
  88. data/lib/searchlogic/modifiers/tan.rb +15 -0
  89. data/lib/searchlogic/modifiers/trim.rb +15 -0
  90. data/lib/searchlogic/modifiers/upper.rb +15 -0
  91. data/lib/searchlogic/modifiers/week.rb +11 -0
  92. data/lib/searchlogic/modifiers/year.rb +11 -0
  93. data/lib/searchlogic/search/base.rb +148 -0
  94. data/lib/searchlogic/search/conditions.rb +53 -0
  95. data/lib/searchlogic/search/ordering.rb +244 -0
  96. data/lib/searchlogic/search/pagination.rb +121 -0
  97. data/lib/searchlogic/search/protection.rb +89 -0
  98. data/lib/searchlogic/search/searching.rb +31 -0
  99. data/lib/searchlogic/shared/utilities.rb +50 -0
  100. data/lib/searchlogic/shared/virtual_classes.rb +39 -0
  101. data/lib/searchlogic/version.rb +79 -0
  102. data/searchlogic.gemspec +39 -0
  103. data/test/fixtures/accounts.yml +15 -0
  104. data/test/fixtures/cats.yml +3 -0
  105. data/test/fixtures/dogs.yml +3 -0
  106. data/test/fixtures/orders.yml +14 -0
  107. data/test/fixtures/user_groups.yml +13 -0
  108. data/test/fixtures/users.yml +36 -0
  109. data/test/test_active_record_associations.rb +81 -0
  110. data/test/test_active_record_base.rb +93 -0
  111. data/test/test_condition_base.rb +52 -0
  112. data/test/test_condition_types.rb +143 -0
  113. data/test/test_conditions_base.rb +242 -0
  114. data/test/test_conditions_protection.rb +16 -0
  115. data/test/test_config.rb +23 -0
  116. data/test/test_helper.rb +134 -0
  117. data/test/test_search_base.rb +227 -0
  118. data/test/test_search_conditions.rb +19 -0
  119. data/test/test_search_ordering.rb +165 -0
  120. data/test/test_search_pagination.rb +72 -0
  121. data/test/test_search_protection.rb +24 -0
  122. data/test_libs/acts_as_tree.rb +98 -0
  123. data/test_libs/ordered_hash.rb +9 -0
  124. data/test_libs/rexml_fix.rb +14 -0
  125. metadata +317 -0
@@ -0,0 +1,310 @@
1
+ module Searchlogic
2
+ module Helpers
3
+ # = Control Type Helpers
4
+ #
5
+ # The purpose of these helpers is to make ordering and paginating data, in your view, a breeze. Everyone has their own flavor of displaying data, so I made these helpers extra flexible, just for you.
6
+ #
7
+ # === Tutorial
8
+ #
9
+ # Check out my tutorial on how to implement searchlogic into a rails app: http://www.binarylogic.com/2008/9/7/tutorial-pagination-ordering-and-searching-with-searchlogic
10
+ #
11
+ # === How it's organized
12
+ #
13
+ # If we break it down, you can do 4 different things with your data in your view:
14
+ #
15
+ # 1. Order your data by a single column or an array of columns
16
+ # 2. Descend or ascend your data
17
+ # 3. Change how many items are on each page
18
+ # 4. Paginate through your data
19
+ #
20
+ # Each one of these actions comes with 3 different types of helpers:
21
+ #
22
+ # 1. Link - A single link for a single value. Requires that you pass a value as the first parameter.
23
+ # 2. Links - A group of single links.
24
+ # 3. Select - A select with choices that perform an action once selected. Basically the same thing as a group of links, but just as a select form element
25
+ # 4. Remote - lets you prefix any of these helpers with "remote_" and it will use the built in rails ajax helpers. I highly recommend unobstrusive javascript though, using jQuery.
26
+ #
27
+ # === Examples
28
+ #
29
+ # Sometimes the best way to explain something is with some examples. Let's pretend we are performing these actions on a User model. Check it out:
30
+ #
31
+ # order_by_link(:name)
32
+ # => produces a single link that when clicked will order by the name column, and each time its clicked alternated between "ASC" and "DESC"
33
+ #
34
+ # order_by_links
35
+ # => produces a group of links for all of the columns in your users table, each link is basically order_by_link(column.name)
36
+ #
37
+ # order_by_select
38
+ # => produces a select form element with all of the user's columns as choices, when the value is change (onchange) it will act as if they clicked a link.
39
+ # => This is just order_by_links as a select form element, nothing fancy
40
+ #
41
+ # What about paginating? I got you covered:
42
+ #
43
+ # page_link(2)
44
+ # => creates a link to page 2
45
+ #
46
+ # page_links
47
+ # => creates a group of links for pages, similar to a flickr style of pagination
48
+ #
49
+ # page_select
50
+ # => creates a drop down instead of a group of links. The user can select the page in the drop down and it will be as if they clicked a link for that page.
51
+ #
52
+ # You can apply the _link, _links, or _select to any of the following: order_by, order_as, per_page, page. You have your choice on how you want to set up the interface. For more information and options on these individual
53
+ # helpers check out their source files. Look at the sub modules under this one (Ex: Searchlogic::Helpers::ControlTypes::Select)
54
+ module ControlTypes
55
+ # = Link Control Types
56
+ #
57
+ # These helpers make ordering and paginating your data a breeze in your view. They only produce links.
58
+ module Link
59
+ # Creates a link for ordering data by a column or columns
60
+ #
61
+ # === Example uses for a User class that has many orders
62
+ #
63
+ # order_by_link(:first_name)
64
+ # order_by_link([:first_name, :last_name])
65
+ # order_by_link({:orders => :total})
66
+ # order_by_link([{:orders => :total}, :first_name])
67
+ # order_by_link(:id, :text => "Order Number", :html => {:class => "order_number"})
68
+ #
69
+ # What's nifty about this is that the value gets "serialized", if it is not a string or a symbol, so that it can be passed via a param in the url. Searchlogic will automatically try to "unserializes" this value and use it. This allows you
70
+ # to pass complex objects besides strings and symbols, such as arrays and hashes. All of the hard work is done for you.
71
+ #
72
+ # Another thing to keep in mind is that this will alternate between "asc" and "desc" each time it is clicked.
73
+ #
74
+ # === Options
75
+ # * <tt>:text</tt> -- default: column_name.to_s.humanize, text for the link
76
+ # * <tt>:desc_indicator</tt> -- default: &nbsp;&#9660;, the indicator that this column is descending
77
+ # * <tt>:asc_indicator</tt> -- default: &nbsp;&#9650;, the indicator that this column is ascending
78
+ # * <tt>:html</tt> -- html arrtributes for the <a> tag.
79
+ #
80
+ # === Advanced Options
81
+ # * <tt>:params_scope</tt> -- default: :search, this is the scope in which your search params will be preserved (params[:search]). If you don't want a scope and want your options to be at base level in params such as params[:page], params[:per_page], etc, then set this to nil.
82
+ # * <tt>:search_obj</tt> -- default: @#{params_scope}, this is your search object, everything revolves around this. It will try to infer the name from your params_scope. If your params_scope is :search it will try to get @search, etc. If it can not be inferred by this, you need to pass the object itself.
83
+ # * <tt>:params</tt> -- default: nil, Additional params to add to the url, must be a hash
84
+ # * <tt>:exclude_params</tt> -- default: nil, params you want to exclude. This is nifty because it does a "deep delete". So you can pass {:param1 => {:param2 => :param3}} and it will make sure param3 does not get included. param1 and param2 will not be touched. This also accepts an array or just a symbol or string.
85
+ # * <tt>:search_params</tt> -- default: nil, Additional search params to add to the url, must be a hash. Adds the options into the :params_scope.
86
+ # * <tt>:exclude_search_params</tt> -- default: nil, Same as :exclude_params but for the :search_params.
87
+ def order_by_link(order_by, options = {})
88
+ add_order_by_link_defaults!(order_by, options)
89
+ html = searchlogic_state(options)
90
+
91
+ if !options[:is_remote]
92
+ html += link_to(options[:text], options[:url], options[:html])
93
+ else
94
+ html += link_to_remote(options[:text], options[:remote].merge(:url => options[:url]), options[:html])
95
+ end
96
+
97
+ html
98
+ end
99
+
100
+ # Creates a link for ascending or descending data.
101
+ #
102
+ # === Example uses
103
+ #
104
+ # order_as_link("asc")
105
+ # order_as_link("desc")
106
+ # order_as_link("asc", :text => "Ascending", :html => {:class => "ascending"})
107
+ #
108
+ # === Options
109
+ # * <tt>:text</tt> -- default: column_name.to_s.humanize, text for the link
110
+ # * <tt>:html</tt> -- html arrtributes for the <a> tag.
111
+ #
112
+ # === Advanced Options
113
+ # * <tt>:params_scope</tt> -- default: :search, this is the scope in which your search params will be preserved (params[:search]). If you don't want a scope and want your options to be at base level in params such as params[:page], params[:per_page], etc, then set this to nil.
114
+ # * <tt>:search_obj</tt> -- default: @#{params_scope}, this is your search object, everything revolves around this. It will try to infer the name from your params_scope. If your params_scope is :search it will try to get @search, etc. If it can not be inferred by this, you need to pass the object itself.
115
+ # * <tt>:params</tt> -- default: nil, Additional params to add to the url, must be a hash
116
+ # * <tt>:exclude_params</tt> -- default: nil, params you want to exclude. This is nifty because it does a "deep delete". So you can pass {:param1 => {:param2 => :param3}} and it will make sure param3 does not get included. param1 and param2 will not be touched. This also accepts an array or just a symbol or string.
117
+ # * <tt>:search_params</tt> -- default: nil, Additional search params to add to the url, must be a hash. Adds the options into the :params_scope.
118
+ # * <tt>:exclude_search_params</tt> -- default: nil, Same as :exclude_params but for the :search_params.
119
+ def order_as_link(order_as, options = {})
120
+ add_order_as_link_defaults!(order_as, options)
121
+ html = searchlogic_state(options)
122
+
123
+ if !options[:is_remote]
124
+ html += link_to(options[:text], options[:url], options[:html])
125
+ else
126
+ html += link_to_remote(options[:text], options[:remote].merge(:url => options[:url]), options[:html])
127
+ end
128
+
129
+ html
130
+ end
131
+
132
+ # This is similar to order_by_link but with a small difference. The best way to explain priority ordering is with an example. Let's say you wanted to list products on a page. You have "featured" products
133
+ # that you want to show up first, no matter what. This is what this is all about. It makes ordering by featured products a priority, then searching by price, quantity, etc. is the same as it has always been.
134
+ #
135
+ # The difference between order_by_link and priority_order_by_link is that priority_order_by_link it just a switch. Turn it on or turn it off. You don't neccessarily want to flip between ASC and DESC. If you do
136
+ # then you should just incorporate this into your regular order_by, like: order_by_link [:featured, :price]
137
+ #
138
+ # === Example uses for a User class that has many orders
139
+ #
140
+ # priority_order_by_link(:featured, "DESC")
141
+ # order_by_link([:featured, :created_at], "ASC")
142
+ # order_by_link({:orders => :featured}, "ASC")
143
+ # order_by_link([{:orders => :featured}, :featured], "ASC")
144
+ # order_by_link(:featured, "ASC", :text => "Featured", :html => {:class => "featured_link"})
145
+ #
146
+ # === Options
147
+ # * <tt>:activate_text</tt> -- default: "Show #{column_name.to_s.humanize} first"
148
+ # * <tt>:deactivate_text</tt> -- default: "Don't show #{column_name.to_s.humanize} first", text for the link, text for the link
149
+ # * <tt>:column_name</tt> -- default: column_name.to_s.humanize, automatically inferred by what you are ordering by and is added into the active_text and deactive_text strings.
150
+ # * <tt>:text</tt> -- default: :activate_text or :deactivate_text depending on if its active or not, Overwriting this will make this text stay the same, no matter way. A good alternative would be "Toggle featured first"
151
+ # * <tt>:html</tt> -- html arrtributes for the <a> tag.
152
+ #
153
+ # === Advanced Options
154
+ # * <tt>:params_scope</tt> -- default: :search, this is the scope in which your search params will be preserved (params[:search]). If you don't want a scope and want your options to be at base level in params such as params[:page], params[:per_page], etc, then set this to nil.
155
+ # * <tt>:search_obj</tt> -- default: @#{params_scope}, this is your search object, everything revolves around this. It will try to infer the name from your params_scope. If your params_scope is :search it will try to get @search, etc. If it can not be inferred by this, you need to pass the object itself.
156
+ # * <tt>:params</tt> -- default: nil, Additional params to add to the url, must be a hash
157
+ # * <tt>:exclude_params</tt> -- default: nil, params you want to exclude. This is nifty because it does a "deep delete". So you can pass {:param1 => {:param2 => :param3}} and it will make sure param3 does not get included. param1 and param2 will not be touched. This also accepts an array or just a symbol or string.
158
+ # * <tt>:search_params</tt> -- default: nil, Additional search params to add to the url, must be a hash. Adds the options into the :params_scope.
159
+ # * <tt>:exclude_search_params</tt> -- default: nil, Same as :exclude_params but for the :search_params.
160
+ def priority_order_by_link(priority_order_by, priority_order_as, options = {})
161
+ add_priority_order_by_link_defaults!(priority_order_by, priority_order_as, options)
162
+ html = searchlogic_state(options)
163
+
164
+ if !options[:is_remote]
165
+ html += link_to(options[:text], options[:url], options[:html])
166
+ else
167
+ html += link_to_remote(options[:text], options[:remote].merge(:url => options[:url]), options[:html])
168
+ end
169
+
170
+ html
171
+ end
172
+
173
+ # Creates a link for limiting how many items are on each page
174
+ #
175
+ # === Example uses
176
+ #
177
+ # per_page_link(200)
178
+ # per_page_link(nil) # => Show all
179
+ # per_page_link(nil, :text => "All", :html => {:class => "show_all"})
180
+ #
181
+ # As you can see above, passing nil means "show all" and the text will automatically revert to "show all"
182
+ #
183
+ # === Options
184
+ # * <tt>:html</tt> -- html arrtributes for the <a> tag.
185
+ #
186
+ # === Advanced Options
187
+ # * <tt>:params_scope</tt> -- default: :search, this is the scope in which your search params will be preserved (params[:search]). If you don't want a scope and want your options to be at base level in params such as params[:page], params[:per_page], etc, then set this to nil.
188
+ # * <tt>:search_obj</tt> -- default: @#{params_scope}, this is your search object, everything revolves around this. It will try to infer the name from your params_scope. If your params_scope is :search it will try to get @search, etc. If it can not be inferred by this, you need to pass the object itself.
189
+ # * <tt>:params</tt> -- default: nil, Additional params to add to the url, must be a hash
190
+ # * <tt>:exclude_params</tt> -- default: nil, params you want to exclude. This is nifty because it does a "deep delete". So you can pass {:param1 => {:param2 => :param3}} and it will make sure param3 does not get included. param1 and param2 will not be touched. This also accepts an array or just a symbol or string.
191
+ # * <tt>:search_params</tt> -- default: nil, Additional search params to add to the url, must be a hash. Adds the options into the :params_scope.
192
+ # * <tt>:exclude_search_params</tt> -- default: nil, Same as :exclude_params but for the :search_params.
193
+ def per_page_link(per_page, options = {})
194
+ add_per_page_link_defaults!(per_page, options)
195
+ html = searchlogic_state(options)
196
+
197
+ if !options[:is_remote]
198
+ html += link_to(options[:text], options[:url], options[:html])
199
+ else
200
+ html += link_to_remote(options[:text], options[:remote].merge(:url => options[:url]), options[:html])
201
+ end
202
+
203
+ html
204
+ end
205
+
206
+ # Creates a link for changing to a sepcific page of your data
207
+ #
208
+ # === Example uses
209
+ #
210
+ # page_link(2)
211
+ # page_link(1)
212
+ # page_link(5, :text => "Fifth page", :html => {:class => "fifth_page"})
213
+ #
214
+ # === Options
215
+ # * <tt>:text</tt> -- default: column_name.to_s.humanize, text for the link
216
+ # * <tt>:html</tt> -- html arrtributes for the <a> tag.
217
+ #
218
+ # === Advanced Options
219
+ # * <tt>:params_scope</tt> -- default: :search, this is the scope in which your search params will be preserved (params[:search]). If you don't want a scope and want your options to be at base level in params such as params[:page], params[:per_page], etc, then set this to nil.
220
+ # * <tt>:search_obj</tt> -- default: @#{params_scope}, this is your search object, everything revolves around this. It will try to infer the name from your params_scope. If your params_scope is :search it will try to get @search, etc. If it can not be inferred by this, you need to pass the object itself.
221
+ # * <tt>:params</tt> -- default: nil, Additional params to add to the url, must be a hash
222
+ # * <tt>:exclude_params</tt> -- default: nil, params you want to exclude. This is nifty because it does a "deep delete". So you can pass {:param1 => {:param2 => :param3}} and it will make sure param3 does not get included. param1 and param2 will not be touched. This also accepts an array or just a symbol or string.
223
+ # * <tt>:search_params</tt> -- default: nil, Additional search params to add to the url, must be a hash. Adds the options into the :params_scope.
224
+ # * <tt>:exclude_search_params</tt> -- default: nil, Same as :exclude_params but for the :search_params.
225
+ def page_link(page, options = {})
226
+ add_page_link_defaults!(page, options)
227
+ html = searchlogic_state(options)
228
+
229
+ if !options[:is_remote]
230
+ html += link_to(options[:text], options[:url], options[:html])
231
+ else
232
+ html += link_to_remote(options[:text], options[:remote].merge(:url => options[:url]), options[:html])
233
+ end
234
+
235
+ html
236
+ end
237
+
238
+ private
239
+ def add_order_by_link_defaults!(order_by, options = {})
240
+ add_searchlogic_control_defaults!(options)
241
+ searchlogic_add_class!(options[:html], Config.helpers.order_by_link_class_name)
242
+ ordering_by_this = searchlogic_ordering_by?(order_by, options)
243
+ searchlogic_add_class!(options[:html], "ordering") if ordering_by_this
244
+ options[:text] ||= determine_order_by_text(order_by)
245
+ options[:asc_indicator] ||= Config.helpers.order_by_link_asc_indicator
246
+ options[:desc_indicator] ||= Config.helpers.order_by_link_desc_indicator
247
+ options[:text] += options[:search_obj].desc? ? options[:desc_indicator] : options[:asc_indicator] if ordering_by_this
248
+ options[:url] = searchlogic_params(options.merge(:search_params => {:order_by => order_by}))
249
+ options
250
+ end
251
+
252
+ def add_order_as_link_defaults!(order_as, options = {})
253
+ add_searchlogic_control_defaults!(options)
254
+ searchlogic_add_class!(options[:html], Config.helpers.order_as_link_class_name)
255
+ options[:text] ||= order_as.to_s
256
+ options[:url] = searchlogic_params(options.merge(:search_params => {:order_as => order_as}))
257
+ options
258
+ end
259
+
260
+ def add_priority_order_by_link_defaults!(priority_order_by, priority_order_as, options = {})
261
+ add_searchlogic_control_defaults!(options)
262
+ searchlogic_add_class!(options[:html], Config.helpers.priority_order_by_link_class_name)
263
+ options[:column_name] ||= determine_order_by_text(priority_order_by).downcase
264
+ options[:activate_text] ||= Config.helpers.priority_order_by_link_activate_text % options[:column_name]
265
+ options[:deactivate_text] ||= Config.helpers.priority_order_by_link_deactivate_text % options[:column_name]
266
+ active = deep_stringify(options[:search_obj].priority_order_by) == deep_stringify(priority_order_by) && options[:search_obj].priority_order_as == priority_order_as
267
+ options[:text] ||= active ? options[:deactivate_text] : options[:activate_text]
268
+ if active
269
+ options.merge!(:search_params => {:priority_order_by => nil, :priority_order_as => nil})
270
+ else
271
+ options.merge!(:search_params => {:priority_order_by => priority_order_by, :priority_order_as => priority_order_as})
272
+ end
273
+ options[:url] = searchlogic_params(options)
274
+ options
275
+ end
276
+
277
+ def add_per_page_link_defaults!(per_page, options = {})
278
+ add_searchlogic_control_defaults!(options)
279
+ searchlogic_add_class!(options[:html], Config.helpers.per_page_link_class_name)
280
+ options[:text] ||= per_page.to_s
281
+ options[:url] = searchlogic_params(options.merge(:search_params => {:per_page => per_page}))
282
+ options
283
+ end
284
+
285
+ def add_page_link_defaults!(page, options = {})
286
+ add_searchlogic_control_defaults!(options)
287
+ searchlogic_add_class!(options[:html], Config.helpers.page_link_class_name)
288
+ options[:text] ||= page.to_s
289
+ options[:url] = searchlogic_params(options.merge(:search_params => {:page => page}))
290
+ options
291
+ end
292
+
293
+ def determine_order_by_text(column_name, relationship_name = nil)
294
+ case column_name
295
+ when String, Symbol
296
+ relationship_name.blank? ? column_name.to_s.titleize : "#{relationship_name.to_s.titleize} #{column_name.to_s.titleize}"
297
+ when Array
298
+ determine_order_by_text(column_name.first)
299
+ when Hash
300
+ k = column_name.keys.first
301
+ v = column_name.values.first
302
+ determine_order_by_text(v, k)
303
+ end
304
+ end
305
+ end
306
+ end
307
+ end
308
+ end
309
+
310
+ ActionController::Base.helper Searchlogic::Helpers::ControlTypes::Link if defined?(ActionController)
@@ -0,0 +1,241 @@
1
+ module Searchlogic
2
+ module Helpers
3
+ module ControlTypes
4
+ module Links
5
+ # Creates a group of links that order the data by a column or columns. All that this does is loop through the :choices option and call order_by_link and then glue it all together.
6
+ #
7
+ # === Examples
8
+ #
9
+ # order_by_links
10
+ # order_by_links(:choices => [:name, {:orders => {:line_items => :total}}, :email])
11
+ #
12
+ # === Options
13
+ #
14
+ # Please look at order_by_link. All options there are applicable here and are passed onto each option.
15
+ #
16
+ # * <tt>:choices</tt> -- default: the models column names, the choices to loop through when calling order_by_link
17
+ def order_by_links(options = {})
18
+ add_order_by_links_defaults!(options)
19
+ links_options = options.deep_dup
20
+ links_options.delete(:choices)
21
+ html = ""
22
+ options[:choices].each do |choice|
23
+ link_options = links_option.deep_dup
24
+ text, value = option_text_and_value(choice)
25
+ link_options[:text] ||= text
26
+ html += order_by_link(value, link_options)
27
+ end
28
+ html
29
+ end
30
+
31
+ # Creates a group of links that ascend or descend the data. All that this does is loop through the :choices option and call order_as_link and then glue it all together.
32
+ #
33
+ # === Examples
34
+ #
35
+ # order_as_links
36
+ # order_as_links(:choices => [:ascending, :descending])
37
+ #
38
+ # === Options
39
+ #
40
+ # Please look at order_as_link. All options there are applicable here and are passed onto each option.
41
+ #
42
+ # * <tt>:choices</tt> -- default: ["asc", "desc"], the choices to loop through when calling order_as_link
43
+ def order_as_links(options = {})
44
+ add_order_as_links_defaults!(options)
45
+ links_options = options.deep_dup
46
+ links_options.delete(:choices)
47
+ html = ""
48
+ options[:choices].each do |choice|
49
+ link_options = links_option.deep_dup
50
+ text, value = option_text_and_value(choice)
51
+ link_options[:text] ||= text
52
+ html += order_as_link(value, link_options)
53
+ end
54
+ html
55
+ end
56
+
57
+ # Creates a group of links that limit how many items are on each page. All that this does is loop through the :choices option and call per_page_link and then glue it all together.
58
+ #
59
+ # === Examples
60
+ #
61
+ # per_page_links
62
+ # per_page_links(:choices => [25, 50, nil])
63
+ #
64
+ # === Options
65
+ #
66
+ # Please look at per_page_link. All options there are applicable here and are passed onto each option.
67
+ #
68
+ # * <tt>:choices</tt> -- default: [["10 per page", 10], ["25 per page", 25], ["50 per page", 50], ["100 per page", 100], ["150 per page", 150], ["200 per page", 200], ["Show all", nil]]
69
+ def per_page_links(options = {})
70
+ add_per_page_links_defaults!(options)
71
+ links_options = options.deep_dup
72
+ links_options.delete(:choices)
73
+ html = ""
74
+ options[:choices].each do |choice|
75
+ link_options = links_options.deep_dup
76
+ text, value = option_text_and_value(choice)
77
+ link_options[:text] ||= text
78
+ searchlogic_add_class!(link_options[:html], Config.helpers.order_by_links_ordering_by_class_name) if link_options[:search_obj].per_page == value
79
+ html += per_page_link(value, link_options)
80
+ end
81
+ html
82
+ end
83
+
84
+ # Creates a group of links that paginate through the data. Kind of like a flickr page navigation. This one has some nifty options.
85
+ #
86
+ # === Examples
87
+ #
88
+ # page_links
89
+ # page_links(:first => "<< First", :last => "Last >>")
90
+ #
91
+ # === Classes and tags
92
+ #
93
+ # If the user is on the current page they will get a <span> tag, not an <a> tag. If they are on the first page the "first" and "prev" options will be a <span> also. The same goes
94
+ # for "next" and "last" if the user is on the last page. Other than that each element will come with a CSS class so you can style it to your liking. Somtimes the easiest way to understand this
95
+ # Is to either look at the example (linked in the README) or try it out and view the HTML source. It's pretty simple, but here are the explanations:
96
+ #
97
+ # * <tt>page</tt> - This is in *every* element, span or a.
98
+ # * <tt>first_page</tt> - This is for the "first page" element only.
99
+ # * <tt>prev_page</tt> - This is for the "prev page" element only.
100
+ # * <tt>current_page</tt> - This is for the current page element
101
+ # * <tt>next_page</tt> - This is for the "next page" element only.
102
+ # * <tt>last_page</tt> - This is for the "last page" element only.
103
+ # * <tt>disabled_page</tt> - Any element that is a span instead of an a tag.
104
+ #
105
+ # === Options
106
+ #
107
+ # Please look at per_page_link. All options there are applicable here and are passed onto each option.
108
+ #
109
+ # * <tt>:inner_spread</tt> -- default: 3, set to nil to show all pages, set 0 to show no page links. This represents how many choices available on each side of the current page
110
+ # * <tt>:outer_spread</tt> -- default: 1, set to nil to disable, set to 0 show no outer spread but the separator will still be present. This represents how many choices are in the "outer" spread.
111
+ # * <tt>:prev</tt> -- default: "< Prev", set to nil to omit. This is an extra link on the left side of the page links that will go to the previous page
112
+ # * <tt>:next</tt> -- default: "Next >", set to nil to omit. This is an extra link on the right side of the page links that will go to the next page
113
+ # * <tt>:first</tt> -- default: nil, set to nil to omit. This is an extra link on thefar left side of the page links that will go to the first page
114
+ # * <tt>:last</tt> -- default: nil, set to nil to omit. This is an extra link on the far right side of the page links that will go to the last page
115
+ def page_links(options = {})
116
+ add_page_links_defaults!(options)
117
+ return if options[:last_page] <= 1
118
+
119
+ inner_spread_start = inner_spread_end = lower_gap = lower_outer_spread_start = lower_outer_spread_end = upper_gap = upper_outer_spread_start = upper_outer_spread_end = 0
120
+ if !options[:inner_spread].blank?
121
+ inner_spread_start = options[:current_page] - options[:inner_spread]
122
+ inner_spread_start = options[:first_page] if inner_spread_start < options[:first_page]
123
+ inner_spread_end = options[:current_page] + options[:inner_spread]
124
+ inner_spread_end = options[:last_page] if inner_spread_end > options[:last_page]
125
+
126
+ if !options[:outer_spread].blank?
127
+ lower_gap = inner_spread_start - options[:first_page]
128
+ if lower_gap > 0
129
+ lower_outer_spread_start = options[:first_page]
130
+ lower_outer_spread_end = options[:outer_spread] > lower_gap ? lower_gap : options[:outer_spread]
131
+ end
132
+
133
+ upper_gap = options[:last_page] - inner_spread_end
134
+ if upper_gap > 0
135
+ upper_outer_spread_start = options[:last_page] - (options[:outer_spread] > upper_gap ? upper_gap : options[:outer_spread]) + 1
136
+ upper_outer_spread_end = options[:last_page]
137
+ end
138
+ end
139
+ else
140
+ inner_spread_start = options[:first_page]
141
+ inner_spread_end = options[:last_page]
142
+ end
143
+
144
+ html = ""
145
+ html += span_or_page_link(:first, options.deep_dup, options[:current_page] == options[:first_page]) if options[:first]
146
+ html += span_or_page_link(:prev, options.deep_dup, options[:current_page] == options[:first_page]) if options[:prev]
147
+
148
+ if lower_gap > 0
149
+ (lower_outer_spread_start..lower_outer_spread_end).each { |page| html += span_or_page_link(page, options.deep_dup, false) }
150
+ html += content_tag(:span, "&hellip;", options[:html]) if (inner_spread_start - lower_outer_spread_end) > 1
151
+ end
152
+
153
+ (inner_spread_start..inner_spread_end).each { |page| html += span_or_page_link(page, options.deep_dup, page == options[:current_page]) }
154
+
155
+ if upper_gap > 0
156
+ html += content_tag(:span, "&hellip;", options[:html]) if (upper_outer_spread_start - inner_spread_end) > 1
157
+ (upper_outer_spread_start..upper_outer_spread_end).each { |page| html += span_or_page_link(page, options.deep_dup, false) }
158
+ end
159
+
160
+ html += span_or_page_link(:next, options.deep_dup, options[:current_page] == options[:last_page]) if options[:next]
161
+ html += span_or_page_link(:last, options.deep_dup, options[:current_page] == options[:last_page]) if options[:last]
162
+ html
163
+ end
164
+
165
+ private
166
+ def add_order_by_links_defaults!(options)
167
+ add_searchlogic_control_defaults!(options)
168
+ options[:choices] ||= options[:search_obj].klass.column_names.map(&:humanize)
169
+ options
170
+ end
171
+
172
+ def add_order_as_links_defaults!(options)
173
+ add_searchlogic_control_defaults!(options)
174
+ options[:choices] = [:asc, :desc]
175
+ options
176
+ end
177
+
178
+ def add_per_page_links_defaults!(options)
179
+ add_searchlogic_control_defaults!(options)
180
+ options[:choices] ||= Config.helpers.per_page_links_choices.dup
181
+ if !options[:search_obj].per_page.blank? && !choices_include?(options[:choices], options[:search_obj].per_page)
182
+ options[:choices] << ["#{options[:search_obj].per_page} per page", options[:search_obj].per_page]
183
+ options[:choices].sort! do |a, b|
184
+ a_value = (a.is_a?(Array) ? a.last : a).to_i
185
+ return -1 if a_value == 0
186
+ b_value = (b.is_a?(Array) ? b.last : b).to_i
187
+ a_value <=> b_value
188
+ end
189
+ end
190
+ options
191
+ end
192
+
193
+ def choices_include?(choices, includes)
194
+ choices.each do |choice|
195
+ value = choice.is_a?(Array) ? choice.last : choice
196
+ return true if value == includes
197
+ end
198
+ false
199
+ end
200
+
201
+ def add_page_links_defaults!(options)
202
+ add_searchlogic_control_defaults!(options)
203
+ options[:first_page] ||= 1
204
+ options[:last_page] ||= options[:search_obj].page_count
205
+ options[:current_page] ||= options[:search_obj].page
206
+ options[:inner_spread] = Config.helpers.page_links_inner_spread unless options.has_key?(:inner_spread)
207
+ options[:outer_spread] = Config.helpers.page_links_outer_spread unless options.has_key?(:outer_spread)
208
+ options[:prev] = Config.helpers.page_links_prev unless options.has_key?(:prev)
209
+ options[:next] = Config.helpers.page_links_next unless options.has_key?(:next)
210
+ options[:first] = Config.helpers.page_links_first unless options.has_key?(:first)
211
+ options[:last] = Config.helpers.page_links_last unless options.has_key?(:last)
212
+ options
213
+ end
214
+
215
+ def span_or_page_link(name, options, span)
216
+ text = ""
217
+ page = 0
218
+ case name
219
+ when Fixnum
220
+ text = name
221
+ page = name
222
+ searchlogic_add_class!(options[:html], Config.helpers.page_links_current_page_class_name) if span
223
+ else
224
+ text = options[name]
225
+ page = options[:search_obj].send("#{name}_page")
226
+ searchlogic_add_class!(options[:html], Config.helpers.send("page_links_#{name}_page_class_name"))
227
+ end
228
+
229
+ if span
230
+ searchlogic_add_class!(options[:html], Config.helpers.page_links_disabled_class_name)
231
+ searchlogic_add_class!(options[:html], Config.helpers.page_link_class_name)
232
+ end
233
+ options[:text] = text
234
+ span ? content_tag(:span, text, options[:html]) : page_link(page, options)
235
+ end
236
+ end
237
+ end
238
+ end
239
+ end
240
+
241
+ ActionController::Base.helper Searchlogic::Helpers::ControlTypes::Links if defined?(ActionController)