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_pageshould be considered ordinal or not. -
current_pageis the page identifier addressing this page within the folio. -
per_pageis the number of items requested from the folio when filling this page. -
first_pageis the page identifier addressing the first page within the folio. -
last_pageis the page identifier addressing the final page within the folio, if known. -
next_pageis the page identifier addressing the immediately following page within the folio, if there is one. -
previous_pageis the page identifier addressing the immediately preceding page within the folio, if there is one and it is known. -
total_entriesis the number of items in the folio, if known. -
total_pagesif the number of pages in the folio, if known. It is calculated fromtotal_entriesandper_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_pageis responsible for instantiating aFolio::Pageand configuring itsordinal_pages?,first_page, andlast_pageattributes. -
fill_pagewill receive aFolio::Pagewith theordinal_pages?,first_page,last_page,current_page,per_page, andtotal_entriesattributes configured, and should populate the page with the corresponding items from the folio. It should also set appropriate values for thenext_pageandprevious_pageattributes on the page. If the value provided in the page'scurrent_pagecannot be interpreted as addressing a page in the folio,Folio::InvalidPageshould 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.
-
pageconfigures the page'scurrent_page, defaulting to the page'sfirst_page. -
per_pageconfigures the page'sper_page, defaulting to the folio'sper_pageattribute. -
total_entriesconfigures the page'stotal_entries, if present. otherwise, if the folio implements acountmethod, the page'stotal_entrieswill 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_pagesis always true -
first_pageis always 1 -
previous_pageis always eithercurrent_page-1or nil, depending on howcurrent_pagerelates tofirst_page. -
next_pagecan only be set iftotal_pagesis unknown. iftotal_pagesis known,next_pagewill be eithercurrent_page+1or nil, depending on howcurrent_pagerelates tolast_page. iftotal_pagesis unknown andnext_pageis unset (vs. explicitly set to nil), it will default tocurrent_page+1. -
last_pageis deterministic: alwaystotal_pagesiftotal_pagesis known,current_pageiftotal_pagesis unknown andnext_pageis nil, nil otherwise (indicating the page sequence continues untilnext_pageis 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_pageno longer needs to configureordinal_page?,first_page, orlast_pageon the instantiated page. Instead, just instantiate and return aFolio::PageorFolio::Ordinal::Page. Thenordinal_page?,first_page, andlast_pageare handled for you, as described above. If not provided, the default implementation just returns a subclass ofArraysetup to be aFolio::Ordinal::Page. -
fill_pageno longer needs to configurenext_pageandprevious_page; the ordinal page will handle them. (Note that if necessary, you can still setnext_pageexplicitly to nil.) Also,paginatewill now perform ordinal bounds checking for you, so you can focus entirely on populating the page.
BasicPages 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 Enumerables 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