godfat-pagify 0.5.0
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 +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
|