mjfreshyfresh-leaf 0.0.1
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/MIT-LICENSE +18 -0
- data/README.rdoc +53 -0
- data/Rakefile +26 -0
- data/lib/leaf.rb +15 -0
- data/lib/leaf/array.rb +31 -0
- data/lib/leaf/collection.rb +100 -0
- data/lib/leaf/core_ext.rb +69 -0
- data/lib/leaf/finders.rb +9 -0
- data/lib/leaf/finders/base.rb +82 -0
- data/lib/leaf/finders/sequel.rb +23 -0
- data/lib/leaf/version.rb +9 -0
- data/lib/leaf/view_helpers.rb +34 -0
- data/lib/leaf/view_helpers/base.rb +60 -0
- data/lib/leaf/view_helpers/link_renderer.rb +129 -0
- data/lib/leaf/view_helpers/link_renderer_base.rb +83 -0
- data/lib/leaf/view_helpers/list_renderer.rb +39 -0
- data/lib/leaf/view_helpers/sinatra.rb +20 -0
- metadata +94 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2010 Peter Hellberg
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
7
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
8
|
+
subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
15
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
16
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
18
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
= Leaf
|
2
|
+
|
3
|
+
A _really_ simple pagination library, heavily based on the agnostic branch of
|
4
|
+
{will_paginate}[http://github.com/mislav/will_paginate/tree/agnostic].
|
5
|
+
|
6
|
+
= Description
|
7
|
+
|
8
|
+
Leaf supports pagination for collections responding to +total_pages+,
|
9
|
+
+per_page+, +previous_page+ and +total_entries+ in Sinatra views out of the box.
|
10
|
+
|
11
|
+
It currently supports two renderers: +Leaf::ViewHelpers::LinkRenderer+
|
12
|
+
and +Leaf::ViewHelpers::ListRenderer+
|
13
|
+
|
14
|
+
== Installation
|
15
|
+
|
16
|
+
gem install leaf
|
17
|
+
|
18
|
+
== Example usage
|
19
|
+
|
20
|
+
require 'rubygems'
|
21
|
+
require 'sinatra'
|
22
|
+
require 'leaf'
|
23
|
+
|
24
|
+
include Leaf::ViewHelpers::Base
|
25
|
+
|
26
|
+
# Needed to paginate any array
|
27
|
+
# you’ll probably use something else.
|
28
|
+
require 'leaf/array'
|
29
|
+
|
30
|
+
get '/' do
|
31
|
+
page = (params[:page]) ? params[:page] : 1
|
32
|
+
array = ('a'..'z').to_a
|
33
|
+
|
34
|
+
haml :index, :locals => {
|
35
|
+
:collection => array.paginate({
|
36
|
+
:page => page,
|
37
|
+
:per_page => 5
|
38
|
+
})
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
__END__
|
43
|
+
@@ index
|
44
|
+
= leaf(collection, :renderer => Leaf::ViewHelpers::ListRenderer)
|
45
|
+
%ul
|
46
|
+
- collection.each do |letter|
|
47
|
+
%li= letter
|
48
|
+
|
49
|
+
== Authors and credits
|
50
|
+
|
51
|
+
Leaf is based on {will_paginate}[http://github.com/mislav/will_paginate/] which
|
52
|
+
was originally written by PJ Hyett, who later handed over development to Mislav
|
53
|
+
Marohnić. (The library was completely rewritten since then.)
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'rdoc' # Required on Mac OS X to get a “working” rdoc version
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
load 'spec/tasks.rake'
|
6
|
+
|
7
|
+
desc 'Default: run specs.'
|
8
|
+
task :default => :spec
|
9
|
+
|
10
|
+
desc 'Generate RDoc documentation for the leaf gem.'
|
11
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
12
|
+
rdoc.rdoc_files.include('README.rdoc', 'MIT-LICENSE').
|
13
|
+
include('lib/**/*.rb').
|
14
|
+
exclude('lib/leaf/finders/sequel.rb').
|
15
|
+
exclude('lib/leaf/view_helpers/sinatra.rb').
|
16
|
+
exclude('lib/leaf/core_ext.rb').
|
17
|
+
exclude('lib/leaf/version.rb')
|
18
|
+
|
19
|
+
rdoc.main = "README.rdoc" # page to start on
|
20
|
+
rdoc.title = "leaf documentation"
|
21
|
+
|
22
|
+
rdoc.rdoc_dir = 'doc' # rdoc output folder
|
23
|
+
rdoc.options << '--inline-source' << '--charset=UTF-8' << '--format=darkfish'
|
24
|
+
rdoc.options << '--main=README.rdoc'
|
25
|
+
rdoc.options << '--webcvs=http://github.com/c7/leaf/tree/master/'
|
26
|
+
end
|
data/lib/leaf.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# = Leafing through the pages!
|
2
|
+
#
|
3
|
+
# First read about Leaf::Finders::Base, then see
|
4
|
+
# Leaf::ViewHelpers. The magical array you're handling in-between is
|
5
|
+
# Leaf::Collection.
|
6
|
+
#
|
7
|
+
# Happy paginating!
|
8
|
+
module Leaf
|
9
|
+
require 'leaf/version'
|
10
|
+
|
11
|
+
# Load the helpers for Sinatra
|
12
|
+
if defined?(Sinatra)
|
13
|
+
require 'leaf/view_helpers/sinatra'
|
14
|
+
end
|
15
|
+
end
|
data/lib/leaf/array.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'leaf/collection'
|
2
|
+
|
3
|
+
class Array
|
4
|
+
# Paginates a static array (extracting a subset of it). The result is a
|
5
|
+
# Leaf::Collection instance, which is an array with a few more
|
6
|
+
# properties about its paginated state.
|
7
|
+
#
|
8
|
+
# Parameters:
|
9
|
+
# * <tt>:page</tt> - current page, defaults to 1
|
10
|
+
# * <tt>:per_page</tt> - limit of items per page, defaults to 30
|
11
|
+
# * <tt>:total_entries</tt> - total number of items in the array, defaults to
|
12
|
+
# <tt>array.length</tt> (obviously)
|
13
|
+
#
|
14
|
+
# Example:
|
15
|
+
# arr = ['a', 'b', 'c', 'd', 'e']
|
16
|
+
# paged = arr.paginate(:per_page => 2) #-> ['a', 'b']
|
17
|
+
# paged.total_entries #-> 5
|
18
|
+
# arr.paginate(:page => 2, :per_page => 2) #-> ['c', 'd']
|
19
|
+
# arr.paginate(:page => 3, :per_page => 2) #-> ['e']
|
20
|
+
#
|
21
|
+
# This method was originally {suggested by Desi
|
22
|
+
# McAdam}[http://www.desimcadam.com/archives/8]
|
23
|
+
def paginate(options = {})
|
24
|
+
raise ArgumentError, "parameter hash expected (got #{options.inspect})" unless Hash === options
|
25
|
+
Leaf::Collection.create options[:page] || 1,
|
26
|
+
options[:per_page] || 30,
|
27
|
+
options[:total_entries] || self.length do |pager|
|
28
|
+
pager.replace self[pager.offset, pager.per_page].to_a
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Leaf
|
2
|
+
# = Invalid page number error
|
3
|
+
# This is an ArgumentError raised in case a page was requested that is either
|
4
|
+
# zero or negative number. You should decide how do deal with such errors in
|
5
|
+
# the controller.
|
6
|
+
class InvalidPage < ArgumentError
|
7
|
+
def initialize(page, page_num)
|
8
|
+
super "#{page.inspect} given as value, which translates to '#{page_num}' as page number"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# = The key to pagination
|
13
|
+
# Arrays returned from paginating finds are, in fact, instances of this little
|
14
|
+
# class. You may think of Leaf::Collection as an ordinary array with
|
15
|
+
# some extra properties. Those properties are used by view helpers to generate
|
16
|
+
# correct page links.
|
17
|
+
#
|
18
|
+
# gem 'leaf'
|
19
|
+
# require 'leaf/collection'
|
20
|
+
#
|
21
|
+
# # now use Leaf::Collection directly or subclass it
|
22
|
+
class Collection < Array
|
23
|
+
attr_reader :current_page, :per_page, :total_entries, :total_pages
|
24
|
+
|
25
|
+
# Arguments to the constructor are the current page number, per-page limit
|
26
|
+
# and the total number of entries. The last argument is optional because it
|
27
|
+
# is best to do lazy counting; in other words, count *conditionally* after
|
28
|
+
# populating the collection using the +replace+ method.
|
29
|
+
def initialize(page, per_page, total = nil)
|
30
|
+
@current_page = page.to_i
|
31
|
+
raise InvalidPage.new(page, @current_page) if @current_page < 1
|
32
|
+
@per_page = per_page.to_i
|
33
|
+
raise ArgumentError, "`per_page` setting cannot be less than 1 (#{@per_page} given)" if @per_page < 1
|
34
|
+
|
35
|
+
self.total_entries = total if total
|
36
|
+
end
|
37
|
+
|
38
|
+
# Just like +new+, but yields the object after instantiation and returns it
|
39
|
+
# afterwards. This is very useful for manual pagination:
|
40
|
+
def self.create(page, per_page, total = nil, &block)
|
41
|
+
pager = new(page, per_page, total)
|
42
|
+
yield pager
|
43
|
+
pager
|
44
|
+
end
|
45
|
+
|
46
|
+
# Helper method that is true when someone tries to fetch a page with a
|
47
|
+
# larger number than the last page. Can be used in combination with flashes
|
48
|
+
# and redirecting.
|
49
|
+
def out_of_bounds?
|
50
|
+
current_page > total_pages
|
51
|
+
end
|
52
|
+
|
53
|
+
# Current offset of the paginated collection. If we're on the first page,
|
54
|
+
# it is always 0. If we're on the 2nd page and there are 30 entries per page,
|
55
|
+
# the offset is 30. This property is useful if you want to render ordinals
|
56
|
+
# besides your records: simply start with offset + 1.
|
57
|
+
def offset
|
58
|
+
(current_page - 1) * per_page
|
59
|
+
end
|
60
|
+
|
61
|
+
# current_page - 1 or nil if there is no previous page
|
62
|
+
def previous_page
|
63
|
+
current_page > 1 ? (current_page - 1) : nil
|
64
|
+
end
|
65
|
+
|
66
|
+
# current_page + 1 or nil if there is no next page
|
67
|
+
def next_page
|
68
|
+
current_page < total_pages ? (current_page + 1) : nil
|
69
|
+
end
|
70
|
+
|
71
|
+
def total_entries=(number)
|
72
|
+
@total_entries = number.to_i
|
73
|
+
@total_pages = (@total_entries / per_page.to_f).ceil
|
74
|
+
end
|
75
|
+
|
76
|
+
# This is a magic wrapper for the original Array#replace method. It serves
|
77
|
+
# for populating the paginated collection after initialization.
|
78
|
+
#
|
79
|
+
# Why magic? Because it tries to guess the total number of entries judging
|
80
|
+
# by the size of given array. If it is shorter than +per_page+ limit, then we
|
81
|
+
# know we're on the last page. This trick is very useful for avoiding
|
82
|
+
# unnecessary hits to the database to do the counting after we fetched the
|
83
|
+
# data for the current page.
|
84
|
+
#
|
85
|
+
# However, after using +replace+ you should always test the value of
|
86
|
+
# +total_entries+ and set it to a proper value if it's +nil+. See the example
|
87
|
+
# in +create+.
|
88
|
+
def replace(array)
|
89
|
+
result = super
|
90
|
+
|
91
|
+
# The collection is shorter then page limit? Rejoice, because
|
92
|
+
# then we know that we are on the last page!
|
93
|
+
if total_entries.nil? and length < per_page and (current_page == 1 or length > 0)
|
94
|
+
self.total_entries = offset + length
|
95
|
+
end
|
96
|
+
|
97
|
+
result
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'leaf/array'
|
3
|
+
|
4
|
+
# helper to check for method existance in ruby 1.8- and 1.9-compatible way
|
5
|
+
# because `methods`, `instance_methods` and others return strings in 1.8 and symbols in 1.9
|
6
|
+
#
|
7
|
+
# ['foo', 'bar'].include_method?(:foo) # => true
|
8
|
+
class Array
|
9
|
+
def include_method?(name)
|
10
|
+
name = name.to_sym
|
11
|
+
!!(find { |item| item.to_sym == name })
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
## everything below copied from ActiveSupport so we don't depend on it ##
|
16
|
+
|
17
|
+
unless Hash.instance_methods.include_method? :except
|
18
|
+
Hash.class_eval do
|
19
|
+
# Returns a new hash without the given keys.
|
20
|
+
def except(*keys)
|
21
|
+
rejected = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys)
|
22
|
+
reject { |key,| rejected.include?(key) }
|
23
|
+
end
|
24
|
+
|
25
|
+
# Replaces the hash without only the given keys.
|
26
|
+
def except!(*keys)
|
27
|
+
replace(except(*keys))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
unless Hash.instance_methods.include_method? :slice
|
33
|
+
Hash.class_eval do
|
34
|
+
# Returns a new hash with only the given keys.
|
35
|
+
def slice(*keys)
|
36
|
+
allowed = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys)
|
37
|
+
reject { |key,| !allowed.include?(key) }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Replaces the hash with only the given keys.
|
41
|
+
def slice!(*keys)
|
42
|
+
replace(slice(*keys))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
unless String.instance_methods.include_method? :constantize
|
48
|
+
String.class_eval do
|
49
|
+
def constantize
|
50
|
+
unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ self
|
51
|
+
raise NameError, "#{self.inspect} is not a valid constant name!"
|
52
|
+
end
|
53
|
+
|
54
|
+
Object.module_eval("::#{$1}", __FILE__, __LINE__)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
unless String.instance_methods.include_method? :underscore
|
60
|
+
String.class_eval do
|
61
|
+
def underscore
|
62
|
+
self.to_s.gsub(/::/, '/').
|
63
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
64
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
65
|
+
tr("-", "_").
|
66
|
+
downcase
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/leaf/finders.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'leaf/core_ext'
|
2
|
+
|
3
|
+
module Leaf
|
4
|
+
module Finders
|
5
|
+
# = Database-agnostic finder module
|
6
|
+
#
|
7
|
+
# Out of the box, leaf supports hooking in the Sequel ORM
|
8
|
+
module Base
|
9
|
+
def per_page
|
10
|
+
@per_page ||= 30
|
11
|
+
end
|
12
|
+
|
13
|
+
def per_page=(limit)
|
14
|
+
@per_page = limit.to_i
|
15
|
+
end
|
16
|
+
|
17
|
+
# This is the main paginating finder.
|
18
|
+
#
|
19
|
+
# == Special parameters for paginating finders
|
20
|
+
# * <tt>:page</tt> -- REQUIRED, but defaults to 1 if false or nil
|
21
|
+
# * <tt>:per_page</tt> -- defaults to <tt>CurrentModel.per_page</tt> (which is 30 if not overridden)
|
22
|
+
# * <tt>:total_entries</tt> -- use only if you manually count total entries
|
23
|
+
# * <tt>:count</tt> -- additional options that are passed on to +count+
|
24
|
+
# * <tt>:finder</tt> -- name of the finder method to use (default: "find")
|
25
|
+
#
|
26
|
+
# All other options (+conditions+, +order+, ...) are forwarded to +find+
|
27
|
+
# and +count+ calls.
|
28
|
+
def paginate(*args, &block)
|
29
|
+
options = args.pop
|
30
|
+
page, per_page, total_entries = leaf_parse_options(options)
|
31
|
+
|
32
|
+
Leaf::Collection.create(page, per_page, total_entries) do |pager|
|
33
|
+
query_options = options.except :page, :per_page, :total_entries
|
34
|
+
wp_query(query_options, pager, args, &block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Iterates through all records by loading one page at a time. This is useful
|
39
|
+
# for migrations or any other use case where you don't want to load all the
|
40
|
+
# records in memory at once.
|
41
|
+
#
|
42
|
+
# It uses +paginate+ internally; therefore it accepts all of its options.
|
43
|
+
# You can specify a starting page with <tt>:page</tt> (default is 1). Default
|
44
|
+
# <tt>:order</tt> is <tt>"id"</tt>, override if necessary.
|
45
|
+
#
|
46
|
+
# {Jamis Buck describes this}[http://weblog.jamisbuck.org/2007/4/6/faking-cursors-in-activerecord]
|
47
|
+
# and also uses a more efficient way for MySQL.
|
48
|
+
def paginated_each(options = {}, &block)
|
49
|
+
options = { :order => 'id', :page => 1 }.merge options
|
50
|
+
options[:page] = options[:page].to_i
|
51
|
+
options[:total_entries] = 0 # skip the individual count queries
|
52
|
+
total = 0
|
53
|
+
|
54
|
+
begin
|
55
|
+
collection = paginate(options)
|
56
|
+
total += collection.each(&block).size
|
57
|
+
options[:page] += 1
|
58
|
+
end until collection.size < collection.per_page
|
59
|
+
|
60
|
+
total
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
def leaf_parse_options(options)
|
66
|
+
raise ArgumentError, 'parameter hash expected' unless Hash === options
|
67
|
+
raise ArgumentError, ':page parameter required' unless options.key? :page
|
68
|
+
|
69
|
+
if options[:count] and options[:total_entries]
|
70
|
+
raise ArgumentError, ':count and :total_entries are mutually exclusive'
|
71
|
+
end
|
72
|
+
|
73
|
+
page = options[:page] || 1
|
74
|
+
per_page = options[:per_page] || self.per_page
|
75
|
+
total = options[:total_entries]
|
76
|
+
|
77
|
+
return [page, per_page, total]
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
require 'sequel/extensions/pagination'
|
3
|
+
require 'leaf/core_ext'
|
4
|
+
|
5
|
+
existing_methods = Sequel::Dataset::Pagination.instance_methods
|
6
|
+
|
7
|
+
Sequel::Dataset::Pagination.module_eval do
|
8
|
+
# it should quack like a Leaf::Collection
|
9
|
+
|
10
|
+
alias :total_pages :page_count unless existing_methods.include_method? :total_pages
|
11
|
+
alias :per_page :page_size unless existing_methods.include_method? :per_page
|
12
|
+
alias :previous_page :prev_page unless existing_methods.include_method? :previous_page
|
13
|
+
alias :total_entries :pagination_record_count unless existing_methods.include_method? :total_entries
|
14
|
+
|
15
|
+
def out_of_bounds?
|
16
|
+
current_page > total_pages
|
17
|
+
end
|
18
|
+
|
19
|
+
# Current offset of the paginated collection
|
20
|
+
def offset
|
21
|
+
(current_page - 1) * per_page
|
22
|
+
end
|
23
|
+
end
|
data/lib/leaf/version.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module Leaf
|
2
|
+
# = Leaf view helpers
|
3
|
+
#
|
4
|
+
# The main view helper is +leaf+. It renders the pagination links
|
5
|
+
# for the given collection.
|
6
|
+
#
|
7
|
+
# Read more in Leaf::ViewHelpers::Base
|
8
|
+
module ViewHelpers
|
9
|
+
# ==== Global options for helpers
|
10
|
+
#
|
11
|
+
# Options for pagination helpers are optional and get their default values
|
12
|
+
# from the Leaf::ViewHelpers.pagination_options hash. You can write
|
13
|
+
# to this hash to override default options on the global level:
|
14
|
+
#
|
15
|
+
# Leaf::ViewHelpers.pagination_options[:previous_label] = 'Previous page'
|
16
|
+
def self.pagination_options() @pagination_options; end
|
17
|
+
# Overrides the default +pagination_options+
|
18
|
+
def self.pagination_options=(value) @pagination_options = value; end
|
19
|
+
|
20
|
+
self.pagination_options = {
|
21
|
+
:class => 'pagination',
|
22
|
+
:previous_label => '← Previous',
|
23
|
+
:next_label => 'Next →',
|
24
|
+
:inner_window => 4, # links around the current page
|
25
|
+
:outer_window => 1, # links around beginning and end
|
26
|
+
:separator => ' ', # single space is friendly to spiders and non-graphic browsers
|
27
|
+
:param_name => :page,
|
28
|
+
:params => nil,
|
29
|
+
:renderer => 'Leaf::ViewHelpers::LinkRenderer',
|
30
|
+
:page_links => true,
|
31
|
+
:container => true
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'leaf/core_ext'
|
2
|
+
require 'leaf/view_helpers'
|
3
|
+
|
4
|
+
module Leaf
|
5
|
+
module ViewHelpers
|
6
|
+
# = The main view helpers module
|
7
|
+
#
|
8
|
+
# This is the base module which provides the +leaf+ view helper.
|
9
|
+
module Base
|
10
|
+
# Renders Digg/Flickr-style pagination for a Leaf::Collection object. Nil is
|
11
|
+
# returned if there is only one page in total; pagination links aren't needed in that case.
|
12
|
+
#
|
13
|
+
# ==== Options
|
14
|
+
# * <tt>:class</tt> -- CSS class name for the generated DIV (default: "pagination")
|
15
|
+
# * <tt>:previous_label</tt> -- default: "« Previous"
|
16
|
+
# * <tt>:next_label</tt> -- default: "Next »"
|
17
|
+
# * <tt>:inner_window</tt> -- how many links are shown around the current page (default: 4)
|
18
|
+
# * <tt>:outer_window</tt> -- how many links are around the first and the last page (default: 1)
|
19
|
+
# * <tt>:separator</tt> -- string separator for page HTML elements (default: single space)
|
20
|
+
# * <tt>:param_name</tt> -- parameter name for page number in URLs (default: <tt>:page</tt>)
|
21
|
+
# * <tt>:params</tt> -- additional parameters when generating pagination links
|
22
|
+
# (eg. <tt>:controller => "foo", :action => nil</tt>)
|
23
|
+
# * <tt>:renderer</tt> -- class name, class or instance of a link renderer (default:
|
24
|
+
# <tt>Leaf::LinkRenderer</tt>)
|
25
|
+
# * <tt>:page_links</tt> -- when false, only previous/next links are rendered (default: true)
|
26
|
+
# * <tt>:container</tt> -- toggles rendering of the DIV container for pagination links, set to
|
27
|
+
# false only when you are rendering your own pagination markup (default: true)
|
28
|
+
# * <tt>:id</tt> -- HTML ID for the container (default: nil). Pass +true+ to have the ID
|
29
|
+
# automatically generated from the class name of objects in collection: for example, paginating
|
30
|
+
# ArticleComment models would yield an ID of "article_comments_pagination".
|
31
|
+
#
|
32
|
+
# All options beside listed ones are passed as HTML attributes to the container
|
33
|
+
# element for pagination links (the DIV). For example:
|
34
|
+
#
|
35
|
+
# <%= leaf @posts, :id => 'leaf_posts' %>
|
36
|
+
#
|
37
|
+
# ... will result in:
|
38
|
+
#
|
39
|
+
# <div class="pagination" id="leaf_posts"> ... </div>
|
40
|
+
#
|
41
|
+
def leaf(collection, options = {})
|
42
|
+
# early exit if there is nothing to render
|
43
|
+
return nil unless collection.total_pages > 1
|
44
|
+
|
45
|
+
options = Leaf::ViewHelpers.pagination_options.merge(options)
|
46
|
+
|
47
|
+
# get the renderer instance
|
48
|
+
renderer = case options[:renderer]
|
49
|
+
when String then options[:renderer].constantize.new
|
50
|
+
when Class then options[:renderer].new
|
51
|
+
else options[:renderer]
|
52
|
+
end
|
53
|
+
|
54
|
+
# render HTML for pagination
|
55
|
+
renderer.prepare collection, options, self
|
56
|
+
renderer.to_html
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'leaf/core_ext'
|
2
|
+
require 'leaf/view_helpers/link_renderer_base'
|
3
|
+
|
4
|
+
module Leaf
|
5
|
+
module ViewHelpers
|
6
|
+
# This class does the heavy lifting of actually building the pagination
|
7
|
+
# links. It is used by +leaf+ helper internally.
|
8
|
+
class LinkRenderer < LinkRendererBase
|
9
|
+
|
10
|
+
# * +collection+ is a Leaf::Collection instance or any other object
|
11
|
+
# that conforms to that API
|
12
|
+
# * +options+ are forwarded from +leaf+ view helper
|
13
|
+
# * +template+ is the reference to the template being rendered
|
14
|
+
def prepare(collection, options, template)
|
15
|
+
super(collection, options)
|
16
|
+
@template = template
|
17
|
+
@container_attributes = @base_url_params = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
# Process it! This method returns the complete HTML string which contains
|
21
|
+
# pagination links. Feel free to subclass LinkRenderer and change this
|
22
|
+
# method as you see fit.
|
23
|
+
def to_html
|
24
|
+
html = pagination.map do |item|
|
25
|
+
item.is_a?(Fixnum) ?
|
26
|
+
page_number(item) :
|
27
|
+
send(item)
|
28
|
+
end.join(@options[:separator])
|
29
|
+
|
30
|
+
@options[:container] ? html_container(html) : html
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the subset of +options+ this instance was initialized with that
|
34
|
+
# represent HTML attributes for the container element of pagination links.
|
35
|
+
def container_attributes
|
36
|
+
@container_attributes ||= begin
|
37
|
+
attributes = @options.except *(Leaf::ViewHelpers.pagination_options.keys - [:class])
|
38
|
+
# pagination of Post models will have the ID of "posts_pagination"
|
39
|
+
if @options[:container] and @options[:id] === true
|
40
|
+
attributes[:id] = @collection.first.class.name.underscore.pluralize + '_pagination'
|
41
|
+
end
|
42
|
+
attributes
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def page_number(page)
|
49
|
+
unless page == current_page
|
50
|
+
link(page, page, :rel => rel_value(page))
|
51
|
+
else
|
52
|
+
tag(:em, page)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def gap
|
57
|
+
'<span class="gap">…</span>'
|
58
|
+
end
|
59
|
+
|
60
|
+
def previous_page
|
61
|
+
previous_or_next_page(@collection.previous_page, @options[:previous_label], 'previous_page')
|
62
|
+
end
|
63
|
+
|
64
|
+
def next_page
|
65
|
+
previous_or_next_page(@collection.next_page, @options[:next_label], 'next_page')
|
66
|
+
end
|
67
|
+
|
68
|
+
def previous_or_next_page(page, text, classname)
|
69
|
+
if page
|
70
|
+
link(text, page, :class => classname)
|
71
|
+
else
|
72
|
+
tag(:span, text, :class => classname + ' disabled')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def html_container(html)
|
77
|
+
tag(:div, html, container_attributes)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns URL params for +page_link_or_span+, taking the current GET params
|
81
|
+
# and <tt>:params</tt> option into account.
|
82
|
+
def url(page)
|
83
|
+
raise NotImplementedError
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def link(text, target, attributes = {})
|
89
|
+
if target.is_a? Fixnum
|
90
|
+
attributes[:rel] = rel_value(target)
|
91
|
+
target = url(target)
|
92
|
+
end
|
93
|
+
attributes[:href] = target
|
94
|
+
tag(:a, text, attributes)
|
95
|
+
end
|
96
|
+
|
97
|
+
def tag(name, value, attributes = {})
|
98
|
+
string_attributes = attributes.inject('') do |attrs, pair|
|
99
|
+
unless pair.last.nil?
|
100
|
+
attrs << %( #{pair.first}="#{Rack::Utils.escape_html(pair.last.to_s)}")
|
101
|
+
end
|
102
|
+
attrs
|
103
|
+
end
|
104
|
+
"<#{name}#{string_attributes}>#{value}</#{name}>"
|
105
|
+
end
|
106
|
+
|
107
|
+
def rel_value(page)
|
108
|
+
case page
|
109
|
+
when @collection.previous_page; 'prev' + (page == 1 ? ' start' : '')
|
110
|
+
when @collection.next_page; 'next'
|
111
|
+
when 1; 'start'
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def symbolized_update(target, other)
|
116
|
+
other.each do |key, value|
|
117
|
+
key = key.to_sym
|
118
|
+
existing = target[key]
|
119
|
+
|
120
|
+
if value.is_a?(Hash) and (existing.is_a?(Hash) or existing.nil?)
|
121
|
+
symbolized_update(existing || (target[key] = {}), value)
|
122
|
+
else
|
123
|
+
target[key] = value
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'leaf/view_helpers'
|
2
|
+
|
3
|
+
module Leaf
|
4
|
+
module ViewHelpers
|
5
|
+
# This class does the heavy lifting of actually building the pagination
|
6
|
+
# links. It is used by +leaf+ helper internally.
|
7
|
+
class LinkRendererBase
|
8
|
+
|
9
|
+
# * +collection+ is a Leaf::Collection instance or any other object
|
10
|
+
# that conforms to that API
|
11
|
+
# * +options+ are forwarded from +leaf+ view helper
|
12
|
+
def prepare(collection, options)
|
13
|
+
@collection = collection
|
14
|
+
@options = options
|
15
|
+
|
16
|
+
# reset values in case we're re-using this instance
|
17
|
+
@total_pages = @param_name = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def pagination
|
21
|
+
items = @options[:page_links] ? windowed_page_numbers : []
|
22
|
+
items.unshift :previous_page
|
23
|
+
items.push :next_page
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
# Calculates visible page numbers using the <tt>:inner_window</tt> and
|
29
|
+
# <tt>:outer_window</tt> options.
|
30
|
+
def windowed_page_numbers
|
31
|
+
inner_window, outer_window = @options[:inner_window].to_i, @options[:outer_window].to_i
|
32
|
+
window_from = current_page - inner_window
|
33
|
+
window_to = current_page + inner_window
|
34
|
+
|
35
|
+
# adjust lower or upper limit if other is out of bounds
|
36
|
+
if window_to > total_pages
|
37
|
+
window_from -= window_to - total_pages
|
38
|
+
window_to = total_pages
|
39
|
+
end
|
40
|
+
if window_from < 1
|
41
|
+
window_to += 1 - window_from
|
42
|
+
window_from = 1
|
43
|
+
window_to = total_pages if window_to > total_pages
|
44
|
+
end
|
45
|
+
|
46
|
+
# these are always visible
|
47
|
+
middle = window_from..window_to
|
48
|
+
|
49
|
+
# left window
|
50
|
+
if outer_window + 3 < middle.first # there's a gap
|
51
|
+
left = (1..(outer_window + 1)).to_a
|
52
|
+
left << :gap
|
53
|
+
else # runs into visible pages
|
54
|
+
left = 1...middle.first
|
55
|
+
end
|
56
|
+
|
57
|
+
# right window
|
58
|
+
if total_pages - outer_window - 2 > middle.last # again, gap
|
59
|
+
right = ((total_pages - outer_window)..total_pages).to_a
|
60
|
+
right.unshift :gap
|
61
|
+
else # runs into visible pages
|
62
|
+
right = (middle.last + 1)..total_pages
|
63
|
+
end
|
64
|
+
|
65
|
+
left.to_a + middle.to_a + right.to_a
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def current_page
|
71
|
+
@collection.current_page
|
72
|
+
end
|
73
|
+
|
74
|
+
def total_pages
|
75
|
+
@collection.total_pages
|
76
|
+
end
|
77
|
+
|
78
|
+
def param_name
|
79
|
+
@param_name ||= @options[:param_name].to_s
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'leaf/view_helpers/link_renderer'
|
2
|
+
|
3
|
+
module Leaf
|
4
|
+
module ViewHelpers
|
5
|
+
class ListRenderer < LinkRenderer
|
6
|
+
def to_html
|
7
|
+
html = pagination.map do |item|
|
8
|
+
"\n " + tag(:li, (item.is_a?(Fixnum) ?
|
9
|
+
page_number(item) :
|
10
|
+
send(item)))
|
11
|
+
end.join(@options[:separator])
|
12
|
+
|
13
|
+
@options[:container] ? html_container(html) : html
|
14
|
+
end
|
15
|
+
|
16
|
+
def previous_or_next_page(page, text, classname)
|
17
|
+
if page
|
18
|
+
link(tag(:span, text), page, :class => classname)
|
19
|
+
else
|
20
|
+
tag(:span, tag(:span, text), :class => classname + ' disabled')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def html_container(html)
|
25
|
+
tag(:div, "\n " +
|
26
|
+
tag(:ul, html + "\n ") + "\n", container_attributes) + "\n"
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def page_number(page)
|
31
|
+
unless page == current_page
|
32
|
+
link(tag(:span, page), page, :rel => rel_value(page))
|
33
|
+
else
|
34
|
+
tag(:em, tag(:span, page))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'leaf/view_helpers/base'
|
2
|
+
require 'leaf/view_helpers/link_renderer'
|
3
|
+
require 'leaf/view_helpers/list_renderer'
|
4
|
+
|
5
|
+
Leaf::ViewHelpers::LinkRenderer.class_eval do
|
6
|
+
protected
|
7
|
+
def url(page)
|
8
|
+
url = @template.request.url
|
9
|
+
if page == 1
|
10
|
+
# strip out page param and trailing ? and & if they exists
|
11
|
+
url.gsub(/(\?|\&)#{@options[:param_name]}=[0-9]+/, '').gsub(/\?$/, '').gsub(/\&$/, '')
|
12
|
+
else
|
13
|
+
if url =~ /(\?|\&)#{@options[:param_name]}=[0-9]+/
|
14
|
+
url.gsub(/#{@options[:param_name]}=[0-9]+/, "#{@options[:param_name]}=#{page}").gsub(/\&+/, '&')
|
15
|
+
else
|
16
|
+
(url =~ /\?/) ? url + "&#{@options[:param_name]}=#{page}" : url + "?#{@options[:param_name]}=#{page}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mjfreshyfresh-leaf
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Peter Hellberg
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-09-13 00:00:00 -07:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rack
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 1
|
29
|
+
- 2
|
30
|
+
- 0
|
31
|
+
version: 1.2.0
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
description: A really simple pagination library, heavily based on the agnostic branch of will_paginate
|
35
|
+
email: peter@c7.se
|
36
|
+
executables: []
|
37
|
+
|
38
|
+
extensions: []
|
39
|
+
|
40
|
+
extra_rdoc_files:
|
41
|
+
- README.rdoc
|
42
|
+
- MIT-LICENSE
|
43
|
+
files:
|
44
|
+
- lib/leaf/array.rb
|
45
|
+
- lib/leaf/collection.rb
|
46
|
+
- lib/leaf/core_ext.rb
|
47
|
+
- lib/leaf/finders/base.rb
|
48
|
+
- lib/leaf/finders/sequel.rb
|
49
|
+
- lib/leaf/finders.rb
|
50
|
+
- lib/leaf/version.rb
|
51
|
+
- lib/leaf/view_helpers/base.rb
|
52
|
+
- lib/leaf/view_helpers/link_renderer.rb
|
53
|
+
- lib/leaf/view_helpers/link_renderer_base.rb
|
54
|
+
- lib/leaf/view_helpers/list_renderer.rb
|
55
|
+
- lib/leaf/view_helpers/sinatra.rb
|
56
|
+
- lib/leaf/view_helpers.rb
|
57
|
+
- lib/leaf.rb
|
58
|
+
- MIT-LICENSE
|
59
|
+
- Rakefile
|
60
|
+
- README.rdoc
|
61
|
+
has_rdoc: true
|
62
|
+
homepage: http://c7.github.com/leaf/
|
63
|
+
licenses:
|
64
|
+
- MIT-LICENSE
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options:
|
67
|
+
- --main
|
68
|
+
- README.rdoc
|
69
|
+
- --charset=UTF-8
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
segments:
|
77
|
+
- 0
|
78
|
+
version: "0"
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
segments:
|
84
|
+
- 0
|
85
|
+
version: "0"
|
86
|
+
requirements: []
|
87
|
+
|
88
|
+
rubyforge_project: leaf
|
89
|
+
rubygems_version: 1.3.6
|
90
|
+
signing_key:
|
91
|
+
specification_version: 3
|
92
|
+
summary: Simple pagination library
|
93
|
+
test_files: []
|
94
|
+
|