pagify 0.6.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/CHANGES +42 -0
- data/LICENSE +201 -0
- data/NOTICE +5 -0
- data/README +135 -0
- data/Rakefile +54 -0
- data/TODO +12 -0
- data/lib/pagify.rb +14 -0
- data/lib/pagify/active_record.rb +17 -0
- data/lib/pagify/array.rb +6 -0
- data/lib/pagify/data_mapper.rb +8 -0
- data/lib/pagify/helper/abstract.rb +112 -0
- data/lib/pagify/helper/detail/setting.rb +54 -0
- data/lib/pagify/helper/detail/setup.rb +23 -0
- data/lib/pagify/helper/html.rb +86 -0
- data/lib/pagify/helper/rails.rb +23 -0
- data/lib/pagify/page/basic.rb +56 -0
- data/lib/pagify/page/null.rb +10 -0
- data/lib/pagify/pager/active_record.rb +30 -0
- data/lib/pagify/pager/array.rb +21 -0
- data/lib/pagify/pager/basic.rb +114 -0
- data/lib/pagify/pager/data_mapper.rb +29 -0
- data/lib/pagify/pager/detail/page_accept_string_or_blank.rb +13 -0
- data/lib/pagify/pager/null.rb +15 -0
- data/lib/pagify/pagifier/abstract.rb +10 -0
- data/lib/pagify/pagifier/active_record.rb +11 -0
- data/lib/pagify/pagifier/array.rb +9 -0
- data/lib/pagify/pagifier/data_mapper.rb +11 -0
- data/lib/pagify/version.rb +4 -0
- data/pagify.gemspec +43 -0
- data/spec/pagify_spec.rb +8 -0
- data/spec/spec_helper.rb +17 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +292 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/tasks/zentest.rake +36 -0
- data/test/helper.rb +26 -0
- data/test/helper_model.rb +83 -0
- data/test/helper_pagify.rb +39 -0
- data/test/test_active_record.rb +66 -0
- data/test/test_array.rb +19 -0
- data/test/test_basic.rb +22 -0
- data/test/test_data_mapper.rb +56 -0
- data/test/test_html.rb +186 -0
- data/test/test_null.rb +31 -0
- data/test/test_rails.rb +32 -0
- metadata +163 -0
data/lib/pagify.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
module Pagify
|
3
|
+
autoload :ArrayPager, 'pagify/array'
|
4
|
+
autoload :DataMapperPager, 'pagify/data_mapper'
|
5
|
+
autoload :ActiveRecordPager, 'pagify/active_record'
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'pagify/pager/basic'
|
9
|
+
require 'pagify/page/basic'
|
10
|
+
|
11
|
+
require 'singleton'
|
12
|
+
|
13
|
+
require 'pagify/pager/null'
|
14
|
+
require 'pagify/page/null'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
# anything for active_record
|
3
|
+
|
4
|
+
require 'pagify'
|
5
|
+
require 'pagify/pager/detail/page_accept_string_or_blank'
|
6
|
+
|
7
|
+
require 'pagify/pager/active_record'
|
8
|
+
require 'pagify/pagifier/active_record'
|
9
|
+
|
10
|
+
require 'pagify/array' # this was needed for active_record...
|
11
|
+
|
12
|
+
# workaround for active_record...
|
13
|
+
module Pagify
|
14
|
+
class ArrayPager < BasicPager
|
15
|
+
include PageAcceptStringOrBlank
|
16
|
+
end
|
17
|
+
end
|
data/lib/pagify/array.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
|
2
|
+
module Pagify
|
3
|
+
module Helper
|
4
|
+
class Abstract
|
5
|
+
def self.default_attributes
|
6
|
+
{}
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.setting
|
10
|
+
@setting ||= Setting.new(self)
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :pager
|
14
|
+
def initialize pager
|
15
|
+
@pager = pager
|
16
|
+
end
|
17
|
+
|
18
|
+
def setting
|
19
|
+
@setting ||= Setting.new(self, self.class.setting)
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
def prepare_links page
|
24
|
+
size = pager.size
|
25
|
+
return [] if page < 1 || page > size
|
26
|
+
|
27
|
+
links_prev, links_post = caculate_2_parts(*caculate_4_parts(page, size))
|
28
|
+
|
29
|
+
current = links_prev.empty? && links_post.empty? ? [] : [page]
|
30
|
+
|
31
|
+
links_prev + current + links_post
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def caculate_2_parts outer_prev, inner_prev, inner_post, outer_post
|
36
|
+
[ caculate_part(outer_prev, inner_prev),
|
37
|
+
caculate_part(inner_post, outer_post) ]
|
38
|
+
end
|
39
|
+
|
40
|
+
# concat outer and inner, remove overlap
|
41
|
+
def caculate_part left, right
|
42
|
+
# don't use ellipsis if there's no spaces between them
|
43
|
+
ellipsis = !left.empty? && (left.last + 1) == right.first ?
|
44
|
+
[] : [setting[:ellipsis]]
|
45
|
+
|
46
|
+
part = left + ellipsis + right
|
47
|
+
|
48
|
+
# clean up overlap and remove '...' if
|
49
|
+
part.delete(setting[:ellipsis]) if
|
50
|
+
part.uniq! || # there's overlap
|
51
|
+
part.size == 1 # no pages there
|
52
|
+
|
53
|
+
part
|
54
|
+
end
|
55
|
+
|
56
|
+
def caculate_4_parts page, size
|
57
|
+
inner_prev, inner_post = caculate_inner(setting[:inner_links], page, size)
|
58
|
+
outer_prev, outer_post = caculate_outer(setting[:outer_links], page, size,
|
59
|
+
inner_prev, inner_post)
|
60
|
+
|
61
|
+
[outer_prev, inner_prev, inner_post, outer_post]
|
62
|
+
end
|
63
|
+
|
64
|
+
def caculate_inner limit, page, size
|
65
|
+
prev, post = page - limit, page + limit
|
66
|
+
|
67
|
+
left = extract_pages( prev ... page, size )
|
68
|
+
right = extract_pages( (page + 1) .. post, size )
|
69
|
+
|
70
|
+
# adding more right pages if left pages were not enough.
|
71
|
+
right.push(
|
72
|
+
*extract_pages(
|
73
|
+
(post + 1) .. (post + limit - left.size), size )) if left.size < limit
|
74
|
+
|
75
|
+
# ditto
|
76
|
+
left.unshift(
|
77
|
+
*extract_pages(
|
78
|
+
(prev - limit - right.size) .. (prev - 1), size )) if right.size < limit
|
79
|
+
|
80
|
+
[left, right]
|
81
|
+
end
|
82
|
+
|
83
|
+
def caculate_outer limit, page, size, inner_prev, inner_post
|
84
|
+
# caculate index
|
85
|
+
prev_last = [limit, (inner_prev.first || 1) ].min
|
86
|
+
post_first = [size - limit + 1, (inner_post.last || size)].max
|
87
|
+
|
88
|
+
# create outer
|
89
|
+
outer_prev, outer_post = [ ( 1 .. prev_last ).to_a,
|
90
|
+
( post_first .. size ).to_a ]
|
91
|
+
|
92
|
+
# remove current page
|
93
|
+
outer_prev.delete(page)
|
94
|
+
outer_post.delete(page)
|
95
|
+
|
96
|
+
[outer_prev, outer_post]
|
97
|
+
end
|
98
|
+
|
99
|
+
def extract_pages range, size
|
100
|
+
range.map{ |i| page_exists?(i, size) }.compact
|
101
|
+
end
|
102
|
+
|
103
|
+
def page_exists? page, size
|
104
|
+
if page >= 1 && page <= size
|
105
|
+
page
|
106
|
+
else
|
107
|
+
nil
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
require 'pagify/helper/abstract'
|
3
|
+
|
4
|
+
module Pagify
|
5
|
+
module Helper
|
6
|
+
# TODO: refactor me!!
|
7
|
+
class Setting
|
8
|
+
def initialize helper, parent = {}
|
9
|
+
@helper = helper
|
10
|
+
@parent = parent
|
11
|
+
@attributes = extract_default_attributes helper
|
12
|
+
end
|
13
|
+
|
14
|
+
def [] key
|
15
|
+
if @attributes[key]
|
16
|
+
@attributes[key]
|
17
|
+
else
|
18
|
+
@parent[key]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def []= key, value
|
23
|
+
@attributes[key] = value
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_hash
|
27
|
+
@attributes.dup
|
28
|
+
end
|
29
|
+
|
30
|
+
def restore_default!
|
31
|
+
@attributes = extract_default_attributes @helper
|
32
|
+
end
|
33
|
+
|
34
|
+
def additional_attributes
|
35
|
+
defaults = extract_default_attributes(
|
36
|
+
@helper.kind_of?(Abstract) ? @helper.class : @helper).keys
|
37
|
+
|
38
|
+
@parent.to_hash.merge(@attributes).reject{ |key, value| defaults.member?(key) }
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def extract_default_attributes helper
|
43
|
+
if helper.respond_to? :default_attributes
|
44
|
+
helper.default_attributes
|
45
|
+
else
|
46
|
+
{}
|
47
|
+
end
|
48
|
+
rescue NotImplementedError
|
49
|
+
{}
|
50
|
+
end
|
51
|
+
|
52
|
+
end # of Setting
|
53
|
+
end # of Helper
|
54
|
+
end # of Pagify
|
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
module Pagify
|
3
|
+
module Helper
|
4
|
+
def self.setup helper_class
|
5
|
+
Pagify::BasicPager.module_eval do
|
6
|
+
helper_name = helper_class.to_s.downcase[/::?(\w+)$/, 1]
|
7
|
+
define_method helper_name do
|
8
|
+
variable_name = "@helper_#{helper_name}"
|
9
|
+
instance_variable_get(variable_name) or
|
10
|
+
instance_variable_set(variable_name, helper_class.new(self))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# hmm.... how should i define this method?
|
15
|
+
# Pagify::BasicPage.module_eval do
|
16
|
+
# helper_name = helper_class.to_s.downcase[/::?(\w+)$/, 1]
|
17
|
+
# define_method helper_name do
|
18
|
+
# pager.__send__ helper_name
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
|
2
|
+
require 'pagify/helper/abstract'
|
3
|
+
|
4
|
+
require 'pagify/helper/detail/setting'
|
5
|
+
require 'pagify/helper/detail/setup'
|
6
|
+
|
7
|
+
module Pagify
|
8
|
+
module Helper
|
9
|
+
class HTML < Abstract
|
10
|
+
def self.default_attributes
|
11
|
+
super.merge(
|
12
|
+
{ :first_text => '« First',
|
13
|
+
:last_text => 'Last »',
|
14
|
+
:prev_text => '< Previous',
|
15
|
+
:next_text => 'Next >',
|
16
|
+
:inner_links => 4,
|
17
|
+
:outer_links => 2,
|
18
|
+
# :step => 3,
|
19
|
+
:separator => ' ',
|
20
|
+
:ellipsis => '...' })
|
21
|
+
end
|
22
|
+
|
23
|
+
def links_full page, &block
|
24
|
+
page = normalize_page(page)
|
25
|
+
"#{links_navigate(page, &block)}<br />#{links(page, &block)}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def links_navigate page
|
29
|
+
page = normalize_page(page)
|
30
|
+
size = pager.size
|
31
|
+
attrs = extract_html_attributes
|
32
|
+
|
33
|
+
prev = if page > 1 && page_exists?(page - 1, size)
|
34
|
+
"<a href=\"#{yield(page - 1)}\"#{attrs}>#{setting[:prev_text]}</a>"
|
35
|
+
else
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
post = if page < size && page_exists?(page + 1, size)
|
40
|
+
"<a href=\"#{yield(page + 1)}\"#{attrs}>#{setting[:next_text]}</a>"
|
41
|
+
else
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
[prev, post].compact.join(setting[:separator])
|
46
|
+
end
|
47
|
+
|
48
|
+
def links page
|
49
|
+
page = normalize_page(page)
|
50
|
+
size = pager.size
|
51
|
+
attrs = extract_html_attributes
|
52
|
+
|
53
|
+
prepare_links(page).map{ |i|
|
54
|
+
if i == page
|
55
|
+
case page
|
56
|
+
when 1; setting[:first_text]
|
57
|
+
when size; setting[ :last_text]
|
58
|
+
else; page
|
59
|
+
end
|
60
|
+
else
|
61
|
+
case i
|
62
|
+
when 1; "<a href=\"#{yield(i)}\"#{attrs}>#{setting[:first_text]}</a>"
|
63
|
+
when size; "<a href=\"#{yield(i)}\"#{attrs}>#{setting[ :last_text]}</a>"
|
64
|
+
when Fixnum; "<a href=\"#{yield(i)}\"#{attrs}>#{i}</a>"
|
65
|
+
else; i.to_s
|
66
|
+
end
|
67
|
+
end
|
68
|
+
}.join(setting[:separator])
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
def extract_html_attributes
|
73
|
+
attrs = setting.additional_attributes.
|
74
|
+
map{ |key, value| "#{key}=\"#{value}\"" }.join(' ')
|
75
|
+
|
76
|
+
attrs = ' ' + attrs if attrs != ''
|
77
|
+
attrs
|
78
|
+
end
|
79
|
+
|
80
|
+
def normalize_page page
|
81
|
+
pager.__send__(:normalize_page, page)
|
82
|
+
end
|
83
|
+
end # of HTML
|
84
|
+
setup HTML
|
85
|
+
end # of Helper
|
86
|
+
end # of Pagify
|
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
require 'pagify/helper/html'
|
3
|
+
|
4
|
+
module Pagify::Helper::Rails
|
5
|
+
def default_attributes
|
6
|
+
super.merge(:query_name => :page, :wrapper_class => 'pagination')
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Pagify::Helper::HTML
|
11
|
+
extend Pagify::Helper::Rails
|
12
|
+
end
|
13
|
+
|
14
|
+
module ApplicationHelper
|
15
|
+
def would_paginate objs, &path
|
16
|
+
path = lambda{ request.path } unless block_given?
|
17
|
+
html = objs.pager.html
|
18
|
+
name = html.setting[:query_name]
|
19
|
+
"<div class=\"#{html.setting[:wrapper_class]}\">" +
|
20
|
+
html.links_full(params[name]){ |p| path.call + "?#{name}=#{p}" } +
|
21
|
+
'</div>'
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
|
2
|
+
module Pagify
|
3
|
+
# which was produced by Paginator#page / Paginator#[],
|
4
|
+
# representing page that contained target data.
|
5
|
+
# it would use lazy fetching, whenever you need the data,
|
6
|
+
# the fetcher would be invoked that time. whenever the data
|
7
|
+
# was fetched, it won't fetch again. it you need refetch,
|
8
|
+
# call Page#fetch
|
9
|
+
class BasicPage
|
10
|
+
# prvent Page#to_a return [#<Page:0x12345>]
|
11
|
+
undef_method :to_a if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('1.9.0')
|
12
|
+
|
13
|
+
# pager to get the original pager; page to get the number of this page
|
14
|
+
attr_reader :pager, :page
|
15
|
+
|
16
|
+
# don't create a page instance yourself unless you have to
|
17
|
+
def initialize pager, page; @pager, @page = pager, page; end
|
18
|
+
|
19
|
+
# return the page instance next to this page
|
20
|
+
def next; pager.page(page + 1); end
|
21
|
+
|
22
|
+
# return the page instance prev to this page
|
23
|
+
def prev; pager.page(page - 1); end
|
24
|
+
|
25
|
+
# if the page numbers and the pagers are equal,
|
26
|
+
# then the pages are equal.
|
27
|
+
def == rhs
|
28
|
+
rhs.kind_of?(BasicPage) &&
|
29
|
+
page == rhs.page &&
|
30
|
+
pager == rhs.pager
|
31
|
+
end
|
32
|
+
|
33
|
+
# explicitly fetch the data from pager
|
34
|
+
def fetch; @data = pager.fetcher[self.begin, pager.per_page]; end
|
35
|
+
|
36
|
+
# get the data fetched from pager
|
37
|
+
def data; @data ||= fetch; end
|
38
|
+
|
39
|
+
# the real beginning index
|
40
|
+
def begin; pager.offset page; end
|
41
|
+
|
42
|
+
# the real ending index (need fetch, because we don't know the size)
|
43
|
+
def end; self.begin + self.size - 1; end
|
44
|
+
|
45
|
+
# any other method call would delegate to the data,
|
46
|
+
# e.g., each, to_a, map, first, method_you_defined, etc.
|
47
|
+
def method_missing msg, *args, &block
|
48
|
+
if data.respond_to?(msg)
|
49
|
+
data.__send__(msg, *args, &block)
|
50
|
+
else
|
51
|
+
super(msg, *args, &block)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end # of BasicPage
|
56
|
+
end # of Pagify
|
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
# you'll need activerecord to use ActiveRecordPager
|
3
|
+
|
4
|
+
module Pagify
|
5
|
+
# active_record paginator was provided for convenience,
|
6
|
+
# it wraps Paginator for you, and you can just pass the model class to it.
|
7
|
+
# you don't have to care about the fetcher and counter.
|
8
|
+
# additionally, you can pass other options to rails paginator,
|
9
|
+
# they would be used in find options. e.g.,
|
10
|
+
# ActiveRecordPaginator.new Model, :order => 'created_at DESC'
|
11
|
+
# would invoke Model.find :all, :offset => ?, :limit => ?, order => 'created_at DESC'
|
12
|
+
class ActiveRecordPager < BasicPager
|
13
|
+
include PageAcceptStringOrBlank
|
14
|
+
# the model class that you passed in this paginator
|
15
|
+
attr_reader :model
|
16
|
+
|
17
|
+
def initialize model_class, opts = {}
|
18
|
+
@model = model_class
|
19
|
+
query_opts = reject_pager_opts(opts)
|
20
|
+
|
21
|
+
super(opts.merge(
|
22
|
+
:fetcher => lambda{ |offset, per_page|
|
23
|
+
model.find(:all, query_opts.merge(:offset => offset, :limit => per_page))
|
24
|
+
},
|
25
|
+
:counter => lambda{
|
26
|
+
model.count(query_opts)
|
27
|
+
}))
|
28
|
+
end
|
29
|
+
end # of ActiveRecordPager
|
30
|
+
end # of Pagify
|