kaminari-jets-core 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1 -0
- data/MIT-LICENSE +21 -0
- data/README.md +31 -0
- data/app/views/kaminari/_first_page.html.erb +11 -0
- data/app/views/kaminari/_first_page.html.haml +9 -0
- data/app/views/kaminari/_first_page.html.slim +10 -0
- data/app/views/kaminari/_gap.html.erb +8 -0
- data/app/views/kaminari/_gap.html.haml +8 -0
- data/app/views/kaminari/_gap.html.slim +9 -0
- data/app/views/kaminari/_last_page.html.erb +11 -0
- data/app/views/kaminari/_last_page.html.haml +9 -0
- data/app/views/kaminari/_last_page.html.slim +10 -0
- data/app/views/kaminari/_next_page.html.erb +11 -0
- data/app/views/kaminari/_next_page.html.haml +9 -0
- data/app/views/kaminari/_next_page.html.slim +10 -0
- data/app/views/kaminari/_page.html.erb +12 -0
- data/app/views/kaminari/_page.html.haml +10 -0
- data/app/views/kaminari/_page.html.slim +11 -0
- data/app/views/kaminari/_paginator.html.erb +25 -0
- data/app/views/kaminari/_paginator.html.haml +18 -0
- data/app/views/kaminari/_paginator.html.slim +19 -0
- data/app/views/kaminari/_prev_page.html.erb +11 -0
- data/app/views/kaminari/_prev_page.html.haml +9 -0
- data/app/views/kaminari/_prev_page.html.slim +10 -0
- data/config/locales/kaminari.yml +23 -0
- data/kaminari-core.gemspec +26 -0
- data/lib/generators/kaminari/config_generator.rb +19 -0
- data/lib/generators/kaminari/templates/kaminari_config.rb +14 -0
- data/lib/generators/kaminari/views_generator.rb +137 -0
- data/lib/kaminari/config.rb +40 -0
- data/lib/kaminari/core/version.rb +8 -0
- data/lib/kaminari/core.rb +29 -0
- data/lib/kaminari/engine.rb +6 -0
- data/lib/kaminari/exceptions.rb +5 -0
- data/lib/kaminari/helpers/helper_methods.rb +247 -0
- data/lib/kaminari/helpers/paginator.rb +191 -0
- data/lib/kaminari/helpers/tags.rb +164 -0
- data/lib/kaminari/jets/engine.rb +6 -0
- data/lib/kaminari/jets/turbine.rb +9 -0
- data/lib/kaminari/models/array_extension.rb +73 -0
- data/lib/kaminari/models/configuration_methods.rb +60 -0
- data/lib/kaminari/models/page_scope_methods.rb +91 -0
- data/lib/kaminari/railtie.rb +9 -0
- metadata +115 -0
@@ -0,0 +1,247 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kaminari
|
4
|
+
module Helpers
|
5
|
+
|
6
|
+
# The Kaminari::Helpers::UrlHelper module provides useful methods for
|
7
|
+
# generating a path or url to a particular page. A class must implement the
|
8
|
+
# following methods:
|
9
|
+
#
|
10
|
+
# * <tt>url_for</tt>: A method that generates an actual path
|
11
|
+
# * <tt>params</tt>: A method that returns query string parameters
|
12
|
+
# * <tt>request</tt>: A method that returns a Rack::Request object
|
13
|
+
#
|
14
|
+
# A normal Rails controller implements all the methods, which make it
|
15
|
+
# trivial to use this module:
|
16
|
+
#
|
17
|
+
# ==== Examples
|
18
|
+
#
|
19
|
+
# class UsersController < ApplicationController
|
20
|
+
# include Kaminari::Helpers::UrlHelper
|
21
|
+
#
|
22
|
+
# def index
|
23
|
+
# @users = User.page(1)
|
24
|
+
#
|
25
|
+
# path_to_next_page(@items)
|
26
|
+
# # => /items?page=2
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
module UrlHelper
|
31
|
+
|
32
|
+
# A helper that calculates the url to the next page.
|
33
|
+
#
|
34
|
+
# ==== Examples
|
35
|
+
# Basic usage:
|
36
|
+
#
|
37
|
+
# <%= next_page_url @items %>
|
38
|
+
# #-> http://www.example.org/items?page=2
|
39
|
+
#
|
40
|
+
# It will return `nil` if there is no next page.
|
41
|
+
def next_page_url(scope, options = {})
|
42
|
+
"#{request.base_url}#{next_page_path(scope, options)}" if scope.next_page
|
43
|
+
end
|
44
|
+
alias url_to_next_page next_page_url
|
45
|
+
|
46
|
+
def path_to_next_url(scope, options = {})
|
47
|
+
ActiveSupport::Deprecation.warn 'path_to_next_url is deprecated. Use next_page_url or url_to_next_page instead.'
|
48
|
+
next_page_url(scope, options)
|
49
|
+
end
|
50
|
+
|
51
|
+
# A helper that calculates the url to the previous page.
|
52
|
+
#
|
53
|
+
# ==== Examples
|
54
|
+
# Basic usage:
|
55
|
+
#
|
56
|
+
# <%= prev_page_url @items %>
|
57
|
+
# #-> http://www.example.org/items
|
58
|
+
#
|
59
|
+
# It will return `nil` if there is no previous page.
|
60
|
+
def prev_page_url(scope, options = {})
|
61
|
+
"#{request.base_url}#{prev_page_path(scope, options)}" if scope.prev_page
|
62
|
+
end
|
63
|
+
alias previous_page_url prev_page_url
|
64
|
+
alias url_to_prev_page prev_page_url
|
65
|
+
alias url_to_previous_page prev_page_url
|
66
|
+
|
67
|
+
# A helper that calculates the path to the next page.
|
68
|
+
#
|
69
|
+
# ==== Examples
|
70
|
+
# Basic usage:
|
71
|
+
#
|
72
|
+
# <%= path_to_next_page @items %>
|
73
|
+
# #-> /items?page=2
|
74
|
+
#
|
75
|
+
# It will return `nil` if there is no next page.
|
76
|
+
def next_page_path(scope, options = {})
|
77
|
+
Kaminari::Helpers::NextPage.new(self, **options.reverse_merge(current_page: scope.current_page)).url if scope.next_page
|
78
|
+
end
|
79
|
+
alias path_to_next_page next_page_path
|
80
|
+
|
81
|
+
# A helper that calculates the path to the previous page.
|
82
|
+
#
|
83
|
+
# ==== Examples
|
84
|
+
# Basic usage:
|
85
|
+
#
|
86
|
+
# <%= path_to_prev_page @items %>
|
87
|
+
# #-> /items
|
88
|
+
#
|
89
|
+
# It will return `nil` if there is no previous page.
|
90
|
+
def prev_page_path(scope, options = {})
|
91
|
+
Kaminari::Helpers::PrevPage.new(self, **options.reverse_merge(current_page: scope.current_page)).url if scope.prev_page
|
92
|
+
end
|
93
|
+
alias previous_page_path prev_page_path
|
94
|
+
alias path_to_previous_page prev_page_path
|
95
|
+
alias path_to_prev_page prev_page_path
|
96
|
+
end
|
97
|
+
|
98
|
+
module HelperMethods
|
99
|
+
include UrlHelper
|
100
|
+
|
101
|
+
# A helper that renders the pagination links.
|
102
|
+
#
|
103
|
+
# <%= paginate @articles %>
|
104
|
+
#
|
105
|
+
# ==== Options
|
106
|
+
# * <tt>:window</tt> - The "inner window" size (4 by default).
|
107
|
+
# * <tt>:outer_window</tt> - The "outer window" size (0 by default).
|
108
|
+
# * <tt>:left</tt> - The "left outer window" size (0 by default).
|
109
|
+
# * <tt>:right</tt> - The "right outer window" size (0 by default).
|
110
|
+
# * <tt>:params</tt> - url_for parameters for the links (:controller, :action, etc.)
|
111
|
+
# * <tt>:param_name</tt> - parameter name for page number in the links (:page by default)
|
112
|
+
# * <tt>:remote</tt> - Ajax? (false by default)
|
113
|
+
# * <tt>:paginator_class</tt> - Specify a custom Paginator (Kaminari::Helpers::Paginator by default)
|
114
|
+
# * <tt>:template</tt> - Specify a custom template renderer for rendering the Paginator (receiver by default)
|
115
|
+
# * <tt>:ANY_OTHER_VALUES</tt> - Any other hash key & values would be directly passed into each tag as :locals value.
|
116
|
+
def paginate(scope, paginator_class: Kaminari::Helpers::Paginator, template: nil, **options)
|
117
|
+
options[:total_pages] ||= scope.total_pages
|
118
|
+
options.reverse_merge! current_page: scope.current_page, per_page: scope.limit_value, remote: false
|
119
|
+
|
120
|
+
paginator = paginator_class.new (template || self), **options
|
121
|
+
paginator.to_s
|
122
|
+
end
|
123
|
+
|
124
|
+
# A simple "Twitter like" pagination link that creates a link to the previous page.
|
125
|
+
#
|
126
|
+
# ==== Examples
|
127
|
+
# Basic usage:
|
128
|
+
#
|
129
|
+
# <%= link_to_previous_page @items, 'Previous Page' %>
|
130
|
+
#
|
131
|
+
# Ajax:
|
132
|
+
#
|
133
|
+
# <%= link_to_previous_page @items, 'Previous Page', remote: true %>
|
134
|
+
#
|
135
|
+
# By default, it renders nothing if there are no more results on the previous page.
|
136
|
+
# You can customize this output by passing a block.
|
137
|
+
#
|
138
|
+
# <%= link_to_previous_page @users, 'Previous Page' do %>
|
139
|
+
# <span>At the Beginning</span>
|
140
|
+
# <% end %>
|
141
|
+
def link_to_previous_page(scope, name, **options)
|
142
|
+
prev_page = path_to_prev_page(scope, options)
|
143
|
+
|
144
|
+
options.except! :params, :param_name
|
145
|
+
options[:rel] ||= 'prev'
|
146
|
+
|
147
|
+
if prev_page
|
148
|
+
link_to name, prev_page, options
|
149
|
+
elsif block_given?
|
150
|
+
yield
|
151
|
+
end
|
152
|
+
end
|
153
|
+
alias link_to_prev_page link_to_previous_page
|
154
|
+
|
155
|
+
# A simple "Twitter like" pagination link that creates a link to the next page.
|
156
|
+
#
|
157
|
+
# ==== Examples
|
158
|
+
# Basic usage:
|
159
|
+
#
|
160
|
+
# <%= link_to_next_page @items, 'Next Page' %>
|
161
|
+
#
|
162
|
+
# Ajax:
|
163
|
+
#
|
164
|
+
# <%= link_to_next_page @items, 'Next Page', remote: true %>
|
165
|
+
#
|
166
|
+
# By default, it renders nothing if there are no more results on the next page.
|
167
|
+
# You can customize this output by passing a block.
|
168
|
+
#
|
169
|
+
# <%= link_to_next_page @users, 'Next Page' do %>
|
170
|
+
# <span>No More Pages</span>
|
171
|
+
# <% end %>
|
172
|
+
def link_to_next_page(scope, name, **options)
|
173
|
+
next_page = path_to_next_page(scope, options)
|
174
|
+
|
175
|
+
options.except! :params, :param_name
|
176
|
+
options[:rel] ||= 'next'
|
177
|
+
|
178
|
+
if next_page
|
179
|
+
link_to name, next_page, options
|
180
|
+
elsif block_given?
|
181
|
+
yield
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Renders a helpful message with numbers of displayed vs. total entries.
|
186
|
+
# Ported from mislav/will_paginate
|
187
|
+
#
|
188
|
+
# ==== Examples
|
189
|
+
# Basic usage:
|
190
|
+
#
|
191
|
+
# <%= page_entries_info @posts %>
|
192
|
+
# #-> Displaying posts 6 - 10 of 26 in total
|
193
|
+
#
|
194
|
+
# By default, the message will use the humanized class name of objects
|
195
|
+
# in collection: for instance, "project types" for ProjectType models.
|
196
|
+
# The namespace will be cutted out and only the last name will be used.
|
197
|
+
# Override this with the <tt>:entry_name</tt> parameter:
|
198
|
+
#
|
199
|
+
# <%= page_entries_info @posts, entry_name: 'item' %>
|
200
|
+
# #-> Displaying items 6 - 10 of 26 in total
|
201
|
+
def page_entries_info(collection, entry_name: nil)
|
202
|
+
entry_name = if entry_name
|
203
|
+
entry_name.pluralize(collection.size, I18n.locale)
|
204
|
+
else
|
205
|
+
collection.entry_name(count: collection.size).downcase
|
206
|
+
end
|
207
|
+
|
208
|
+
if collection.total_pages < 2
|
209
|
+
t('helpers.page_entries_info.one_page.display_entries', entry_name: entry_name, count: collection.total_count)
|
210
|
+
else
|
211
|
+
from = collection.offset_value + 1
|
212
|
+
to = collection.offset_value + (collection.respond_to?(:records) ? collection.records : collection.to_a).size
|
213
|
+
|
214
|
+
t('helpers.page_entries_info.more_pages.display_entries', entry_name: entry_name, first: from, last: to, total: collection.total_count)
|
215
|
+
end.html_safe
|
216
|
+
end
|
217
|
+
|
218
|
+
# Renders rel="next" and rel="prev" links to be used in the head.
|
219
|
+
#
|
220
|
+
# ==== Examples
|
221
|
+
# Basic usage:
|
222
|
+
#
|
223
|
+
# In head:
|
224
|
+
# <head>
|
225
|
+
# <title>My Website</title>
|
226
|
+
# <%= yield :head %>
|
227
|
+
# </head>
|
228
|
+
#
|
229
|
+
# Somewhere in body:
|
230
|
+
# <% content_for :head do %>
|
231
|
+
# <%= rel_next_prev_link_tags @items %>
|
232
|
+
# <% end %>
|
233
|
+
#
|
234
|
+
# #-> <link rel="next" href="/items/page/3"><link rel="prev" href="/items/page/1">
|
235
|
+
#
|
236
|
+
def rel_next_prev_link_tags(scope, options = {})
|
237
|
+
next_page = path_to_next_page(scope, options)
|
238
|
+
prev_page = path_to_prev_page(scope, options)
|
239
|
+
|
240
|
+
output = String.new
|
241
|
+
output << %Q|<link rel="next" href="#{next_page}">| if next_page
|
242
|
+
output << %Q|<link rel="prev" href="#{prev_page}">| if prev_page
|
243
|
+
output.html_safe
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/inflector'
|
4
|
+
require 'kaminari/helpers/tags'
|
5
|
+
|
6
|
+
module Kaminari
|
7
|
+
module Helpers
|
8
|
+
# The main container tag
|
9
|
+
class Paginator < Tag
|
10
|
+
def initialize(template, window: nil, outer_window: Kaminari.config.outer_window, left: Kaminari.config.left, right: Kaminari.config.right, inner_window: Kaminari.config.window, **options) #:nodoc:
|
11
|
+
@window_options = {window: window || inner_window, left: left.zero? ? outer_window : left, right: right.zero? ? outer_window : right}
|
12
|
+
|
13
|
+
@template, @options, @theme, @views_prefix, @last = template, options, options[:theme], options[:views_prefix], nil
|
14
|
+
@window_options.merge! @options
|
15
|
+
@window_options[:current_page] = @options[:current_page] = PageProxy.new(@window_options, @options[:current_page], nil)
|
16
|
+
|
17
|
+
#XXX Using parent template's buffer class for rendering each partial here. This might cause problems if the handler mismatches
|
18
|
+
@output_buffer = if defined?(::ActionView::OutputBuffer)
|
19
|
+
::ActionView::OutputBuffer.new
|
20
|
+
elsif template.instance_variable_get(:@output_buffer)
|
21
|
+
template.instance_variable_get(:@output_buffer).class.new
|
22
|
+
else
|
23
|
+
ActiveSupport::SafeBuffer.new
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# render given block as a view template
|
28
|
+
def render(&block)
|
29
|
+
instance_eval(&block) if @options[:total_pages] > 1
|
30
|
+
|
31
|
+
# This allows for showing fall-back HTML when there's only one page:
|
32
|
+
#
|
33
|
+
# <%= paginate(@search_results) || "Showing all search results" %>
|
34
|
+
@output_buffer.presence
|
35
|
+
end
|
36
|
+
|
37
|
+
# enumerate each page providing PageProxy object as the block parameter
|
38
|
+
# Because of performance reason, this doesn't actually enumerate all pages but pages that are seemingly relevant to the paginator.
|
39
|
+
# "Relevant" pages are:
|
40
|
+
# * pages inside the left outer window plus one for showing the gap tag
|
41
|
+
# * pages inside the inner window plus one on the left plus one on the right for showing the gap tags
|
42
|
+
# * pages inside the right outer window plus one for showing the gap tag
|
43
|
+
def each_relevant_page
|
44
|
+
return to_enum(:each_relevant_page) unless block_given?
|
45
|
+
|
46
|
+
relevant_pages(@window_options).each do |page|
|
47
|
+
yield PageProxy.new(@window_options, page, @last)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
alias each_page each_relevant_page
|
51
|
+
|
52
|
+
def relevant_pages(options)
|
53
|
+
left_window_plus_one = [*1..options[:left] + 1]
|
54
|
+
right_window_plus_one = [*options[:total_pages] - options[:right]..options[:total_pages]]
|
55
|
+
inside_window_plus_each_sides = [*options[:current_page] - options[:window] - 1..options[:current_page] + options[:window] + 1]
|
56
|
+
|
57
|
+
(left_window_plus_one | inside_window_plus_each_sides | right_window_plus_one).sort.reject {|x| (x < 1) || (x > options[:total_pages])}
|
58
|
+
end
|
59
|
+
private :relevant_pages
|
60
|
+
|
61
|
+
def page_tag(page)
|
62
|
+
@last = Page.new @template, **@options.merge(page: page)
|
63
|
+
end
|
64
|
+
|
65
|
+
%w[first_page prev_page next_page last_page gap].each do |tag|
|
66
|
+
eval <<-DEF, nil, __FILE__, __LINE__ + 1
|
67
|
+
def #{tag}_tag
|
68
|
+
@last = #{tag.classify}.new @template, **@options
|
69
|
+
end
|
70
|
+
DEF
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_s #:nodoc:
|
74
|
+
Thread.current[:kaminari_rendering] = true
|
75
|
+
super @window_options.merge paginator: self
|
76
|
+
ensure
|
77
|
+
Thread.current[:kaminari_rendering] = false
|
78
|
+
end
|
79
|
+
|
80
|
+
# delegates view helper methods to @template
|
81
|
+
def method_missing(name, *args, &block)
|
82
|
+
@template.respond_to?(name) ? @template.send(name, *args, &block) : super
|
83
|
+
end
|
84
|
+
private :method_missing
|
85
|
+
|
86
|
+
# Wraps a "page number" and provides some utility methods
|
87
|
+
class PageProxy
|
88
|
+
include Comparable
|
89
|
+
|
90
|
+
def initialize(options, page, last) #:nodoc:
|
91
|
+
@options, @page, @last = options, page, last
|
92
|
+
end
|
93
|
+
|
94
|
+
# the page number
|
95
|
+
def number
|
96
|
+
@page
|
97
|
+
end
|
98
|
+
|
99
|
+
# current page or not
|
100
|
+
def current?
|
101
|
+
@page == @options[:current_page]
|
102
|
+
end
|
103
|
+
|
104
|
+
# the first page or not
|
105
|
+
def first?
|
106
|
+
@page == 1
|
107
|
+
end
|
108
|
+
|
109
|
+
# the last page or not
|
110
|
+
def last?
|
111
|
+
@page == @options[:total_pages]
|
112
|
+
end
|
113
|
+
|
114
|
+
# the previous page or not
|
115
|
+
def prev?
|
116
|
+
@page == @options[:current_page] - 1
|
117
|
+
end
|
118
|
+
|
119
|
+
# the next page or not
|
120
|
+
def next?
|
121
|
+
@page == @options[:current_page] + 1
|
122
|
+
end
|
123
|
+
|
124
|
+
# relationship with the current page
|
125
|
+
def rel
|
126
|
+
if next?
|
127
|
+
'next'
|
128
|
+
elsif prev?
|
129
|
+
'prev'
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# within the left outer window or not
|
134
|
+
def left_outer?
|
135
|
+
@page <= @options[:left]
|
136
|
+
end
|
137
|
+
|
138
|
+
# within the right outer window or not
|
139
|
+
def right_outer?
|
140
|
+
@options[:total_pages] - @page < @options[:right]
|
141
|
+
end
|
142
|
+
|
143
|
+
# inside the inner window or not
|
144
|
+
def inside_window?
|
145
|
+
(@options[:current_page] - @page).abs <= @options[:window]
|
146
|
+
end
|
147
|
+
|
148
|
+
# Current page is an isolated gap or not
|
149
|
+
def single_gap?
|
150
|
+
((@page == @options[:current_page] - @options[:window] - 1) && (@page == @options[:left] + 1)) ||
|
151
|
+
((@page == @options[:current_page] + @options[:window] + 1) && (@page == @options[:total_pages] - @options[:right]))
|
152
|
+
end
|
153
|
+
|
154
|
+
# The page number exceeds the range of pages or not
|
155
|
+
def out_of_range?
|
156
|
+
@page > @options[:total_pages]
|
157
|
+
end
|
158
|
+
|
159
|
+
# The last rendered tag was "truncated" or not
|
160
|
+
def was_truncated?
|
161
|
+
@last.is_a? Gap
|
162
|
+
end
|
163
|
+
|
164
|
+
#Should we display the link tag?
|
165
|
+
def display_tag?
|
166
|
+
left_outer? || right_outer? || inside_window? || single_gap?
|
167
|
+
end
|
168
|
+
|
169
|
+
def to_i #:nodoc:
|
170
|
+
number
|
171
|
+
end
|
172
|
+
|
173
|
+
def to_s #:nodoc:
|
174
|
+
number.to_s
|
175
|
+
end
|
176
|
+
|
177
|
+
def +(other) #:nodoc:
|
178
|
+
to_i + other.to_i
|
179
|
+
end
|
180
|
+
|
181
|
+
def -(other) #:nodoc:
|
182
|
+
to_i - other.to_i
|
183
|
+
end
|
184
|
+
|
185
|
+
def <=>(other) #:nodoc:
|
186
|
+
to_i <=> other.to_i
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kaminari
|
4
|
+
module Helpers
|
5
|
+
PARAM_KEY_EXCEPT_LIST = [:authenticity_token, :commit, :utf8, :_method, :script_name, :original_script_name].freeze
|
6
|
+
|
7
|
+
# A tag stands for an HTML tag inside the paginator.
|
8
|
+
# Basically, a tag has its own partial template file, so every tag can be
|
9
|
+
# rendered into String using its partial template.
|
10
|
+
#
|
11
|
+
# The template file should be placed in your app/views/kaminari/ directory
|
12
|
+
# with underscored class name (besides the "Tag" class. Tag is an abstract
|
13
|
+
# class, so _tag partial is not needed).
|
14
|
+
# e.g.) PrevLink -> app/views/kaminari/_prev_link.html.erb
|
15
|
+
#
|
16
|
+
# When no matching templates were found in your app, the engine's pre
|
17
|
+
# installed template will be used.
|
18
|
+
# e.g.) Paginator -> $GEM_HOME/kaminari-x.x.x/app/views/kaminari/_paginator.html.erb
|
19
|
+
class Tag
|
20
|
+
def initialize(template, params: {}, param_name: nil, theme: nil, views_prefix: nil, **options) #:nodoc:
|
21
|
+
@template, @theme, @views_prefix, @options = template, theme, views_prefix, options
|
22
|
+
@param_name = param_name || Kaminari.config.param_name
|
23
|
+
@params = template.params
|
24
|
+
# @params in Rails 5 no longer inherits from Hash
|
25
|
+
@params = @params.to_unsafe_h if @params.respond_to?(:to_unsafe_h)
|
26
|
+
@params = @params.with_indifferent_access
|
27
|
+
@params.except!(*PARAM_KEY_EXCEPT_LIST)
|
28
|
+
@params.merge! params
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s(locals = {}) #:nodoc:
|
32
|
+
formats = (@template.respond_to?(:formats) ? @template.formats : Array(@template.params[:format])) + [:html]
|
33
|
+
@template.render partial: partial_path, locals: @options.merge(locals), formats: formats
|
34
|
+
end
|
35
|
+
|
36
|
+
def page_url_for(page)
|
37
|
+
params = params_for(page)
|
38
|
+
params[:only_path] = true
|
39
|
+
@template.url_for params
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def params_for(page)
|
45
|
+
page_params = Rack::Utils.parse_nested_query("#{@param_name}=#{page}")
|
46
|
+
page_params = @params.deep_merge(page_params)
|
47
|
+
|
48
|
+
if !Kaminari.config.params_on_first_page && (page <= 1)
|
49
|
+
# This converts a hash:
|
50
|
+
# from: {other: "params", page: 1}
|
51
|
+
# to: {other: "params", page: nil}
|
52
|
+
# (when @param_name == "page")
|
53
|
+
#
|
54
|
+
# from: {other: "params", user: {name: "yuki", page: 1}}
|
55
|
+
# to: {other: "params", user: {name: "yuki", page: nil}}
|
56
|
+
# (when @param_name == "user[page]")
|
57
|
+
@param_name.to_s.scan(/[\w\.]+/)[0..-2].inject(page_params){|h, k| h[k] }[$&] = nil
|
58
|
+
end
|
59
|
+
|
60
|
+
page_params
|
61
|
+
end
|
62
|
+
|
63
|
+
def partial_path
|
64
|
+
[
|
65
|
+
@views_prefix,
|
66
|
+
"kaminari",
|
67
|
+
@theme,
|
68
|
+
self.class.name.demodulize.underscore
|
69
|
+
].compact.join("/").gsub('//', '/')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Tag that contains a link
|
74
|
+
module Link
|
75
|
+
# target page number
|
76
|
+
def page
|
77
|
+
raise 'Override page with the actual page value to be a Page.'
|
78
|
+
end
|
79
|
+
# the link's href
|
80
|
+
def url
|
81
|
+
page_url_for page
|
82
|
+
end
|
83
|
+
def to_s(locals = {}) #:nodoc:
|
84
|
+
locals[:url] = url
|
85
|
+
super locals
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# A page
|
90
|
+
class Page < Tag
|
91
|
+
include Link
|
92
|
+
# target page number
|
93
|
+
def page
|
94
|
+
@options[:page]
|
95
|
+
end
|
96
|
+
def to_s(locals = {}) #:nodoc:
|
97
|
+
locals[:page] = page
|
98
|
+
super locals
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Link with page number that appears at the leftmost
|
103
|
+
class FirstPage < Tag
|
104
|
+
include Link
|
105
|
+
def page #:nodoc:
|
106
|
+
1
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Link with page number that appears at the rightmost
|
111
|
+
class LastPage < Tag
|
112
|
+
include Link
|
113
|
+
def page #:nodoc:
|
114
|
+
@options[:total_pages]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# The "previous" page of the current page
|
119
|
+
class PrevPage < Tag
|
120
|
+
include Link
|
121
|
+
|
122
|
+
# TODO: Remove this initializer before 1.3.0.
|
123
|
+
def initialize(template, params: {}, param_name: nil, theme: nil, views_prefix: nil, **options) #:nodoc:
|
124
|
+
# params in Rails 5 may not be a Hash either,
|
125
|
+
# so it must be converted to a Hash to be merged into @params
|
126
|
+
if params && params.respond_to?(:to_unsafe_h)
|
127
|
+
ActiveSupport::Deprecation.warn 'Explicitly passing params to helpers could be omitted.'
|
128
|
+
params = params.to_unsafe_h
|
129
|
+
end
|
130
|
+
|
131
|
+
super(template, params: params, param_name: param_name, theme: theme, views_prefix: views_prefix, **options)
|
132
|
+
end
|
133
|
+
|
134
|
+
def page #:nodoc:
|
135
|
+
@options[:current_page] - 1
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# The "next" page of the current page
|
140
|
+
class NextPage < Tag
|
141
|
+
include Link
|
142
|
+
|
143
|
+
# TODO: Remove this initializer before 1.3.0.
|
144
|
+
def initialize(template, params: {}, param_name: nil, theme: nil, views_prefix: nil, **options) #:nodoc:
|
145
|
+
# params in Rails 5 may not be a Hash either,
|
146
|
+
# so it must be converted to a Hash to be merged into @params
|
147
|
+
if params && params.respond_to?(:to_unsafe_h)
|
148
|
+
ActiveSupport::Deprecation.warn 'Explicitly passing params to helpers could be omitted.'
|
149
|
+
params = params.to_unsafe_h
|
150
|
+
end
|
151
|
+
|
152
|
+
super(template, params: params, param_name: param_name, theme: theme, views_prefix: views_prefix, **options)
|
153
|
+
end
|
154
|
+
|
155
|
+
def page #:nodoc:
|
156
|
+
@options[:current_page] + 1
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Non-link tag that stands for skipped pages...
|
161
|
+
class Gap < Tag
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/module'
|
4
|
+
module Kaminari
|
5
|
+
# Kind of Array that can paginate
|
6
|
+
class PaginatableArray < Array
|
7
|
+
include Kaminari::ConfigurationMethods::ClassMethods
|
8
|
+
|
9
|
+
ENTRY = 'entry'.freeze
|
10
|
+
|
11
|
+
attr_internal_accessor :limit_value, :offset_value
|
12
|
+
|
13
|
+
# ==== Options
|
14
|
+
# * <tt>:limit</tt> - limit
|
15
|
+
# * <tt>:offset</tt> - offset
|
16
|
+
# * <tt>:total_count</tt> - total_count
|
17
|
+
# * <tt>:padding</tt> - padding
|
18
|
+
def initialize(original_array = [], limit: nil, offset: nil, total_count: nil, padding: nil)
|
19
|
+
@_original_array, @_limit_value, @_offset_value, @_total_count, @_padding = original_array, (limit || default_per_page).to_i, offset.to_i, total_count, padding.to_i
|
20
|
+
|
21
|
+
if limit && offset
|
22
|
+
extend Kaminari::PageScopeMethods
|
23
|
+
end
|
24
|
+
|
25
|
+
if @_total_count && (@_total_count <= original_array.count)
|
26
|
+
original_array = original_array.first(@_total_count)[@_offset_value, @_limit_value]
|
27
|
+
end
|
28
|
+
|
29
|
+
unless @_total_count
|
30
|
+
original_array = original_array[@_offset_value, @_limit_value]
|
31
|
+
end
|
32
|
+
|
33
|
+
super(original_array || [])
|
34
|
+
end
|
35
|
+
|
36
|
+
# Used for page_entry_info
|
37
|
+
def entry_name(options = {})
|
38
|
+
I18n.t('helpers.page_entries_info.entry', **options.reverse_merge(default: ENTRY.pluralize(options[:count])))
|
39
|
+
end
|
40
|
+
|
41
|
+
# items at the specified "page"
|
42
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
43
|
+
def #{Kaminari.config.page_method_name}(num = 1)
|
44
|
+
offset(limit_value * ((num = num.to_i - 1) < 0 ? 0 : num))
|
45
|
+
end
|
46
|
+
RUBY
|
47
|
+
|
48
|
+
# returns another chunk of the original array
|
49
|
+
def limit(num)
|
50
|
+
self.class.new @_original_array, limit: num, offset: @_offset_value, total_count: @_total_count, padding: @_padding
|
51
|
+
end
|
52
|
+
|
53
|
+
# total item numbers of the original array
|
54
|
+
def total_count
|
55
|
+
@_total_count || @_original_array.length
|
56
|
+
end
|
57
|
+
|
58
|
+
# returns another chunk of the original array
|
59
|
+
def offset(num)
|
60
|
+
self.class.new @_original_array, limit: @_limit_value, offset: num, total_count: @_total_count, padding: @_padding
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Wrap an Array object to make it paginatable
|
65
|
+
# ==== Options
|
66
|
+
# * <tt>:limit</tt> - limit
|
67
|
+
# * <tt>:offset</tt> - offset
|
68
|
+
# * <tt>:total_count</tt> - total_count
|
69
|
+
# * <tt>:padding</tt> - padding
|
70
|
+
def self.paginate_array(array, limit: nil, offset: nil, total_count: nil, padding: nil)
|
71
|
+
PaginatableArray.new array, limit: limit, offset: offset, total_count: total_count, padding: padding
|
72
|
+
end
|
73
|
+
end
|