folio-pagination 0.0.2
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.
- data/.gitignore +17 -0
- data/.travis.yml +3 -0
- data/Gemfile +16 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +235 -0
- data/Rakefile +11 -0
- data/folio-pagination.gemspec +27 -0
- data/lib/folio/core_ext/enumerable.rb +51 -0
- data/lib/folio/invalid_page.rb +5 -0
- data/lib/folio/ordinal/page.rb +107 -0
- data/lib/folio/ordinal.rb +33 -0
- data/lib/folio/page.rb +86 -0
- data/lib/folio/per_page.rb +15 -0
- data/lib/folio/rails.rb +35 -0
- data/lib/folio/version.rb +3 -0
- data/lib/folio/will_paginate/active_record.rb +132 -0
- data/lib/folio/will_paginate/collection.rb +19 -0
- data/lib/folio/will_paginate/view_helpers/action_view.rb +10 -0
- data/lib/folio/will_paginate/view_helpers/link_renderer.rb +54 -0
- data/lib/folio/will_paginate/view_helpers/link_renderer_base.rb +44 -0
- data/lib/folio/will_paginate/view_helpers.rb +57 -0
- data/lib/folio-pagination.rb +3 -0
- data/lib/folio.rb +84 -0
- data/test/folio/core_ext/enumerable_test.rb +55 -0
- data/test/folio/ordinal/page_test.rb +173 -0
- data/test/folio/ordinal_test.rb +57 -0
- data/test/folio/page_test.rb +198 -0
- data/test/folio/per_page_test.rb +49 -0
- data/test/folio/rails_test.rb +15 -0
- data/test/folio/version_test.rb +8 -0
- data/test/folio/will_paginate/active_record_test.rb +118 -0
- data/test/folio/will_paginate/collection_test.rb +43 -0
- data/test/folio/will_paginate/view_helpers/link_renderer_base_test.rb +58 -0
- data/test/folio/will_paginate/view_helpers/link_renderer_test.rb +58 -0
- data/test/folio/will_paginate/view_helpers_test.rb +49 -0
- data/test/folio_test.rb +192 -0
- data/test/setup/active_record.rb +13 -0
- metadata +133 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Instructure, Inc.
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
# Folio
|
2
|
+
|
3
|
+
`Folio` is a library for pagination. It's meant to be nearly compatible
|
4
|
+
with `WillPaginate`, but with broader -- yet more well-defined --
|
5
|
+
semantics to allow for sources whose page identifiers are non-ordinal
|
6
|
+
(i.e. a page identifier of `3` does not necessarily indicate the third
|
7
|
+
page).
|
8
|
+
|
9
|
+
[](https://travis-ci.org/instructure/folio)
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem 'folio-pagination'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install folio-pagination
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
The core `Folio` interface is defined by two mixins. Mixing `Folio` into
|
28
|
+
a source of items creates a "folio" and provides pagination on that
|
29
|
+
folio. Mixing `Folio::Page` into a subset of items from a folio creates
|
30
|
+
a "page" with additional properties relating it to the folio and the
|
31
|
+
other pages in the folio.
|
32
|
+
|
33
|
+
`Folio` also provides some basic implementations, both standalone and by
|
34
|
+
mixing these modules in to familiar classes.
|
35
|
+
|
36
|
+
### Pages
|
37
|
+
|
38
|
+
You can mix `Folio::Page` into any `Enumerable`. The mixin gives you
|
39
|
+
eight attributes and one method:
|
40
|
+
|
41
|
+
* `ordinal_pages?` indicates whether the page identifiers in
|
42
|
+
`current_page`, `first_page`, `last_page`, `previous_page`, and
|
43
|
+
`next_page` should be considered ordinal or not.
|
44
|
+
|
45
|
+
* `current_page` is the page identifier addressing this page within the
|
46
|
+
folio.
|
47
|
+
|
48
|
+
* `per_page` is the number of items requested from the folio when
|
49
|
+
filling this page.
|
50
|
+
|
51
|
+
* `first_page` is the page identifier addressing the first page within
|
52
|
+
the folio.
|
53
|
+
|
54
|
+
* `last_page` is the page identifier addressing the final page within
|
55
|
+
the folio, if known.
|
56
|
+
|
57
|
+
* `next_page` is the page identifier addressing the immediately
|
58
|
+
following page within the folio, if there is one.
|
59
|
+
|
60
|
+
* `previous_page` is the page identifier addressing the immediately
|
61
|
+
preceding page within the folio, if there is one and it is known.
|
62
|
+
|
63
|
+
* `total_entries` is the number of items in the folio, if known.
|
64
|
+
|
65
|
+
* `total_pages` if the number of pages in the folio, if known. It is
|
66
|
+
calculated from `total_entries` and `per_page`.
|
67
|
+
|
68
|
+
`ordinal_pages?`, `first_page`, and `last_page` are common to all pages
|
69
|
+
created by a folio and are configured, as available, when the folio
|
70
|
+
creates a blank page in its `build_page` method (see below).
|
71
|
+
|
72
|
+
`current_page`, `per_page`, and `total_entries` control the filling of a
|
73
|
+
page and are configured from parameters to the folio's `paginate`
|
74
|
+
method.
|
75
|
+
|
76
|
+
`next_page` and `previous_page` are configured, as available, when the
|
77
|
+
folio fills the configured page in its `fill_page` method (see below).
|
78
|
+
|
79
|
+
### Folios
|
80
|
+
|
81
|
+
You can mix `Folio` into any class implementing two methods:
|
82
|
+
|
83
|
+
* `build_page` is responsible for instantiating a `Folio::Page` and
|
84
|
+
configuring its `ordinal_pages?`, `first_page`, and `last_page`
|
85
|
+
attributes.
|
86
|
+
|
87
|
+
* `fill_page` will receive a `Folio::Page` with the `ordinal_pages?`,
|
88
|
+
`first_page`, `last_page`, `current_page`, `per_page`, and
|
89
|
+
`total_entries` attributes configured, and should populate the page
|
90
|
+
with the corresponding items from the folio. It should also set
|
91
|
+
appropriate values for the `next_page` and `previous_page` attributes
|
92
|
+
on the page. If the value provided in the page's `current_page`
|
93
|
+
cannot be interpreted as addressing a page in the folio,
|
94
|
+
`Folio::InvalidPage` should be raised.
|
95
|
+
|
96
|
+
In return, `Folio` provides a `paginate` method and `per_page`
|
97
|
+
attributes for both the folio class and for individual folio instances.
|
98
|
+
|
99
|
+
The `paginate` method coordinates the page creation, configuration, and
|
100
|
+
population. It takes three parameters: `page`, `per_page`, and
|
101
|
+
`total_entries`, each optional.
|
102
|
+
|
103
|
+
* `page` configures the page's `current_page`, defaulting to the page's
|
104
|
+
`first_page`.
|
105
|
+
|
106
|
+
* `per_page` configures the page's `per_page`, defaulting to the
|
107
|
+
folio's `per_page` attribute.
|
108
|
+
|
109
|
+
* `total_entries` configures the page's `total_entries`, if present.
|
110
|
+
otherwise, if the folio implements a `count` method, the page's
|
111
|
+
`total_entries` will be set from that method.
|
112
|
+
|
113
|
+
NOTE: providing a `total_entries` parameter of nil will still bypass the
|
114
|
+
`count` method, leaving `total_entries` nil. This is useful when the
|
115
|
+
count would be too expensive and you'd rather just leave the number of
|
116
|
+
entries unknown.
|
117
|
+
|
118
|
+
The `per_page` attribute added to the folio instance will default to the
|
119
|
+
`per_page` attribute from the folio class when unset. The `per_page`
|
120
|
+
class attribute added to the folio class will default to global
|
121
|
+
`Folio.per_page` when unset.
|
122
|
+
|
123
|
+
### Ordinal Pages and Folios
|
124
|
+
|
125
|
+
A typical use case for pagination deals with ordinal page identifiers;
|
126
|
+
e.g. "1" means the first page, "2" means the second page, etc.
|
127
|
+
|
128
|
+
As a matter of convenience for these use cases, additional mixins of
|
129
|
+
`Folio::Ordinal` and `Folio::Ordinal::Page` are provided.
|
130
|
+
|
131
|
+
Mixing `Folio::Ordinal::Page` into an `Enumerable` provides the same
|
132
|
+
methods as `Folio::Page` but with the following overrides:
|
133
|
+
|
134
|
+
* `ordinal_pages` is always true
|
135
|
+
|
136
|
+
* `first_page` is always 1
|
137
|
+
|
138
|
+
* `previous_page` is always either `current_page-1` or nil, depending
|
139
|
+
on how `current_page` relates to `first_page`.
|
140
|
+
|
141
|
+
* `next_page` can only be set if `total_pages` is unknown. if
|
142
|
+
`total_pages` is known, `next_page` will be either `current_page+1`
|
143
|
+
or nil, depending on how `current_page` relates to `last_page`. if
|
144
|
+
`total_pages` is unknown and `next_page` is unset (vs. explicitly set
|
145
|
+
to nil), it will default to `current_page+1`.
|
146
|
+
|
147
|
+
* `last_page` is deterministic: always `total_pages` if `total_pages`
|
148
|
+
is known, `current_page` if `total_pages` is unknown and `next_page`
|
149
|
+
is nil, nil otherwise (indicating the page sequence continues until
|
150
|
+
`next_page` is nil).
|
151
|
+
|
152
|
+
Similarly, mixing `Folio::Ordinal` into a source provides the same
|
153
|
+
methods as `Folio`, but simplifies your `build_page` and `fill_page`
|
154
|
+
methods by moving some responsibility into the `paginate` method.
|
155
|
+
|
156
|
+
* `build_page` no longer needs to configure `ordinal_page?`, `first_page`,
|
157
|
+
or `last_page` on the instantiated page. Instead, just instantiate
|
158
|
+
and return a `Folio::Page` or `Folio::Ordinal::Page`. If what you
|
159
|
+
return is not a `Folio::Ordinal::Page`, `paginate` will decorate it
|
160
|
+
to be one. Then `ordinal_page?`, `first_page`, and `last_page` are
|
161
|
+
handled for you, as described above.
|
162
|
+
|
163
|
+
* `fill_page` no longer needs to configure `next_page` and
|
164
|
+
`previous_page`; the ordinal page will handle them. (Note that if
|
165
|
+
necessary, you can still set `next_page` explicitly to nil.) Also,
|
166
|
+
`paginate` will now perform ordinal bounds checking for you, so you
|
167
|
+
can focus entirely on populating the page.
|
168
|
+
|
169
|
+
### Decorations and `create`
|
170
|
+
|
171
|
+
Often times you just want to take a simple collection, such as an
|
172
|
+
array, and have it act like a page. One way would be to subclass
|
173
|
+
`Array` and mixin `Folio::Page`, then instantiate the subclass.
|
174
|
+
|
175
|
+
Alternately, you can just call `Folio::Page.decorate(collection)`. This
|
176
|
+
will decorate the collection instance in a delegate that mixes in
|
177
|
+
`Folio::Page` for you. So, for example, a simple `build_page` method
|
178
|
+
could just be:
|
179
|
+
|
180
|
+
```
|
181
|
+
def build_page
|
182
|
+
Folio::Page.decorate([])
|
183
|
+
end
|
184
|
+
```
|
185
|
+
|
186
|
+
For the common case where a new empty array is what you intend to
|
187
|
+
decorate, you can simply call `Folio::Page.create`.
|
188
|
+
|
189
|
+
`Folio::Page::Ordinal.decorate(collection)` and
|
190
|
+
`Folio::Page::Ordinal.create` are also available, respectively, for the
|
191
|
+
ordinal case. `decorate` will assume the passed in collection lacks any
|
192
|
+
pagination and will include `Folio::Page` along with
|
193
|
+
`Folio::Page::Ordinal`.
|
194
|
+
|
195
|
+
### Enumerable Extension
|
196
|
+
|
197
|
+
If you require `folio/core_ext/enumerable`, all `Enumerable`s will be
|
198
|
+
extended with `Folio::Ordinal` and naive `build_page` and `fill_page`
|
199
|
+
methods.
|
200
|
+
|
201
|
+
`build_page` will simply return a basic ordinal page as from
|
202
|
+
`Folio::Page::Ordinal.create`. `fill_page` then selects an appropriate
|
203
|
+
range of items from the folio using standard `Enumerable` methods, then
|
204
|
+
calls `replace` on the page (it's a decorated array) with this subset.
|
205
|
+
|
206
|
+
This lets you do things like:
|
207
|
+
|
208
|
+
```
|
209
|
+
require 'folio/core_ext/enumerable'
|
210
|
+
|
211
|
+
natural_numbers = Enumerator.new do |enum|
|
212
|
+
n = 0
|
213
|
+
loop{ enum.yield(n += 1) }
|
214
|
+
end
|
215
|
+
page = natural_numbers.paginate(page: 3, per_page: 5, total_entries: nil)
|
216
|
+
|
217
|
+
page.ordinal_pages? #=> true
|
218
|
+
page.per_page #=> 5
|
219
|
+
page.first_page #=> 1
|
220
|
+
page.previous_page #=> 2
|
221
|
+
page.current_page #=> 3
|
222
|
+
page.next_page #=> 4
|
223
|
+
page.last_page #=> nil
|
224
|
+
page.total_entries #=> nil
|
225
|
+
page.total_pages #=> nil
|
226
|
+
page #=> [11, 12, 13, 14, 15]
|
227
|
+
```
|
228
|
+
|
229
|
+
## Contributing
|
230
|
+
|
231
|
+
1. Fork it
|
232
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
233
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
234
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
235
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'folio/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "folio-pagination"
|
8
|
+
spec.version = Folio::VERSION
|
9
|
+
spec.authors = ["Jacob Fugal"]
|
10
|
+
spec.email = ["jacob@instructure.com"]
|
11
|
+
spec.description = %q{A pagination library.}
|
12
|
+
spec.summary = %q{
|
13
|
+
Folio is a library for pagination. It's meant to be nearly compatible
|
14
|
+
with WillPaginate, but with broader -- yet more well-defined -- semantics
|
15
|
+
to allow for sources whose page identifiers are non-ordinal.
|
16
|
+
}
|
17
|
+
spec.homepage = "https://github.com/instructure/folio"
|
18
|
+
spec.license = "MIT"
|
19
|
+
|
20
|
+
spec.files = `git ls-files`.split($/)
|
21
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
22
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
26
|
+
spec.add_development_dependency "rake"
|
27
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'folio/ordinal'
|
2
|
+
require 'delegate'
|
3
|
+
|
4
|
+
module Folio
|
5
|
+
module Enumerable
|
6
|
+
class Decorator < ::SimpleDelegator
|
7
|
+
# just fill a typical ordinal page
|
8
|
+
def build_page
|
9
|
+
::Folio::Ordinal::Page.create
|
10
|
+
end
|
11
|
+
|
12
|
+
# fill by taking the appropriate slice out of the enumerable. if
|
13
|
+
# the slice is empty and it's not the first page, it's invalid
|
14
|
+
def fill_page(page)
|
15
|
+
slice = self.each_slice(page.per_page).first(page.current_page)[page.current_page-1] || []
|
16
|
+
raise ::Folio::InvalidPage if slice.empty? && page.current_page != page.first_page
|
17
|
+
page.replace slice
|
18
|
+
page
|
19
|
+
end
|
20
|
+
|
21
|
+
# things that already included Enumerable won't have extended the
|
22
|
+
# PerPage, so the instance's default default_per_page method looking
|
23
|
+
# at self.class.per_page won't work. point it back at
|
24
|
+
# Folio.per_page
|
25
|
+
def default_per_page
|
26
|
+
Folio.per_page
|
27
|
+
end
|
28
|
+
|
29
|
+
include ::Folio::Ordinal
|
30
|
+
|
31
|
+
METHODS = self.instance_methods - SimpleDelegator.instance_methods
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Extends any Enumerable to act like a Folio::Ordinal. Important: the
|
37
|
+
# source Enumerable is still not a Folio::Ordinal (it's not in the
|
38
|
+
# ancestors list). Instead, any Enumerable can be decorated via its new
|
39
|
+
# `pagination_decorated` method, and all folio methods are forwarded to
|
40
|
+
# that decorated version.
|
41
|
+
module Enumerable
|
42
|
+
def pagination_decorated
|
43
|
+
@pagination_decorated ||= Folio::Enumerable::Decorator.new(self)
|
44
|
+
end
|
45
|
+
|
46
|
+
Folio::Enumerable::Decorator::METHODS.each do |method|
|
47
|
+
define_method(method) do |*args|
|
48
|
+
pagination_decorated.send(method, *args)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'folio/page'
|
2
|
+
|
3
|
+
# Mix in to an Enumerable to provide the same methods as Folio::Page but with
|
4
|
+
# the following overrides:
|
5
|
+
#
|
6
|
+
# * ordinal_pages is always true
|
7
|
+
#
|
8
|
+
# * first_page is always 1
|
9
|
+
#
|
10
|
+
# * current_page is forced to an integer
|
11
|
+
#
|
12
|
+
# * previous_page is always either current_page-1 or nil, depending on how
|
13
|
+
# current_page relates to first_page.
|
14
|
+
#
|
15
|
+
# * next_page can only be set if total_pages is unknown. if total_pages is
|
16
|
+
# known, next_page will be either current_page+1 or nil, depending on how
|
17
|
+
# current_page relates to last_page. if total_pages is unknown and next_page
|
18
|
+
# is unset (vs. explicitly set to nil), it will default to current_page+1.
|
19
|
+
# if next_page is set to a non-nil value, that value will be forced to an
|
20
|
+
# integer.
|
21
|
+
#
|
22
|
+
# * last_page is deterministic: always total_pages if total_pages is known,
|
23
|
+
# current_page if total_pages is unknown and next_page is nil, nil otherwise
|
24
|
+
# (indicating the page sequence continues until next_page is nil).
|
25
|
+
module Folio
|
26
|
+
module Ordinal
|
27
|
+
module Page
|
28
|
+
def ordinal_pages
|
29
|
+
true
|
30
|
+
end
|
31
|
+
alias :ordinal_pages? :ordinal_pages
|
32
|
+
|
33
|
+
def first_page
|
34
|
+
1
|
35
|
+
end
|
36
|
+
|
37
|
+
def last_page
|
38
|
+
(total_pages || next_page) ? total_pages : current_page
|
39
|
+
end
|
40
|
+
|
41
|
+
def current_page=(value)
|
42
|
+
@current_page = value.to_i
|
43
|
+
end
|
44
|
+
|
45
|
+
def next_page=(value)
|
46
|
+
@next_page = value && value.to_i
|
47
|
+
end
|
48
|
+
|
49
|
+
def next_page
|
50
|
+
if total_pages && current_page >= total_pages
|
51
|
+
# known number of pages and we've reached the last one. no next page
|
52
|
+
# (even if explicitly set)
|
53
|
+
nil
|
54
|
+
elsif total_pages || !defined?(@next_page)
|
55
|
+
# (1) known number of pages and we haven't reached the last one
|
56
|
+
# (because we're not in the branch above), or
|
57
|
+
# (2) unknown number of pages, but nothing set, so we assume an
|
58
|
+
# infinite stream
|
59
|
+
# so there's a next page, and it's the one after this one
|
60
|
+
current_page + 1
|
61
|
+
else
|
62
|
+
# just use what they set
|
63
|
+
@next_page
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def previous_page
|
68
|
+
current_page > first_page ? current_page - 1 : nil
|
69
|
+
end
|
70
|
+
|
71
|
+
def out_of_bounds?
|
72
|
+
(current_page < first_page) || (last_page && current_page > last_page) || false
|
73
|
+
end
|
74
|
+
|
75
|
+
def offset
|
76
|
+
(current_page - 1) * per_page
|
77
|
+
end
|
78
|
+
|
79
|
+
class Decorator < Folio::Page::Decorator
|
80
|
+
include Folio::Ordinal::Page
|
81
|
+
end
|
82
|
+
|
83
|
+
class DecoratedArray < Decorator
|
84
|
+
def initialize
|
85
|
+
super []
|
86
|
+
end
|
87
|
+
|
88
|
+
def replace(array)
|
89
|
+
result = super
|
90
|
+
if total_entries.nil? and length < per_page and (current_page == 1 or length > 0)
|
91
|
+
self.total_entries = offset + length
|
92
|
+
end
|
93
|
+
result
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.decorate(collection)
|
98
|
+
collection = Folio::Ordinal::Page::Decorator.new(collection) unless collection.is_a?(Folio::Ordinal::Page)
|
99
|
+
collection
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.create
|
103
|
+
Folio::Ordinal::Page::DecoratedArray.new
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'folio'
|
2
|
+
require 'folio/ordinal/page'
|
3
|
+
require 'folio/invalid_page'
|
4
|
+
|
5
|
+
# Mix in to a source to provide the same methods as Folio, but with simpler
|
6
|
+
# build_page and fill_page methods required on the host (some responsibility is
|
7
|
+
# moved into the paginate method).
|
8
|
+
#
|
9
|
+
# * build_page no longer needs to configure ordinal_page?, first_page,
|
10
|
+
# or last_page on the instantiated page. Instead, just instantiate
|
11
|
+
# and return a Folio::Ordinal::Page. If what you return is not a
|
12
|
+
# Folio::Ordinal::Page, paginate will decorate it to be one.
|
13
|
+
#
|
14
|
+
# * fill_page no longer needs to configure next_page and previous_page; the
|
15
|
+
# ordinal page will handle them. (Note that if necessary, you can still set
|
16
|
+
# next_page explicitly to nil.) Also, paginate will now perform ordinal
|
17
|
+
# bounds checking for you, so you can focus entirely on populating the page.
|
18
|
+
#
|
19
|
+
module Folio
|
20
|
+
module Ordinal
|
21
|
+
# decorate the page before configuring, and then validate the configured
|
22
|
+
# current_page before returning it
|
23
|
+
def configure_pagination(page, options)
|
24
|
+
page = super(::Folio::Ordinal::Page.decorate(page), options)
|
25
|
+
raise ::Folio::InvalidPage unless page.current_page.is_a?(Integer)
|
26
|
+
raise ::Folio::InvalidPage if page.out_of_bounds?
|
27
|
+
page
|
28
|
+
end
|
29
|
+
|
30
|
+
# otherwise acts like a normal folio
|
31
|
+
include ::Folio
|
32
|
+
end
|
33
|
+
end
|
data/lib/folio/page.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'folio/per_page'
|
2
|
+
require 'delegate'
|
3
|
+
|
4
|
+
# Mix into any Enumerable. The mixin gives you the eight attributes and
|
5
|
+
# one method described below.
|
6
|
+
#
|
7
|
+
# ordinal_pages?, first_page, and last_page are common to all pages
|
8
|
+
# created by a folio and are configured, as available, when the folio
|
9
|
+
# creates a blank page.
|
10
|
+
#
|
11
|
+
# current_page, per_page, and total_entries control the filling of a
|
12
|
+
# page and are configured from parameters to the folio's paginate
|
13
|
+
# method.
|
14
|
+
#
|
15
|
+
# next_page and previous_page are configured, as available, when the
|
16
|
+
# folio fills the configured page.
|
17
|
+
module Folio
|
18
|
+
module Page
|
19
|
+
# indicates whether the page identifiers in current_page,
|
20
|
+
# first_page, last_page, previous_page, and next_page should be
|
21
|
+
# considered ordinal or not.
|
22
|
+
attr_accessor :ordinal_pages
|
23
|
+
alias :ordinal_pages? :ordinal_pages
|
24
|
+
|
25
|
+
# page identifier addressing this page within the folio.
|
26
|
+
attr_accessor :current_page
|
27
|
+
|
28
|
+
# number of items requested from the folio when filling the page.
|
29
|
+
include Folio::PerPage
|
30
|
+
|
31
|
+
# page identifier addressing the first page within the folio.
|
32
|
+
attr_accessor :first_page
|
33
|
+
|
34
|
+
# page identifier addressing the final page within the folio, if
|
35
|
+
# known.
|
36
|
+
def last_page=(value)
|
37
|
+
@last_page = value
|
38
|
+
end
|
39
|
+
|
40
|
+
def last_page
|
41
|
+
if next_page.nil?
|
42
|
+
current_page
|
43
|
+
else
|
44
|
+
@last_page
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# page identifier addressing the immediately following page within
|
49
|
+
# the folio, if there is one.
|
50
|
+
attr_accessor :next_page
|
51
|
+
|
52
|
+
# page identifier addressing the immediately preceding page within
|
53
|
+
# the folio, if there is one and it is known.
|
54
|
+
attr_accessor :previous_page
|
55
|
+
|
56
|
+
# number of items in the folio, if known.
|
57
|
+
attr_accessor :total_entries
|
58
|
+
|
59
|
+
# number of pages in the folio, if known. calculated from
|
60
|
+
# total_entries and per_page.
|
61
|
+
def total_pages
|
62
|
+
return nil unless total_entries && per_page && per_page > 0
|
63
|
+
return 1 if total_entries <= 0
|
64
|
+
(total_entries / per_page.to_f).ceil
|
65
|
+
end
|
66
|
+
|
67
|
+
class Decorator < ::SimpleDelegator
|
68
|
+
include Folio::Page
|
69
|
+
end
|
70
|
+
|
71
|
+
class DecoratedArray < Decorator
|
72
|
+
def initialize
|
73
|
+
super []
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.decorate(collection)
|
78
|
+
collection = Folio::Page::Decorator.new(collection) unless collection.is_a?(Folio::Page)
|
79
|
+
collection
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.create
|
83
|
+
Folio::Page::DecoratedArray.new
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Folio
|
2
|
+
module PerPage
|
3
|
+
def default_per_page
|
4
|
+
Folio.per_page
|
5
|
+
end
|
6
|
+
|
7
|
+
def per_page(*args)
|
8
|
+
raise ArgumentError if args.size > 1
|
9
|
+
@per_page = (args.first && args.first.to_i) if args.size > 0
|
10
|
+
@per_page ? @per_page : default_per_page
|
11
|
+
end
|
12
|
+
|
13
|
+
alias_method :per_page=, :per_page
|
14
|
+
end
|
15
|
+
end
|
data/lib/folio/rails.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
begin
|
2
|
+
require 'rails'
|
3
|
+
rescue LoadError
|
4
|
+
raise "folio-pagination's rails support requires rails"
|
5
|
+
end
|
6
|
+
|
7
|
+
begin
|
8
|
+
require 'will_paginate/railtie'
|
9
|
+
rescue LoadError
|
10
|
+
raise "folio-pagination's rails support requires will_paginate"
|
11
|
+
end
|
12
|
+
|
13
|
+
module Folio
|
14
|
+
module WillPaginate
|
15
|
+
class Railtie < Rails::Railtie
|
16
|
+
config.action_dispatch.rescue_responses.merge!(
|
17
|
+
'Folio::InvalidPage' => :not_found
|
18
|
+
)
|
19
|
+
|
20
|
+
initializer "folio" do |app|
|
21
|
+
ActiveSupport.on_load :active_record do
|
22
|
+
require 'folio/will_paginate/active_record'
|
23
|
+
end
|
24
|
+
|
25
|
+
ActiveSupport.on_load :action_view do
|
26
|
+
require 'folio/will_paginate/view_helpers/action_view'
|
27
|
+
end
|
28
|
+
|
29
|
+
# don't need early access to our stuff, but will_paginate loaded
|
30
|
+
# theirs, so we better patch it up now rather than later
|
31
|
+
require 'folio/will_paginate/view_helpers'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|