Created
May 14, 2012 16:52
-
-
Save dimiro1/2695030 to your computer and use it in GitHub Desktop.
PaginationBuilder
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'ramaze/gestalt' | |
module Ramaze | |
module Helper | |
# Helper for pagination and pagination-navigation. | |
# | |
# See {Ramaze::Helper::Paginate#paginate} for more information. | |
# | |
module Paginate | |
include Traited | |
class PaginationBuilder | |
include Ramaze::Helper | |
helper :link, :cgi | |
def initialize(pager, css, var) | |
@pager, @css, @var = pager, css, var | |
end | |
def link(g, n, text = n, hash = {}) | |
text = h(text.to_s) | |
action = Current.action | |
params = request.params.merge(@var.to_s => n) | |
hash[:href] = action.node.r(action.path, params) | |
g.a(hash){ text } | |
end | |
def build(limit = 8) | |
g = Ramaze::Gestalt.new | |
g.div :class => :pager do | |
if @pager.first_page? | |
g.span(:class => "#{@css[:first]} #{@css[:disabled]}"){ h('<<') } | |
g.span(:class => "#{@css[:prev]} #{@css[:disabled]}"){ h('<') } | |
else | |
link(g, 1, '<<', :class => @css[:first]) | |
link(g, @pager.prev_page, '<', :class => @css[:prev]) | |
end | |
lower = limit ? (@pager.current_page - limit) : 1 | |
lower = lower < 1 ? 1 : lower | |
([email protected]_page).each do |n| | |
link(g, n, n, :class => @css[:number]) | |
end | |
link(g, @pager.current_page, @pager.current_page, | |
:class => "#{@css[:current]} #{@css[:number]}" ) | |
if @pager.last_page? | |
g.span(:class => "#{@css[:next]} #{@css[:disabled]}"){ h('>') } | |
g.span(:class => "#{@css[:last]} #{@css[:disabled]}"){ h('>>') } | |
elsif @pager.next_page | |
higher = limit ? (@pager.next_page + limit) : @pager.page_count | |
higher = [higher, @pager.page_count].min | |
(@pager.next_page..higher).each do |n| | |
link(g, n, n, :class => @css[:number]) | |
end | |
link(g, @pager.next_page, '>', :class => @css[:next]) | |
link(g, @pager.page_count, '>>', :class => @css[:last]) | |
end | |
end | |
g.to_s | |
end | |
end | |
# Define default options in your Controller, they are being retrieved by | |
# ancestral_trait, so you can also put it into a common superclass | |
trait :paginate => { | |
:limit => 10, | |
:var => 'pager', | |
:builder => PaginationBuilder, | |
:css => { | |
:first => 'first', | |
:prev => 'prev', | |
:next => 'next', | |
:last => 'last', | |
:current => 'current', | |
:number => '', | |
:disabled => 'grey' | |
} | |
} | |
## | |
# Returns a new Paginator instance. | |
# | |
# Note that the pagination relies on being inside a Ramaze request to | |
# gain necessary metadata about the page it resides on, you cannot use it | |
# outside of Ramaze yet. | |
# | |
# The examples below are meant to be used within your controller or view. | |
# | |
# Usage with Array: | |
# | |
# data = (1..100).to_a | |
# @pager = paginate(data, :limit => 30, :page => 2) | |
# @pager.navigation | |
# @pager.each{|e| puts(e) } | |
# | |
# Usage with Sequel: | |
# | |
# data = Article.filter(:public => true) | |
# @pager = paginate(data, :limit => 5) | |
# @pager.navigation | |
# @pager.each{|e| puts(e) | |
# | |
# Note that you must first extend Sequel with the pagination extension" | |
# | |
# Sequel.extension :pagination | |
# | |
# Customizing the classes to use for the various HTML elements of the | |
# page list can be done by passing a key ``:css`` to the list of options. | |
# In this hash you can set the following keys: | |
# | |
# * first, defaults to "first" | |
# * prev, defaults to "prev" | |
# * next, defaults to "next" | |
# * last, defaults to "last" | |
# * current, defaults to "current" | |
# * number, empty by default | |
# * disabled, defaults to "grey" | |
# | |
# These options can also be specified globally in the trait ``:paginate`` | |
# as following: | |
# | |
# class Posts < Ramaze::Controller | |
# map '/' | |
# | |
# trait :paginate => { | |
# :limit => 10, | |
# :var => 'page', | |
# :css => { | |
# :first => 'first_item', | |
# :prev => 'prev_item', | |
# # and so on. | |
# } | |
# } | |
# end | |
# | |
# @param [Sequel::Dataset|Array] dataset May be a Sequel dataset or an | |
# Array | |
# @param [Hash] options A hash containing custom options that takes | |
# precedence to ``trait[:paginate]. | |
# @option options [Fixnum] :limit The number of elements used when you | |
# call #each on the paginator | |
# @option options [String] :var The variable name being used in the | |
# request, this is helpful if you want to use two or more independent | |
# paginations on the same page. | |
# @option options [Fixnum] :page The page you are currently on, if not | |
# given it will be retrieved from current request variables. Defaults to | |
# 1 if neither exists. | |
# @option options [Hash] :css A hash containing various options that can | |
# be used to customize the HTML classes to use for the various page | |
# links. | |
# | |
def paginate(dataset, options = {}) | |
options = ancestral_trait[:paginate].merge(options) | |
limit = options[:limit] | |
var = options[:var] | |
page = options[:page] || (request[var] || 1).to_i | |
opts = {} | |
opts.merge!({:css => options[:css]}) if options[:css] | |
Paginator.new(dataset, page, limit, var, opts) | |
end | |
# Provides easy pagination and navigation | |
class Paginator | |
attr_reader :css | |
def initialize(data = [], page = 1, limit = 10, var = 'pager', opts = {}) | |
@data, @page, @limit, @var = data, page, limit, var | |
@css = Paginate.trait[:paginate][:css].dup | |
@css.merge!(opts[:css]) if opts[:css] | |
@pager = pager_for(data) | |
@page = @page > page_count ? page_count : @page | |
@pager = pager_for(data) | |
@builder_class = Paginate.trait[:paginate][:builder] | |
end | |
## | |
# Returns String with navigation div. | |
# | |
# This cannot be customized very nicely, but you can style it easily | |
# with CSS. | |
# | |
# Output with 5 elements, page 1, limit 3: | |
# | |
# <div class="pager"> | |
# <span class="first grey"><<</span> | |
# <span class="previous grey"><</span> | |
# <a class="current" href="/index?pager=1">1</a> | |
# <a href="/index?pager=2">2</a> | |
# <a class="next" href="/index?pager=2">></a> | |
# <a class="last" href="/index?pager=2">>></a> | |
# </div> | |
# | |
# Output with 5 elements, page 2, limit 3: | |
# | |
# <div class="pager" /> | |
# <a class="first" href="/index?user_page=1"><<</a> | |
# <a class="previous" href="/index?user_page=1"><</a> | |
# <a href="/index?user_page=1">1</a> | |
# <a class="current" href="/index?user_page=2">2</a> | |
# <span class="next grey">></span> | |
# <span class="last grey">>></span> | |
# </div> | |
# | |
def navigation(limit = 8) | |
@builder = @builder_class.new(@pager, @css, @var) | |
@builder.build(limit) | |
end | |
# Useful to omit pager if it's of no use. | |
def needed? | |
@pager.page_count > 1 | |
end | |
# these methods are actually on the pager, but we def them here for | |
# convenience (method_missing in helper is evil and even slower) | |
def page_count ; @pager.page_count ; end | |
def each(&block); @pager.each(&block); end | |
def first_page? ; @pager.first_page? ; end | |
def prev_page ; @pager.prev_page ; end | |
def current_page; @pager.current_page; end | |
def last_page ; @pager.last_page ; end | |
def last_page? ; @pager.last_page? ; end | |
def next_page ; @pager.next_page ; end | |
def empty? ; @pager.empty? ; end | |
def count ; @pager.count ; end | |
private | |
def pager_for(obj) | |
@page = @page < 1 ? 1 : @page | |
case obj | |
when Array | |
ArrayPager.new(obj, @page, @limit) | |
when (defined?(DataMapper::Collection) and DataMapper::Collection) | |
DataMapperPager.new(obj, @page, @limit) | |
else | |
obj.paginate(@page, @limit) | |
end | |
end | |
# Wrapper for Array to behave like the Sequel pagination | |
class ArrayPager | |
def initialize(array, page, limit) | |
@array, @page, @limit = array, page, limit | |
@page = page_count if @page > page_count | |
end | |
def size | |
@array.size | |
end | |
def empty? | |
@array.empty? | |
end | |
def page_count | |
pages, rest = size.divmod(@limit) | |
rest == 0 ? pages : pages + 1 | |
end | |
def current_page | |
@page | |
end | |
def next_page | |
page_count == @page ? nil : @page + 1 | |
end | |
def prev_page | |
@page <= 1 ? nil : @page - 1 | |
end | |
def first_page? | |
@page <= 1 | |
end | |
def last_page? | |
page_count == @page | |
end | |
def each(&block) | |
from = ((@page - 1) * @limit) | |
to = from + @limit | |
a = @array[from...to] || [] | |
a.each(&block) | |
end | |
include Enumerable | |
end | |
# Wrapper for DataMapper::Collection to behave like the Sequel | |
# pagination. | |
# needs 'datamapper' (or 'dm-core' and 'dm-aggregates') | |
class DataMapperPager < ArrayPager | |
def initialize(*args) | |
unless defined?(DataMapper::Aggregates) | |
Ramaze::Log.warn "paginate.rb: it is strongly " + | |
"recommended to require 'dm-aggregates'" | |
end | |
super | |
end | |
def size | |
@cached_size ||= @array.count | |
end | |
def empty? | |
size == 0 | |
end | |
end # DataMapperPager | |
end # Paginator | |
end # Paginate | |
end # Helper | |
end # Ramaze |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment