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
Rails Support
To use Folio's optional Rails support, you will need to load the 'will_paginate' gem into your application along with folio, but don't require it. For instance in your Gemfile:
gem 'will_paginate', require: false
And then you can:
require 'folio/rails'
This will load just the necessary portions of the will_paginate gem.
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 true -
first_page
is always 1 -
previous_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
also has a default implementation.
-
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
. Thenordinal_page?
,first_page
, andlast_page
are handled for you, as described above. If not provided, the default implementation just returns a subclass ofArray
setup to be aFolio::Ordinal::Page
. -
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.
BasicPage
s and create
Often times you just want to take the simplest collection possible. One
way would be to subclass Array
and mixin Folio::Page
, then
instantiate the subclass. When you want to add more, or Array
isn't the
proper superclass, you can still do this.
For the common case we've already done it. This is the
Folio::BasicPage
class. We've also provided a shortcut for
instantiating one: Folio::Page.create
. So, for example, a simple
build_page
method could just be:
def build_page
page = Folio::Page.create
# setup ordinal_pages?, first_page, etc.
page
end
Folio::Ordinal::BasicPage
and Folio::Ordinal::Page.create
are also
available, respectively, for the ordinal case.
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 Folio::Ordinal::BasicPage
) 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