hobo_will_paginate 2.1.0

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 (55) hide show
  1. checksums.yaml +15 -0
  2. data/LICENSE +18 -0
  3. data/README.md +61 -0
  4. data/Rakefile +25 -0
  5. data/lib/will_paginate.rb +25 -0
  6. data/lib/will_paginate/active_record.rb +216 -0
  7. data/lib/will_paginate/array.rb +57 -0
  8. data/lib/will_paginate/collection.rb +149 -0
  9. data/lib/will_paginate/core_ext.rb +30 -0
  10. data/lib/will_paginate/data_mapper.rb +95 -0
  11. data/lib/will_paginate/deprecation.rb +55 -0
  12. data/lib/will_paginate/i18n.rb +22 -0
  13. data/lib/will_paginate/locale/en.yml +33 -0
  14. data/lib/will_paginate/page_number.rb +57 -0
  15. data/lib/will_paginate/per_page.rb +27 -0
  16. data/lib/will_paginate/railtie.rb +68 -0
  17. data/lib/will_paginate/sequel.rb +39 -0
  18. data/lib/will_paginate/version.rb +9 -0
  19. data/lib/will_paginate/view_helpers.rb +161 -0
  20. data/lib/will_paginate/view_helpers/action_view.rb +148 -0
  21. data/lib/will_paginate/view_helpers/link_renderer.rb +132 -0
  22. data/lib/will_paginate/view_helpers/link_renderer_base.rb +77 -0
  23. data/lib/will_paginate/view_helpers/merb.rb +26 -0
  24. data/lib/will_paginate/view_helpers/sinatra.rb +41 -0
  25. data/spec/ci.rb +29 -0
  26. data/spec/collection_spec.rb +139 -0
  27. data/spec/console +12 -0
  28. data/spec/console_fixtures.rb +28 -0
  29. data/spec/database.yml +22 -0
  30. data/spec/finders/active_record_spec.rb +543 -0
  31. data/spec/finders/activerecord_test_connector.rb +113 -0
  32. data/spec/finders/data_mapper_spec.rb +103 -0
  33. data/spec/finders/data_mapper_test_connector.rb +54 -0
  34. data/spec/finders/sequel_spec.rb +67 -0
  35. data/spec/finders/sequel_test_connector.rb +9 -0
  36. data/spec/fixtures/admin.rb +3 -0
  37. data/spec/fixtures/developer.rb +13 -0
  38. data/spec/fixtures/developers_projects.yml +13 -0
  39. data/spec/fixtures/project.rb +15 -0
  40. data/spec/fixtures/projects.yml +6 -0
  41. data/spec/fixtures/replies.yml +29 -0
  42. data/spec/fixtures/reply.rb +9 -0
  43. data/spec/fixtures/schema.rb +38 -0
  44. data/spec/fixtures/topic.rb +7 -0
  45. data/spec/fixtures/topics.yml +30 -0
  46. data/spec/fixtures/user.rb +2 -0
  47. data/spec/fixtures/users.yml +35 -0
  48. data/spec/page_number_spec.rb +65 -0
  49. data/spec/per_page_spec.rb +41 -0
  50. data/spec/spec_helper.rb +71 -0
  51. data/spec/view_helpers/action_view_spec.rb +423 -0
  52. data/spec/view_helpers/base_spec.rb +130 -0
  53. data/spec/view_helpers/link_renderer_base_spec.rb +87 -0
  54. data/spec/view_helpers/view_example_group.rb +114 -0
  55. metadata +104 -0
@@ -0,0 +1,161 @@
1
+ # encoding: utf-8
2
+ require 'will_paginate/core_ext'
3
+ require 'will_paginate/i18n'
4
+ require 'will_paginate/deprecation'
5
+
6
+ module WillPaginate
7
+ # = Will Paginate view helpers
8
+ #
9
+ # The main view helper is +will_paginate+. It renders the pagination links
10
+ # for the given collection. The helper itself is lightweight and serves only
11
+ # as a wrapper around LinkRenderer instantiation; the renderer then does
12
+ # all the hard work of generating the HTML.
13
+ module ViewHelpers
14
+ class << self
15
+ # Write to this hash to override default options on the global level:
16
+ #
17
+ # WillPaginate::ViewHelpers.pagination_options[:page_links] = false
18
+ #
19
+ attr_accessor :pagination_options
20
+ end
21
+
22
+ # default view options
23
+ self.pagination_options = Deprecation::Hash.new \
24
+ :class => 'pagination',
25
+ :previous_label => nil,
26
+ :next_label => nil,
27
+ :inner_window => 4, # links around the current page
28
+ :outer_window => 1, # links around beginning and end
29
+ :link_separator => ' ', # single space is friendly to spiders and non-graphic browsers
30
+ :param_name => :page,
31
+ :params => nil,
32
+ :page_links => true,
33
+ :container => true
34
+
35
+ label_deprecation = Proc.new { |key, value|
36
+ "set the 'will_paginate.#{key}' key in your i18n locale instead of editing pagination_options" if defined? Rails
37
+ }
38
+ pagination_options.deprecate_key(:previous_label, :next_label, &label_deprecation)
39
+ pagination_options.deprecate_key(:renderer) { |key, _| "pagination_options[#{key.inspect}] shouldn't be set globally" }
40
+
41
+ include WillPaginate::I18n
42
+
43
+ # Returns HTML representing page links for a WillPaginate::Collection-like object.
44
+ # In case there is no more than one page in total, nil is returned.
45
+ #
46
+ # ==== Options
47
+ # * <tt>:class</tt> -- CSS class name for the generated DIV (default: "pagination")
48
+ # * <tt>:previous_label</tt> -- default: "« Previous"
49
+ # * <tt>:next_label</tt> -- default: "Next »"
50
+ # * <tt>:page_links</tt> -- when false, only previous/next links are rendered (default: true)
51
+ # * <tt>:inner_window</tt> -- how many links are shown around the current page (default: 4)
52
+ # * <tt>:outer_window</tt> -- how many links are around the first and the last page (default: 1)
53
+ # * <tt>:link_separator</tt> -- string separator for page HTML elements (default: single space)
54
+ # * <tt>:param_name</tt> -- parameter name for page number in URLs (default: <tt>:page</tt>)
55
+ # * <tt>:params</tt> -- additional parameters when generating pagination links
56
+ # (eg. <tt>:controller => "foo", :action => nil</tt>)
57
+ # * <tt>:renderer</tt> -- class name, class or instance of a link renderer (default in Rails:
58
+ # <tt>WillPaginate::ActionView::LinkRenderer</tt>)
59
+ # * <tt>:page_links</tt> -- when false, only previous/next links are rendered (default: true)
60
+ # * <tt>:container</tt> -- toggles rendering of the DIV container for pagination links, set to
61
+ # false only when you are rendering your own pagination markup (default: true)
62
+ #
63
+ # All options not recognized by will_paginate will become HTML attributes on the container
64
+ # element for pagination links (the DIV). For example:
65
+ #
66
+ # <%= will_paginate @posts, :style => 'color:blue' %>
67
+ #
68
+ # will result in:
69
+ #
70
+ # <div class="pagination" style="color:blue"> ... </div>
71
+ #
72
+ def will_paginate(collection, options = {})
73
+ # early exit if there is nothing to render
74
+ return nil unless collection.total_pages > 1
75
+
76
+ options = WillPaginate::ViewHelpers.pagination_options.merge(options)
77
+
78
+ options[:previous_label] ||= will_paginate_translate(:previous_label) { '&#8592; Previous' }
79
+ options[:next_label] ||= will_paginate_translate(:next_label) { 'Next &#8594;' }
80
+
81
+ # get the renderer instance
82
+ renderer = case options[:renderer]
83
+ when nil
84
+ raise ArgumentError, ":renderer not specified"
85
+ when String
86
+ klass = if options[:renderer].respond_to? :constantize then options[:renderer].constantize
87
+ else Object.const_get(options[:renderer]) # poor man's constantize
88
+ end
89
+ klass.new
90
+ when Class then options[:renderer].new
91
+ else options[:renderer]
92
+ end
93
+ # render HTML for pagination
94
+ renderer.prepare collection, options, self
95
+ renderer.to_html
96
+ end
97
+
98
+ # Renders a message containing number of displayed vs. total entries.
99
+ #
100
+ # <%= page_entries_info @posts %>
101
+ # #-> Displaying posts 6 - 12 of 26 in total
102
+ #
103
+ # The default output contains HTML. Use ":html => false" for plain text.
104
+ def page_entries_info(collection, options = {})
105
+ model = options[:model]
106
+ model = collection.first.class unless model or collection.empty?
107
+ model ||= 'entry'
108
+ model_key = if model.respond_to? :model_name
109
+ model.model_name.i18n_key # ActiveModel::Naming
110
+ else
111
+ model.to_s.underscore
112
+ end
113
+
114
+ if options.fetch(:html, true)
115
+ b, eb = '<b>', '</b>'
116
+ sp = '&nbsp;'
117
+ html_key = '_html'
118
+ else
119
+ b = eb = html_key = ''
120
+ sp = ' '
121
+ end
122
+
123
+ model_count = collection.total_pages > 1 ? 5 : collection.size
124
+ defaults = ["models.#{model_key}"]
125
+ defaults << Proc.new { |_, opts|
126
+ if model.respond_to? :model_name
127
+ model.model_name.human(:count => opts[:count])
128
+ else
129
+ name = model_key.to_s.tr('_', ' ')
130
+ raise "can't pluralize model name: #{model.inspect}" unless name.respond_to? :pluralize
131
+ opts[:count] == 1 ? name : name.pluralize
132
+ end
133
+ }
134
+ model_name = will_paginate_translate defaults, :count => model_count
135
+
136
+ if collection.total_pages < 2
137
+ i18n_key = :"page_entries_info.single_page#{html_key}"
138
+ keys = [:"#{model_key}.#{i18n_key}", i18n_key]
139
+
140
+ will_paginate_translate keys, :count => collection.size, :model => model_name do |_, opts|
141
+ case opts[:count]
142
+ when 0; "No #{opts[:model]} found"
143
+ when 1; "Displaying #{b}1#{eb} #{opts[:model]}"
144
+ else "Displaying #{b}all#{sp}#{opts[:count]}#{eb} #{opts[:model]}"
145
+ end
146
+ end
147
+ else
148
+ i18n_key = :"page_entries_info.multi_page#{html_key}"
149
+ keys = [:"#{model_key}.#{i18n_key}", i18n_key]
150
+ params = {
151
+ :model => model_name, :count => collection.total_entries,
152
+ :from => collection.offset + 1, :to => collection.offset + collection.length
153
+ }
154
+ will_paginate_translate keys, params do |_, opts|
155
+ %{Displaying %s #{b}%d#{sp}-#{sp}%d#{eb} of #{b}%d#{eb} in total} %
156
+ [ opts[:model], opts[:from], opts[:to], opts[:count] ]
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,148 @@
1
+ require 'will_paginate/view_helpers'
2
+ require 'will_paginate/view_helpers/link_renderer'
3
+
4
+ module WillPaginate
5
+ # = ActionView helpers
6
+ #
7
+ # This module serves for availability in ActionView templates. It also adds a new
8
+ # view helper: +paginated_section+.
9
+ #
10
+ # == Using the helper without arguments
11
+ # If the helper is called without passing in the collection object, it will
12
+ # try to read from the instance variable inferred by the controller name.
13
+ # For example, calling +will_paginate+ while the current controller is
14
+ # PostsController will result in trying to read from the <tt>@posts</tt>
15
+ # variable. Example:
16
+ #
17
+ # <%= will_paginate :id => true %>
18
+ #
19
+ # ... will result in <tt>@post</tt> collection getting paginated:
20
+ #
21
+ # <div class="pagination" id="posts_pagination"> ... </div>
22
+ #
23
+ module ActionView
24
+ include ViewHelpers
25
+
26
+ def will_paginate(collection = nil, options = {}) #:nodoc:
27
+ options, collection = collection, nil if collection.is_a? Hash
28
+ collection ||= infer_collection_from_controller
29
+
30
+ options = options.symbolize_keys
31
+ options[:renderer] ||= LinkRenderer
32
+
33
+ super(collection, options).try(:html_safe)
34
+ end
35
+
36
+ def page_entries_info(collection = nil, options = {}) #:nodoc:
37
+ options, collection = collection, nil if collection.is_a? Hash
38
+ collection ||= infer_collection_from_controller
39
+
40
+ super(collection, options.symbolize_keys)
41
+ end
42
+
43
+ # Wrapper for rendering pagination links at both top and bottom of a block
44
+ # of content.
45
+ #
46
+ # <% paginated_section @posts do %>
47
+ # <ol id="posts">
48
+ # <% for post in @posts %>
49
+ # <li> ... </li>
50
+ # <% end %>
51
+ # </ol>
52
+ # <% end %>
53
+ #
54
+ # will result in:
55
+ #
56
+ # <div class="pagination"> ... </div>
57
+ # <ol id="posts">
58
+ # ...
59
+ # </ol>
60
+ # <div class="pagination"> ... </div>
61
+ #
62
+ # Arguments are passed to a <tt>will_paginate</tt> call, so the same options
63
+ # apply. Don't use the <tt>:id</tt> option; otherwise you'll finish with two
64
+ # blocks of pagination links sharing the same ID (which is invalid HTML).
65
+ def paginated_section(*args, &block)
66
+ pagination = will_paginate(*args)
67
+ if pagination
68
+ pagination + capture(&block) + pagination
69
+ else
70
+ capture(&block)
71
+ end
72
+ end
73
+
74
+ def will_paginate_translate(keys, options = {})
75
+ if respond_to? :translate
76
+ if Array === keys
77
+ defaults = keys.dup
78
+ key = defaults.shift
79
+ else
80
+ defaults = nil
81
+ key = keys
82
+ end
83
+ translate(key, options.merge(:default => defaults, :scope => :will_paginate))
84
+ else
85
+ super
86
+ end
87
+ end
88
+
89
+ protected
90
+
91
+ def infer_collection_from_controller
92
+ collection_name = "@#{controller.controller_name}"
93
+ collection = instance_variable_get(collection_name)
94
+ raise ArgumentError, "The #{collection_name} variable appears to be empty. Did you " +
95
+ "forget to pass the collection object for will_paginate?" if collection.nil?
96
+ collection
97
+ end
98
+
99
+ class LinkRenderer < ViewHelpers::LinkRenderer
100
+ protected
101
+
102
+ def default_url_params
103
+ {}
104
+ end
105
+
106
+ def url(page)
107
+ @base_url_params ||= begin
108
+ url_params = merge_get_params(default_url_params)
109
+ merge_optional_params(url_params)
110
+ end
111
+
112
+ url_params = @base_url_params.dup
113
+ add_current_page_param(url_params, page)
114
+
115
+ @template.url_for(url_params)
116
+ end
117
+
118
+ def merge_get_params(url_params)
119
+ if @template.respond_to? :request and @template.request and @template.request.get?
120
+ symbolized_update(url_params, @template.params.except(*@options[:ignore_params]))
121
+ end
122
+ url_params
123
+ end
124
+
125
+ def merge_optional_params(url_params)
126
+ symbolized_update(url_params, @options[:params]) if @options[:params]
127
+ url_params
128
+ end
129
+
130
+ def add_current_page_param(url_params, page)
131
+ unless param_name.index(/[^\w-]/)
132
+ url_params[param_name.to_sym] = page
133
+ else
134
+ page_param = parse_query_parameters("#{param_name}=#{page}")
135
+ symbolized_update(url_params, page_param)
136
+ end
137
+ end
138
+
139
+ private
140
+
141
+ def parse_query_parameters(params)
142
+ Rack::Utils.parse_nested_query(params)
143
+ end
144
+ end
145
+
146
+ ::ActionView::Base.send :include, self
147
+ end
148
+ end
@@ -0,0 +1,132 @@
1
+ require 'cgi'
2
+ require 'will_paginate/core_ext'
3
+ require 'will_paginate/view_helpers'
4
+ require 'will_paginate/view_helpers/link_renderer_base'
5
+
6
+ module WillPaginate
7
+ module ViewHelpers
8
+ # This class does the heavy lifting of actually building the pagination
9
+ # links. It is used by +will_paginate+ helper internally.
10
+ class LinkRenderer < LinkRendererBase
11
+
12
+ # * +collection+ is a WillPaginate::Collection instance or any other object
13
+ # that conforms to that API
14
+ # * +options+ are forwarded from +will_paginate+ view helper
15
+ # * +template+ is the reference to the template being rendered
16
+ def prepare(collection, options, template)
17
+ super(collection, options)
18
+ @template = template
19
+ @container_attributes = @base_url_params = nil
20
+ end
21
+
22
+ # Process it! This method returns the complete HTML string which contains
23
+ # pagination links. Feel free to subclass LinkRenderer and change this
24
+ # method as you see fit.
25
+ def to_html
26
+ html = pagination.map do |item|
27
+ item.is_a?(Fixnum) ?
28
+ page_number(item) :
29
+ send(item)
30
+ end.join(@options[:link_separator])
31
+
32
+ @options[:container] ? html_container(html) : html
33
+ end
34
+
35
+ # Returns the subset of +options+ this instance was initialized with that
36
+ # represent HTML attributes for the container element of pagination links.
37
+ def container_attributes
38
+ @container_attributes ||= @options.except(*(ViewHelpers.pagination_options.keys + [:renderer] - [:class]))
39
+ end
40
+
41
+ protected
42
+
43
+ def page_number(page)
44
+ unless page == current_page
45
+ link(page, page, :rel => rel_value(page))
46
+ else
47
+ tag(:em, page, :class => 'current')
48
+ end
49
+ end
50
+
51
+ def gap
52
+ text = @template.will_paginate_translate(:page_gap) { '&hellip;' }
53
+ %(<span class="gap">#{text}</span>)
54
+ end
55
+
56
+ def previous_page
57
+ num = @collection.current_page > 1 && @collection.current_page - 1
58
+ previous_or_next_page(num, @options[:previous_label], 'previous_page')
59
+ end
60
+
61
+ def next_page
62
+ num = @collection.current_page < @collection.total_pages && @collection.current_page + 1
63
+ previous_or_next_page(num, @options[:next_label], 'next_page')
64
+ end
65
+
66
+ def previous_or_next_page(page, text, classname)
67
+ if page
68
+ link(text, page, :class => classname)
69
+ else
70
+ tag(:span, text, :class => classname + ' disabled')
71
+ end
72
+ end
73
+
74
+ def html_container(html)
75
+ tag(:div, html, container_attributes)
76
+ end
77
+
78
+ # Returns URL params for +page_link_or_span+, taking the current GET params
79
+ # and <tt>:params</tt> option into account.
80
+ def url(page)
81
+ raise NotImplementedError
82
+ end
83
+
84
+ private
85
+
86
+ def param_name
87
+ @options[:param_name].to_s
88
+ end
89
+
90
+ def link(text, target, attributes = {})
91
+ if target.is_a? Fixnum
92
+ attributes[:rel] = rel_value(target)
93
+ target = url(target)
94
+ end
95
+ attributes[:href] = target
96
+ attributes = @options[:extra_attributes].merge(attributes) if @options[:extra_attributes]
97
+ tag(:a, text, attributes)
98
+ end
99
+
100
+ def tag(name, value, attributes = {})
101
+ string_attributes = attributes.inject('') do |attrs, pair|
102
+ unless pair.last.nil?
103
+ attrs << %( #{pair.first}="#{CGI::escapeHTML(pair.last.to_s)}")
104
+ end
105
+ attrs
106
+ end
107
+ "<#{name}#{string_attributes}>#{value}</#{name}>"
108
+ end
109
+
110
+ def rel_value(page)
111
+ case page
112
+ when @collection.current_page - 1; 'prev' + (page == 1 ? ' start' : '')
113
+ when @collection.current_page + 1; 'next'
114
+ when 1; 'start'
115
+ end
116
+ end
117
+
118
+ def symbolized_update(target, other)
119
+ other.each do |key, value|
120
+ key = key.to_sym
121
+ existing = target[key]
122
+
123
+ if value.is_a?(Hash) and (existing.is_a?(Hash) or existing.nil?)
124
+ symbolized_update(existing || (target[key] = {}), value)
125
+ else
126
+ target[key] = value
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,77 @@
1
+ module WillPaginate
2
+ module ViewHelpers
3
+ # This class does the heavy lifting of actually building the pagination
4
+ # links. It is used by +will_paginate+ helper internally.
5
+ class LinkRendererBase
6
+
7
+ # * +collection+ is a WillPaginate::Collection instance or any other object
8
+ # that conforms to that API
9
+ # * +options+ are forwarded from +will_paginate+ view helper
10
+ def prepare(collection, options)
11
+ @collection = collection
12
+ @options = options
13
+
14
+ # reset values in case we're re-using this instance
15
+ @total_pages = nil
16
+ end
17
+
18
+ def pagination
19
+ items = @options[:page_links] ? windowed_page_numbers : []
20
+ items.unshift :previous_page
21
+ items.push :next_page
22
+ end
23
+
24
+ protected
25
+
26
+ # Calculates visible page numbers using the <tt>:inner_window</tt> and
27
+ # <tt>:outer_window</tt> options.
28
+ def windowed_page_numbers
29
+ inner_window, outer_window = @options[:inner_window].to_i, @options[:outer_window].to_i
30
+ window_from = current_page - inner_window
31
+ window_to = current_page + inner_window
32
+
33
+ # adjust lower or upper limit if other is out of bounds
34
+ if window_to > total_pages
35
+ window_from -= window_to - total_pages
36
+ window_to = total_pages
37
+ end
38
+ if window_from < 1
39
+ window_to += 1 - window_from
40
+ window_from = 1
41
+ window_to = total_pages if window_to > total_pages
42
+ end
43
+
44
+ # these are always visible
45
+ middle = window_from..window_to
46
+
47
+ # left window
48
+ if outer_window + 3 < middle.first # there's a gap
49
+ left = (1..(outer_window + 1)).to_a
50
+ left << :gap
51
+ else # runs into visible pages
52
+ left = 1...middle.first
53
+ end
54
+
55
+ # right window
56
+ if total_pages - outer_window - 2 > middle.last # again, gap
57
+ right = ((total_pages - outer_window)..total_pages).to_a
58
+ right.unshift :gap
59
+ else # runs into visible pages
60
+ right = (middle.last + 1)..total_pages
61
+ end
62
+
63
+ left.to_a + middle.to_a + right.to_a
64
+ end
65
+
66
+ private
67
+
68
+ def current_page
69
+ @collection.current_page
70
+ end
71
+
72
+ def total_pages
73
+ @total_pages ||= @collection.total_pages
74
+ end
75
+ end
76
+ end
77
+ end