Folio
Folio
is a library for pagination. It's meant to be nearly compatible
with WillPaginate
, but with broader -- yet more well-defined --
semantics to allow for sources whose page identifiers are non-ordinal
(i.e. a page identifier of 3
does not necessarily indicate the third
page).
Installation
Add this line to your application's Gemfile:
gem 'folio-pagination'
And then execute:
$ bundle
Or install it yourself as:
$ gem install folio-pagination
Usage
The core Folio
interface is defined by two mixins. Mixing Folio
into
a source of items creates a "folio" and provides pagination on that
folio. Mixing Folio::Page
into a subset of items from a folio creates
a "page" with additional properties relating it to the folio and the
other pages in the folio.
Folio
also provides some basic implementations, both standalone and by
mixing these modules in to familiar classes.
Pages
You can mix Folio::Page
into any Enumerable
. The mixin gives you
eight attributes and one method:
ordinal_pages?
indicates whether the page identifiers incurrent_page
,first_page
,last_page
,previous_page
, andnext_page
should be considered ordinal or not.current_page
is the page identifier addressing this page within the folio.per_page
is the number of items requested from the folio when filling this page.first_page
is the page identifier addressing the first page within the folio.last_page
is the page identifier addressing the final page within the folio, if known.next_page
is the page identifier addressing the immediately following page within the folio, if there is one.previous_page
is the page identifier addressing the immediately preceding page within the folio, if there is one and it is known.total_entries
is the number of items in the folio, if known.total_pages
if the number of pages in the folio, if known. It is calculated fromtotal_entries
andper_page
.
ordinal_pages?
, first_page
, and last_page
are common to all pages
created by a folio and are configured, as available, when the folio
creates a blank page in its build_page
method (see below).
current_page
, per_page
, and total_entries
control the filling of a
page and are configured from parameters to the folio's paginate
method.
next_page
and previous_page
are configured, as available, when the
folio fills the configured page in its fill_page
method (see below).
Folios
You can mix Folio
into any class implementing two methods:
build_page
is responsible for instantiating aFolio::Page
and configuring itsordinal_pages?
,first_page
, andlast_page
attributes.fill_page
will receive aFolio::Page
with theordinal_pages?
,first_page
,last_page
,current_page
,per_page
, andtotal_entries
attributes configured, and should populate the page with the corresponding items from the folio. It should also set appropriate values for thenext_page
andprevious_page
attributes on the page. If the value provided in the page'scurrent_page
cannot be interpreted as addressing a page in the folio,Folio::InvalidPage
should be raised.
In return, Folio
provides a paginate
method and per_page
attributes for both the folio class and for individual folio instances.
The paginate
method coordinates the page creation, configuration, and
population. It takes three parameters: page
, per_page
, and
total_entries
, each optional.
page
configures the page'scurrent_page
, defaulting to the page'sfirst_page
.per_page
configures the page'sper_page
, defaulting to the folio'sper_page
attribute.total_entries
configures the page'stotal_entries
, if present. otherwise, if the folio implements acount
method, the page'stotal_entries
will be set from that method.
NOTE: providing a total_entries
parameter of nil will still bypass the
count
method, leaving total_entries
nil. This is useful when the
count would be too expensive and you'd rather just leave the number of
entries unknown.
The per_page
attribute added to the folio instance will default to the
per_page
attribute from the folio class when unset. The per_page
class attribute added to the folio class will default to global
Folio.per_page
when unset.
Ordinal Pages and Folios
A typical use case for pagination deals with ordinal page identifiers; e.g. "1" means the first page, "2" means the second page, etc.
As a matter of convenience for these use cases, additional mixins of
Folio::Ordinal
and Folio::Ordinal::Page
are provided.
Mixing Folio::Ordinal::Page
into an Enumerable
provides the same
methods as Folio::Page
but with the following overrides:
ordinal_pages
is always truefirst_page
is always 1previous_page
is always eithercurrent_page-1
or nil, depending on howcurrent_page
relates tofirst_page
.next_page
can only be set iftotal_pages
is unknown. iftotal_pages
is known,next_page
will be eithercurrent_page+1
or nil, depending on howcurrent_page
relates tolast_page
. iftotal_pages
is unknown andnext_page
is unset (vs. explicitly set to nil), it will default tocurrent_page+1
.last_page
is deterministic: alwaystotal_pages
iftotal_pages
is known,current_page
iftotal_pages
is unknown andnext_page
is nil, nil otherwise (indicating the page sequence continues untilnext_page
is nil).
Similarly, mixing Folio::Ordinal
into a source provides the same
methods as Folio
, but simplifies your build_page
and fill_page
methods by moving some responsibility into the paginate
method.
build_page
no longer needs to configureordinal_page?
,first_page
, orlast_page
on the instantiated page. Instead, just instantiate and return aFolio::Page
orFolio::Ordinal::Page
. If what you return is not aFolio::Ordinal::Page
,paginate
will decorate it to be one. Thenordinal_page?
,first_page
, andlast_page
are handled for you, as described above.fill_page
no longer needs to configurenext_page
andprevious_page
; the ordinal page will handle them. (Note that if necessary, you can still setnext_page
explicitly to nil.) Also,paginate
will now perform ordinal bounds checking for you, so you can focus entirely on populating the page.
Decorations and create
Often times you just want to take a simple collection, such as an
array, and have it act like a page. One way would be to subclass
Array
and mixin Folio::Page
, then instantiate the subclass.
Alternately, you can just call Folio::Page.decorate(collection)
. This
will decorate the collection instance in a delegate that mixes in
Folio::Page
for you. So, for example, a simple build_page
method
could just be:
def build_page
Folio::Page.decorate([])
end
For the common case where a new empty array is what you intend to
decorate, you can simply call Folio::Page.create
.
Folio::Page::Ordinal.decorate(collection)
and
Folio::Page::Ordinal.create
are also available, respectively, for the
ordinal case. decorate
will assume the passed in collection lacks any
pagination and will include Folio::Page
along with
Folio::Page::Ordinal
.
Enumerable Extension
If you require folio/core_ext/enumerable
, all Enumerable
s will be
extended with Folio::Ordinal
and naive build_page
and fill_page
methods.
build_page
will simply return a basic ordinal page as from
Folio::Page::Ordinal.create
. fill_page
then selects an appropriate
range of items from the folio using standard Enumerable
methods, then
calls replace
on the page (it's a decorated array) with this subset.
This lets you do things like:
require 'folio/core_ext/enumerable'
natural_numbers = Enumerator.new do |enum|
n = 0
loop{ enum.yield(n += 1) }
end
page = natural_numbers.paginate(page: 3, per_page: 5, total_entries: nil)
page.ordinal_pages? #=> true
page.per_page #=> 5
page.first_page #=> 1
page.previous_page #=> 2
page.current_page #=> 3
page.next_page #=> 4
page.last_page #=> nil
page.total_entries #=> nil
page.total_pages #=> nil
page #=> [11, 12, 13, 14, 15]
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request