godfat-pagify 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +31 -0
- data/LICENSE +201 -0
- data/NOTICE +5 -0
- data/README +121 -0
- data/Rakefile +62 -0
- data/TODO +4 -0
- data/lib/pagify.rb +9 -0
- data/lib/pagify/active_record.rb +19 -0
- data/lib/pagify/array.rb +6 -0
- data/lib/pagify/basic.rb +3 -0
- data/lib/pagify/data_mapper.rb +16 -0
- data/lib/pagify/helpers/abstract.rb +92 -0
- data/lib/pagify/helpers/details/setting.rb +54 -0
- data/lib/pagify/helpers/details/setup.rb +23 -0
- data/lib/pagify/helpers/html.rb +85 -0
- data/lib/pagify/null.rb +5 -0
- data/lib/pagify/pagers/active_record.rb +32 -0
- data/lib/pagify/pagers/array.rb +22 -0
- data/lib/pagify/pagers/basic.rb +114 -0
- data/lib/pagify/pagers/data_mapper.rb +30 -0
- data/lib/pagify/pagers/details/page_accept_string_or_blank.rb +14 -0
- data/lib/pagify/pagers/null.rb +17 -0
- data/lib/pagify/pages/basic.rb +58 -0
- data/lib/pagify/pages/null.rb +11 -0
- data/lib/pagify/pagifiers/abstract.rb +34 -0
- data/lib/pagify/pagifiers/active_record.rb +11 -0
- data/lib/pagify/pagifiers/array.rb +9 -0
- data/lib/pagify/pagifiers/data_mapper.rb +11 -0
- data/lib/pagify/version.rb +4 -0
- data/pagify.gemspec +51 -0
- data/spec/pagify_spec.rb +8 -0
- data/spec/spec_helper.rb +17 -0
- data/test/helper.rb +14 -0
- data/test/suite_for_model.rb +125 -0
- data/test/test_active_record.rb +71 -0
- data/test/test_array.rb +19 -0
- data/test/test_basic.rb +22 -0
- data/test/test_data_mapper.rb +55 -0
- data/test/test_html.rb +160 -0
- data/test/test_null.rb +22 -0
- data/test/test_pagify.rb +41 -0
- metadata +176 -0
data/lib/pagify/array.rb
ADDED
data/lib/pagify/basic.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
# anything for data_mapper
|
3
|
+
|
4
|
+
gem 'extlib', '<=0.9.8'
|
5
|
+
gem 'dm-core', '<=0.9.7'
|
6
|
+
gem 'dm-aggregates', '<=0.9.7'
|
7
|
+
|
8
|
+
require 'dm-core'
|
9
|
+
require 'dm-aggregates'
|
10
|
+
|
11
|
+
require 'pagify'
|
12
|
+
require 'pagify/pagers/details/page_accept_string_or_blank'
|
13
|
+
|
14
|
+
require 'pagify/pagers/data_mapper'
|
15
|
+
require 'pagify/pagifiers/data_mapper'
|
16
|
+
|
@@ -0,0 +1,92 @@
|
|
1
|
+
|
2
|
+
module Pagify
|
3
|
+
module Helpers
|
4
|
+
class Abstract
|
5
|
+
def self.default_attributes
|
6
|
+
raise NotImplementedError
|
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_prev(outer_prev, inner_prev),
|
37
|
+
caculate_post(inner_post, outer_post) ]
|
38
|
+
end
|
39
|
+
def caculate_prev outer_prev, inner_prev
|
40
|
+
# concat outer and inner, remove overlap
|
41
|
+
links_prev = outer_prev + [setting[:ellipsis]] + inner_prev
|
42
|
+
|
43
|
+
# clean up overlap and remove '...' if there's overlap or no pages there
|
44
|
+
links_prev.delete(setting[:ellipsis]) if links_prev.uniq! || links_prev.size == 1
|
45
|
+
|
46
|
+
links_prev
|
47
|
+
end
|
48
|
+
def caculate_post inner_post, outer_post
|
49
|
+
# concat outer and inner, remove overlap
|
50
|
+
links_post = inner_post + [setting[:ellipsis]] + outer_post
|
51
|
+
|
52
|
+
# clean up overlap and remove '...' if there's overlap or no pages there
|
53
|
+
links_post.delete(setting[:ellipsis]) if links_post.uniq! || links_post.size == 1
|
54
|
+
|
55
|
+
links_post
|
56
|
+
end
|
57
|
+
def caculate_4_parts page, size
|
58
|
+
inner_prev, inner_post = caculate_inner(setting[:inner_links], page, size)
|
59
|
+
outer_prev, outer_post = caculate_outer(setting[:outer_links], page, size,
|
60
|
+
inner_prev, inner_post)
|
61
|
+
|
62
|
+
[outer_prev, inner_prev, inner_post, outer_post]
|
63
|
+
end
|
64
|
+
def caculate_inner limit, page, size
|
65
|
+
[ (1..limit).map{ |i| page_exists?(page - i, size) }.reverse.compact,
|
66
|
+
(1..limit).map{ |i| page_exists?(page + i, size) }.compact ]
|
67
|
+
end
|
68
|
+
def caculate_outer limit, page, size, inner_prev, inner_post
|
69
|
+
# caculate index
|
70
|
+
prev_last = [limit, (inner_prev.first || 1) ].min
|
71
|
+
post_first = [size - limit + 1, (inner_post.last || size)].max
|
72
|
+
|
73
|
+
# create outer
|
74
|
+
outer_prev, outer_post = [ ( 1 .. prev_last ).to_a,
|
75
|
+
( post_first .. size ).to_a ]
|
76
|
+
|
77
|
+
# remove current page
|
78
|
+
outer_prev.delete(page)
|
79
|
+
outer_post.delete(page)
|
80
|
+
|
81
|
+
[outer_prev, outer_post]
|
82
|
+
end
|
83
|
+
def page_exists? page, size
|
84
|
+
if page >= 1 && page <= size
|
85
|
+
page
|
86
|
+
else
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
require 'pagify/helpers/abstract'
|
3
|
+
|
4
|
+
module Pagify
|
5
|
+
module Helpers
|
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
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
module Pagify
|
3
|
+
module Helpers
|
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,85 @@
|
|
1
|
+
|
2
|
+
require 'pagify/helpers/abstract'
|
3
|
+
|
4
|
+
require 'pagify/helpers/details/setting'
|
5
|
+
require 'pagify/helpers/details/setup'
|
6
|
+
|
7
|
+
module Pagify
|
8
|
+
module Helpers
|
9
|
+
class HTML < Abstract
|
10
|
+
def self.default_attributes
|
11
|
+
{ :first_text => '« First',
|
12
|
+
:last_text => 'Last »',
|
13
|
+
:prev_text => '< Previous',
|
14
|
+
:next_text => 'Next >',
|
15
|
+
:inner_links => 4,
|
16
|
+
:outer_links => 2,
|
17
|
+
# :step => 3,
|
18
|
+
:separator => ' ',
|
19
|
+
:ellipsis => '...' }
|
20
|
+
end
|
21
|
+
|
22
|
+
def links_full page, &block
|
23
|
+
page = normalize_page(page)
|
24
|
+
"#{links_navigate(page, &block)}<br />\n#{links(page, &block)}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def links_navigate page
|
28
|
+
page = normalize_page(page)
|
29
|
+
size = pager.size
|
30
|
+
attrs = extract_html_attributes
|
31
|
+
|
32
|
+
prev = if page > 1 && page_exists?(page - 1, size)
|
33
|
+
"<a href=\"#{yield(page - 1)}\"#{attrs}>#{setting[:prev_text]}</a>"
|
34
|
+
else
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
post = if page < size && page_exists?(page + 1, size)
|
39
|
+
"<a href=\"#{yield(page + 1)}\"#{attrs}>#{setting[:next_text]}</a>"
|
40
|
+
else
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
[prev, post].compact.join(setting[:separator])
|
45
|
+
end
|
46
|
+
|
47
|
+
def links page
|
48
|
+
page = normalize_page(page)
|
49
|
+
size = pager.size
|
50
|
+
attrs = extract_html_attributes
|
51
|
+
|
52
|
+
prepare_links(page).map{ |i|
|
53
|
+
if i == page
|
54
|
+
case page
|
55
|
+
when 1; setting[:first_text]
|
56
|
+
when size; setting[ :last_text]
|
57
|
+
else; page
|
58
|
+
end
|
59
|
+
else
|
60
|
+
case i
|
61
|
+
when 1; "<a href=\"#{yield(i)}\"#{attrs}>#{setting[:first_text]}</a>"
|
62
|
+
when size; "<a href=\"#{yield(i)}\"#{attrs}>#{setting[ :last_text]}</a>"
|
63
|
+
when Fixnum; "<a href=\"#{yield(i)}\"#{attrs}>#{i}</a>"
|
64
|
+
else; i.to_s
|
65
|
+
end
|
66
|
+
end
|
67
|
+
}.join(setting[:separator])
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
def extract_html_attributes
|
72
|
+
attrs = setting.additional_attributes.
|
73
|
+
map{ |key, value| "#{key}=\"#{value}\"" }.join(' ')
|
74
|
+
|
75
|
+
attrs = ' ' + attrs if attrs != ''
|
76
|
+
attrs
|
77
|
+
end
|
78
|
+
|
79
|
+
def normalize_page page
|
80
|
+
pager.__send__(:normalize_page, page)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
setup HTML
|
84
|
+
end
|
85
|
+
end
|
data/lib/pagify/null.rb
ADDED
@@ -0,0 +1,32 @@
|
|
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
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
module Pagify
|
3
|
+
# array paginator would just simply assume your data is an array,
|
4
|
+
# and create pages simply for your_data[offset, per_page]
|
5
|
+
# if your data is much more complex, use Paginator instead of this
|
6
|
+
class ArrayPager < BasicPager
|
7
|
+
attr_reader :data
|
8
|
+
|
9
|
+
# data that you passed in this paginator
|
10
|
+
def initialize data, opts = {}
|
11
|
+
@data = data
|
12
|
+
super(opts.merge(
|
13
|
+
:fetcher => lambda{ |offset, per_page|
|
14
|
+
data[offset, per_page]
|
15
|
+
},
|
16
|
+
:counter => lambda{
|
17
|
+
data.size
|
18
|
+
}))
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
|
2
|
+
module Pagify
|
3
|
+
# pager is something help you paginate the data,
|
4
|
+
# through the fetcher and counter you pass in.
|
5
|
+
# e.g., for a array, the fetcher would be array[offset, per_page],
|
6
|
+
# the counter would be array.size. and for data mapper's resource,
|
7
|
+
# fetcher would be Model.all :offset => offset, :limit => per_page.
|
8
|
+
# see ArrayPager and DataMapperPager, ActiveRecordPager, etc.
|
9
|
+
# if you just simply need them or have a look at the source code
|
10
|
+
# for example usage for Pager.
|
11
|
+
class BasicPager
|
12
|
+
include Enumerable
|
13
|
+
attr_reader :fetcher, :counter
|
14
|
+
attr_accessor :opts
|
15
|
+
|
16
|
+
[:per_page, :null_page].each{ |attr|
|
17
|
+
define_method(attr) do
|
18
|
+
opts[attr]
|
19
|
+
end
|
20
|
+
|
21
|
+
define_method("#{attr}=") do |new_value|
|
22
|
+
opts[attr] = new_value
|
23
|
+
end
|
24
|
+
}
|
25
|
+
|
26
|
+
# fetcher is function that fetch the data,
|
27
|
+
# counter is function that count the data,
|
28
|
+
# null_page indicates that pager sohuld
|
29
|
+
# the default per_page is 20. you can reset per_page property later.
|
30
|
+
def initialize opts = {}
|
31
|
+
raise ArgumentError.new('missing fetcher and/or counter.') if
|
32
|
+
!opts[:fetcher] || !opts[:counter]
|
33
|
+
|
34
|
+
raise ArgumentError.new('fetcher or counter does not respond to call.') if
|
35
|
+
!opts[:fetcher].respond_to?(:call) || !opts[:counter].respond_to?(:call)
|
36
|
+
|
37
|
+
@opts = {}
|
38
|
+
@fetcher = opts[:fetcher]
|
39
|
+
@counter = opts[:counter]
|
40
|
+
|
41
|
+
self.per_page = opts[:per_page] || 20
|
42
|
+
self.null_page = opts[:null_page] || true
|
43
|
+
end
|
44
|
+
|
45
|
+
# if two paginators are equal, then the properties of
|
46
|
+
# per_page, fetcher, counter are all equal.
|
47
|
+
def == rhs
|
48
|
+
return false unless rhs
|
49
|
+
self.per_page == rhs.per_page and
|
50
|
+
self.fetcher == rhs.fetcher and
|
51
|
+
self.counter == rhs.counter
|
52
|
+
end
|
53
|
+
|
54
|
+
# for each page...
|
55
|
+
def each; 1.upto(size){ |i| yield page(i) }; end
|
56
|
+
|
57
|
+
# return all pages in an array
|
58
|
+
def to_a; map{ |e| e }; end
|
59
|
+
alias_method :pages, :to_a
|
60
|
+
|
61
|
+
def page_exists? page
|
62
|
+
page = normalize_page(page)
|
63
|
+
offset = (page - 1) * per_page
|
64
|
+
page > 0 && offset < entries_count
|
65
|
+
end
|
66
|
+
|
67
|
+
# create page instance by page number.
|
68
|
+
# if page number you specified was not existed,
|
69
|
+
# nil would be returned. note, page start at 1, not zero.
|
70
|
+
def page page
|
71
|
+
if page_exists?(page)
|
72
|
+
return BasicPage.new(self, normalize_page(page))
|
73
|
+
|
74
|
+
else
|
75
|
+
if null_page
|
76
|
+
return null_page_instance
|
77
|
+
else
|
78
|
+
return nil
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
alias_method :[], :page
|
85
|
+
|
86
|
+
# return the amount of pages
|
87
|
+
def size; (entries_count / per_page.to_f).ceil; end
|
88
|
+
|
89
|
+
# simply call @counter.call
|
90
|
+
def entries_count; counter.call; end
|
91
|
+
|
92
|
+
# get the offset property about the page.
|
93
|
+
# it is simply (page-1)*@per_page
|
94
|
+
def offset page
|
95
|
+
(normalize_page(page) - 1) * per_page
|
96
|
+
end
|
97
|
+
|
98
|
+
protected
|
99
|
+
def reject_pager_opts opts
|
100
|
+
opts.reject{ |key, value| [:per_page, :null_page].member?(key) }
|
101
|
+
end
|
102
|
+
|
103
|
+
# do nothing for basic pager
|
104
|
+
def normalize_page page
|
105
|
+
page
|
106
|
+
end
|
107
|
+
|
108
|
+
# return a null pager that stubs anything to 0
|
109
|
+
def null_page_instance
|
110
|
+
@null_page_instance ||= NullPage.new(self)
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|