leshill-will_paginate 2.3.11

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 (44) hide show
  1. data/CHANGELOG.rdoc +110 -0
  2. data/LICENSE +18 -0
  3. data/README.rdoc +111 -0
  4. data/Rakefile +53 -0
  5. data/examples/apple-circle.gif +0 -0
  6. data/examples/index.haml +69 -0
  7. data/examples/index.html +92 -0
  8. data/examples/pagination.css +90 -0
  9. data/examples/pagination.sass +91 -0
  10. data/init.rb +1 -0
  11. data/lib/will_paginate.rb +90 -0
  12. data/lib/will_paginate/array.rb +16 -0
  13. data/lib/will_paginate/collection.rb +146 -0
  14. data/lib/will_paginate/core_ext.rb +43 -0
  15. data/lib/will_paginate/finder.rb +264 -0
  16. data/lib/will_paginate/named_scope.rb +170 -0
  17. data/lib/will_paginate/named_scope_patch.rb +37 -0
  18. data/lib/will_paginate/version.rb +9 -0
  19. data/lib/will_paginate/view_helpers.rb +432 -0
  20. data/test/boot.rb +21 -0
  21. data/test/collection_test.rb +143 -0
  22. data/test/console +8 -0
  23. data/test/database.yml +22 -0
  24. data/test/finder_test.rb +473 -0
  25. data/test/fixtures/admin.rb +3 -0
  26. data/test/fixtures/developer.rb +14 -0
  27. data/test/fixtures/developers_projects.yml +13 -0
  28. data/test/fixtures/project.rb +15 -0
  29. data/test/fixtures/projects.yml +6 -0
  30. data/test/fixtures/replies.yml +29 -0
  31. data/test/fixtures/reply.rb +7 -0
  32. data/test/fixtures/schema.rb +38 -0
  33. data/test/fixtures/topic.rb +10 -0
  34. data/test/fixtures/topics.yml +30 -0
  35. data/test/fixtures/user.rb +2 -0
  36. data/test/fixtures/users.yml +35 -0
  37. data/test/helper.rb +40 -0
  38. data/test/lib/activerecord_test_case.rb +43 -0
  39. data/test/lib/activerecord_test_connector.rb +75 -0
  40. data/test/lib/load_fixtures.rb +11 -0
  41. data/test/lib/view_test_process.rb +180 -0
  42. data/test/tasks.rake +59 -0
  43. data/test/view_test.rb +405 -0
  44. metadata +127 -0
@@ -0,0 +1,9 @@
1
+ module WillPaginate
2
+ module VERSION
3
+ MAJOR = 2
4
+ MINOR = 3
5
+ TINY = 11
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,432 @@
1
+ require 'will_paginate/core_ext'
2
+
3
+ module WillPaginate
4
+ # = Will Paginate view helpers
5
+ #
6
+ # The main view helper, #will_paginate, renders
7
+ # pagination links for the given collection. The helper itself is lightweight
8
+ # and serves only as a wrapper around LinkRenderer instantiation; the
9
+ # renderer then does all the hard work of generating the HTML.
10
+ #
11
+ # == Global options for helpers
12
+ #
13
+ # Options for pagination helpers are optional and get their default values from the
14
+ # <tt>WillPaginate::ViewHelpers.pagination_options</tt> hash. You can write to this hash to
15
+ # override default options on the global level:
16
+ #
17
+ # WillPaginate::ViewHelpers.pagination_options[:previous_label] = 'Previous page'
18
+ #
19
+ # By putting this into "config/initializers/will_paginate.rb" (or simply environment.rb in
20
+ # older versions of Rails) you can easily translate link texts to previous
21
+ # and next pages, as well as override some other defaults to your liking.
22
+ module ViewHelpers
23
+ # default options that can be overridden on the global level
24
+ @@pagination_options = {
25
+ :class => 'pagination',
26
+ :previous_label => '&laquo; Previous',
27
+ :next_label => 'Next &raquo;',
28
+ :inner_window => 4, # links around the current page
29
+ :outer_window => 1, # links around beginning and end
30
+ :separator => ' ', # single space is friendly to spiders and non-graphic browsers
31
+ :param_name => :page,
32
+ :params => nil,
33
+ :renderer => 'WillPaginate::LinkRenderer',
34
+ :page_links => true,
35
+ :container => true,
36
+ :semantic => false,
37
+ :semantic_container => :ul
38
+ }
39
+ mattr_reader :pagination_options
40
+
41
+ # Renders Digg/Flickr-style pagination for a WillPaginate::Collection
42
+ # object. Nil is returned if there is only one page in total; no point in
43
+ # rendering the pagination in that case...
44
+ #
45
+ # ==== Options
46
+ # Display options:
47
+ # * <tt>:previous_label</tt> -- default: "« Previous" (this parameter is called <tt>:prev_label</tt> in versions <b>2.3.2</b> and older!)
48
+ # * <tt>:next_label</tt> -- default: "Next »"
49
+ # * <tt>:page_links</tt> -- when false, only previous/next links are rendered (default: true)
50
+ # * <tt>:inner_window</tt> -- how many links are shown around the current page (default: 4)
51
+ # * <tt>:outer_window</tt> -- how many links are around the first and the last page (default: 1)
52
+ # * <tt>:separator</tt> -- string separator for page HTML elements (default: single space)
53
+ #
54
+ # HTML options:
55
+ # * <tt>:class</tt> -- CSS class name for the generated DIV (default: "pagination")
56
+ # * <tt>:container</tt> -- toggles rendering of the DIV container for pagination links, set to
57
+ # false only when you are rendering your own pagination markup (default: true)
58
+ # * <tt>:id</tt> -- HTML ID for the container (default: nil). Pass +true+ to have the ID
59
+ # automatically generated from the class name of objects in collection: for example, paginating
60
+ # ArticleComment models would yield an ID of "article_comments_pagination".
61
+ # * <tt>:semantic</tt> -- semantic pagination render, defaults to false
62
+ # * <tt>:semantic_container</tt> -- container for li elements, defaults to :ul
63
+ #
64
+ # Advanced options:
65
+ # * <tt>:param_name</tt> -- parameter name for page number in URLs (default: <tt>:page</tt>)
66
+ # * <tt>:params</tt> -- additional parameters when generating pagination links
67
+ # (eg. <tt>:controller => "foo", :action => nil</tt>)
68
+ # * <tt>:renderer</tt> -- class name, class or instance of a link renderer (default:
69
+ # <tt>WillPaginate::LinkRenderer</tt>)
70
+ #
71
+ # All options not recognized by will_paginate will become HTML attributes on the container
72
+ # element for pagination links (the DIV). For example:
73
+ #
74
+ # <%= will_paginate @posts, :style => 'font-size: small' %>
75
+ #
76
+ # ... will result in:
77
+ #
78
+ # <div class="pagination" style="font-size: small"> ... </div>
79
+ #
80
+ # ==== Using the helper without arguments
81
+ # If the helper is called without passing in the collection object, it will
82
+ # try to read from the instance variable inferred by the controller name.
83
+ # For example, calling +will_paginate+ while the current controller is
84
+ # PostsController will result in trying to read from the <tt>@posts</tt>
85
+ # variable. Example:
86
+ #
87
+ # <%= will_paginate :id => true %>
88
+ #
89
+ # ... will result in <tt>@post</tt> collection getting paginated:
90
+ #
91
+ # <div class="pagination" id="posts_pagination"> ... </div>
92
+ #
93
+ def will_paginate(collection = nil, options = {})
94
+ options, collection = collection, nil if collection.is_a? Hash
95
+ unless collection or !controller
96
+ collection_name = "@#{controller.controller_name}"
97
+ collection = instance_variable_get(collection_name)
98
+ raise ArgumentError, "The #{collection_name} variable appears to be empty. Did you " +
99
+ "forget to pass the collection object for will_paginate?" unless collection
100
+ end
101
+ # early exit if there is nothing to render
102
+ return nil unless WillPaginate::ViewHelpers.total_pages_for_collection(collection) > 1
103
+
104
+ options = options.symbolize_keys.reverse_merge WillPaginate::ViewHelpers.pagination_options
105
+ if options[:prev_label]
106
+ WillPaginate::Deprecation::warn(":prev_label view parameter is now :previous_label; the old name has been deprecated", caller)
107
+ options[:previous_label] = options.delete(:prev_label)
108
+ end
109
+
110
+ # semantic link rendering
111
+ if options[:semantic]
112
+ options[:renderer] = "WillPaginate::SemanticLinkRenderer"
113
+ options[:separator] = nil
114
+ end
115
+
116
+ # get the renderer instance
117
+ renderer = case options[:renderer]
118
+ when String
119
+ options[:renderer].to_s.constantize.new
120
+ when Class
121
+ options[:renderer].new
122
+ else
123
+ options[:renderer]
124
+ end
125
+ # render HTML for pagination
126
+ renderer.prepare collection, options, self
127
+ renderer.to_html
128
+ end
129
+
130
+ # Wrapper for rendering pagination links at both top and bottom of a block
131
+ # of content.
132
+ #
133
+ # <% paginated_section @posts do %>
134
+ # <ol id="posts">
135
+ # <% for post in @posts %>
136
+ # <li> ... </li>
137
+ # <% end %>
138
+ # </ol>
139
+ # <% end %>
140
+ #
141
+ # will result in:
142
+ #
143
+ # <div class="pagination"> ... </div>
144
+ # <ol id="posts">
145
+ # ...
146
+ # </ol>
147
+ # <div class="pagination"> ... </div>
148
+ #
149
+ # Arguments are passed to a <tt>will_paginate</tt> call, so the same options
150
+ # apply. Don't use the <tt>:id</tt> option; otherwise you'll finish with two
151
+ # blocks of pagination links sharing the same ID (which is invalid HTML).
152
+ def paginated_section(*args, &block)
153
+ pagination = will_paginate(*args).to_s
154
+
155
+ unless ActionView::Base.respond_to? :erb_variable
156
+ concat pagination
157
+ yield
158
+ concat pagination
159
+ else
160
+ content = pagination + capture(&block) + pagination
161
+ concat(content, block.binding)
162
+ end
163
+ end
164
+
165
+ # Renders a helpful message with numbers of displayed vs. total entries.
166
+ # You can use this as a blueprint for your own, similar helpers.
167
+ #
168
+ # <%= page_entries_info @posts %>
169
+ # #-> Displaying posts 6 - 10 of 26 in total
170
+ #
171
+ # By default, the message will use the humanized class name of objects
172
+ # in collection: for instance, "project types" for ProjectType models.
173
+ # Override this with the <tt>:entry_name</tt> parameter:
174
+ #
175
+ # <%= page_entries_info @posts, :entry_name => 'item' %>
176
+ # #-> Displaying items 6 - 10 of 26 in total
177
+ def page_entries_info(collection, options = {})
178
+ entry_name = options[:entry_name] ||
179
+ (collection.empty?? 'entry' : collection.first.class.name.underscore.sub('_', ' '))
180
+
181
+ if collection.total_pages < 2
182
+ case collection.size
183
+ when 0; "No #{entry_name.pluralize} found"
184
+ when 1; "Displaying <b>1</b> #{entry_name}"
185
+ else; "Displaying <b>all #{collection.size}</b> #{entry_name.pluralize}"
186
+ end
187
+ else
188
+ %{Displaying #{entry_name.pluralize} <b>%d&nbsp;-&nbsp;%d</b> of <b>%d</b> in total} % [
189
+ collection.offset + 1,
190
+ collection.offset + collection.length,
191
+ collection.total_entries
192
+ ]
193
+ end
194
+ end
195
+
196
+ def self.total_pages_for_collection(collection) #:nodoc:
197
+ if collection.respond_to?('page_count') and !collection.respond_to?('total_pages')
198
+ WillPaginate::Deprecation.warn %{
199
+ You are using a paginated collection of class #{collection.class.name}
200
+ which conforms to the old API of WillPaginate::Collection by using
201
+ `page_count`, while the current method name is `total_pages`. Please
202
+ upgrade yours or 3rd-party code that provides the paginated collection}, caller
203
+ class << collection
204
+ def total_pages; page_count; end
205
+ end
206
+ end
207
+ collection.total_pages
208
+ end
209
+ end
210
+
211
+ # This class does the heavy lifting of actually building the pagination
212
+ # links. It is used by the <tt>will_paginate</tt> helper internally.
213
+ class LinkRenderer
214
+
215
+ # The gap in page links is represented by:
216
+ #
217
+ # <span class="gap">&hellip;</span>
218
+ attr_accessor :gap_marker
219
+
220
+ def initialize
221
+ @gap_marker = '<span class="gap">&hellip;</span>'
222
+ end
223
+
224
+ # * +collection+ is a WillPaginate::Collection instance or any other object
225
+ # that conforms to that API
226
+ # * +options+ are forwarded from +will_paginate+ view helper
227
+ # * +template+ is the reference to the template being rendered
228
+ def prepare(collection, options, template)
229
+ @collection = collection
230
+ @options = options
231
+ @template = template
232
+
233
+ # reset values in case we're re-using this instance
234
+ @total_pages = @param_name = @url_string = nil
235
+ end
236
+
237
+ # Process it! This method returns the complete HTML string which contains
238
+ # pagination links. Feel free to subclass LinkRenderer and change this
239
+ # method as you see fit.
240
+ def to_html
241
+ links = @options[:page_links] ? windowed_links : []
242
+ # previous/next buttons
243
+ links.unshift page_link_or_span(@collection.previous_page, 'disabled prev_page', @options[:previous_label])
244
+ links.push page_link_or_span(@collection.next_page, 'disabled next_page', @options[:next_label])
245
+
246
+ html = links.join(@options[:separator])
247
+ container_tag = @options[:semantic] ? @options[:semantic_container] : :div
248
+ @options[:container] ? @template.content_tag(container_tag, html, html_attributes) : html
249
+ end
250
+
251
+ # Returns the subset of +options+ this instance was initialized with that
252
+ # represent HTML attributes for the container element of pagination links.
253
+ def html_attributes
254
+ return @html_attributes if @html_attributes
255
+ @html_attributes = @options.except *(WillPaginate::ViewHelpers.pagination_options.keys - [:class])
256
+ # pagination of Post models will have the ID of "posts_pagination"
257
+ if @options[:container] and @options[:id] === true
258
+ @html_attributes[:id] = @collection.first.class.name.underscore.pluralize + '_pagination'
259
+ end
260
+ @html_attributes
261
+ end
262
+
263
+ protected
264
+
265
+ # Collects link items for visible page numbers.
266
+ def windowed_links
267
+ prev = nil
268
+
269
+ visible_page_numbers.inject [] do |links, n|
270
+ # detect gaps:
271
+ links << gap_marker if prev and n > prev + 1
272
+ links << page_link_or_span(n, 'current')
273
+ prev = n
274
+ links
275
+ end
276
+ end
277
+
278
+ # Calculates visible page numbers using the <tt>:inner_window</tt> and
279
+ # <tt>:outer_window</tt> options.
280
+ def visible_page_numbers
281
+ inner_window, outer_window = @options[:inner_window].to_i, @options[:outer_window].to_i
282
+ window_from = current_page - inner_window
283
+ window_to = current_page + inner_window
284
+
285
+ # adjust lower or upper limit if other is out of bounds
286
+ if window_to > total_pages
287
+ window_from -= window_to - total_pages
288
+ window_to = total_pages
289
+ end
290
+ if window_from < 1
291
+ window_to += 1 - window_from
292
+ window_from = 1
293
+ window_to = total_pages if window_to > total_pages
294
+ end
295
+
296
+ visible = (1..total_pages).to_a
297
+ left_gap = (2 + outer_window)...window_from
298
+ right_gap = (window_to + 1)...(total_pages - outer_window)
299
+ visible -= left_gap.to_a if left_gap.last - left_gap.first > 1
300
+ visible -= right_gap.to_a if right_gap.last - right_gap.first > 1
301
+
302
+ visible
303
+ end
304
+
305
+ def page_link_or_span(page, span_class, text = nil)
306
+ text ||= page.to_s
307
+
308
+ if page and page != current_page
309
+ classnames = span_class && span_class.index(' ') && span_class.split(' ', 2).last
310
+ page_link page, text, :rel => rel_value(page), :class => classnames
311
+ else
312
+ page_span page, text, :class => span_class
313
+ end
314
+ end
315
+
316
+ def page_link(page, text, attributes = {})
317
+ @template.link_to text, url_for(page), attributes
318
+ end
319
+
320
+ def page_span(page, text, attributes = {})
321
+ @template.content_tag :span, text, attributes
322
+ end
323
+
324
+ # Returns URL params for +page_link_or_span+, taking the current GET params
325
+ # and <tt>:params</tt> option into account.
326
+ def url_for(page)
327
+ page_one = page == 1
328
+ unless @url_string and !page_one
329
+ @url_params = {}
330
+ # page links should preserve GET parameters
331
+ stringified_merge @url_params, @template.params if @template.request.get?
332
+ stringified_merge @url_params, @options[:params] if @options[:params]
333
+
334
+ if complex = param_name.index(/[^\w-]/)
335
+ page_param = parse_query_parameters("#{param_name}=#{page}")
336
+
337
+ stringified_merge @url_params, page_param
338
+ else
339
+ @url_params[param_name] = page_one ? 1 : 2
340
+ end
341
+
342
+ url = @template.url_for(@url_params)
343
+ return url if page_one
344
+
345
+ if complex
346
+ @url_string = url.sub(%r!((?:\?|&amp;)#{CGI.escape param_name}=)#{page}!, "\\1\0")
347
+ return url
348
+ else
349
+ @url_string = url
350
+ @url_params[param_name] = 3
351
+ @template.url_for(@url_params).split(//).each_with_index do |char, i|
352
+ if char == '3' and url[i, 1] == '2'
353
+ @url_string[i] = "\0"
354
+ break
355
+ end
356
+ end
357
+ end
358
+ end
359
+ # finally!
360
+ @url_string.sub "\0", page.to_s
361
+ end
362
+
363
+ private
364
+
365
+ def rel_value(page)
366
+ case page
367
+ when @collection.previous_page; 'prev' + (page == 1 ? ' start' : '')
368
+ when @collection.next_page; 'next'
369
+ when 1; 'start'
370
+ end
371
+ end
372
+
373
+ def current_page
374
+ @collection.current_page
375
+ end
376
+
377
+ def total_pages
378
+ @total_pages ||= WillPaginate::ViewHelpers.total_pages_for_collection(@collection)
379
+ end
380
+
381
+ def param_name
382
+ @param_name ||= @options[:param_name].to_s
383
+ end
384
+
385
+ # Recursively merge into target hash by using stringified keys from the other one
386
+ def stringified_merge(target, other)
387
+ other.each do |key, value|
388
+ key = key.to_s # this line is what it's all about!
389
+ existing = target[key]
390
+
391
+ if value.is_a?(Hash) and (existing.is_a?(Hash) or existing.nil?)
392
+ stringified_merge(existing || (target[key] = {}), value)
393
+ else
394
+ target[key] = value
395
+ end
396
+ end
397
+ end
398
+
399
+ def parse_query_parameters(params)
400
+ if defined? Rack::Utils
401
+ # For Rails > 2.3
402
+ Rack::Utils.parse_nested_query(params)
403
+ elsif defined?(ActionController::AbstractRequest)
404
+ ActionController::AbstractRequest.parse_query_parameters(params)
405
+ elsif defined?(ActionController::UrlEncodedPairParser)
406
+ # For Rails > 2.2
407
+ ActionController::UrlEncodedPairParser.parse_query_parameters(params)
408
+ elsif defined?(CGIMethods)
409
+ CGIMethods.parse_query_parameters(params)
410
+ else
411
+ raise "unsupported ActionPack version"
412
+ end
413
+ end
414
+ end
415
+
416
+ class SemanticLinkRenderer < LinkRenderer
417
+
418
+ def initialize
419
+ @gap_marker = '<li class="gap">&hellip;</li>'
420
+ end
421
+
422
+ protected
423
+ def page_link(page, text, attributes = {})
424
+ li_attributes = attributes.has_key?(:class) ? {:class => attributes[:class] } : {}
425
+ @template.content_tag(:li, @template.link_to(text, url_for(page), attributes), li_attributes)
426
+ end
427
+
428
+ def page_span(page, text, attributes = {})
429
+ @template.content_tag(:li, text, attributes)
430
+ end
431
+ end
432
+ end
@@ -0,0 +1,21 @@
1
+ plugin_root = File.join(File.dirname(__FILE__), '..')
2
+ version = ENV['RAILS_VERSION']
3
+ version = nil if version and version == ""
4
+
5
+ # first look for a symlink to a copy of the framework
6
+ if !version and framework_root = ["#{plugin_root}/rails", "#{plugin_root}/../../rails"].find { |p| File.directory? p }
7
+ puts "found framework root: #{framework_root}"
8
+ # this allows for a plugin to be tested outside of an app and without Rails gems
9
+ $:.unshift "#{framework_root}/activesupport/lib", "#{framework_root}/activerecord/lib", "#{framework_root}/actionpack/lib"
10
+ else
11
+ # simply use installed gems if available
12
+ puts "using Rails#{version ? ' ' + version : nil} gems"
13
+ require 'rubygems'
14
+
15
+ if version
16
+ gem 'rails', version
17
+ else
18
+ gem 'actionpack'
19
+ gem 'activerecord'
20
+ end
21
+ end