leaf 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.
@@ -0,0 +1,4 @@
1
+ # Leaf
2
+
3
+ A _really_ simple pagination library, heavily based on the agnostic branch
4
+ of [will_paginate](http://github.com/mislav/will_paginate/tree/agnostic)
@@ -0,0 +1 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../../lib')
@@ -0,0 +1,10 @@
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/view_helpers/sinatra'
10
+ end
@@ -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
@@ -0,0 +1,9 @@
1
+ require 'leaf/core_ext'
2
+
3
+ module Lead
4
+ # Database logic for different ORMs
5
+ #
6
+ # See Leaf::Finders::Base
7
+ module Finders
8
+ end
9
+ end
@@ -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 = wp_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 wp_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,22 @@
1
+ require 'sequel'
2
+ require 'sequel/extensions/pagination'
3
+
4
+ existing_methods = Sequel::Dataset::Pagination.instance_methods
5
+
6
+ Sequel::Dataset::Pagination.module_eval do
7
+ # it should quack like a Leaf::Collection
8
+
9
+ alias :total_pages :page_count unless existing_methods.include_method? :total_pages
10
+ alias :per_page :page_size unless existing_methods.include_method? :per_page
11
+ alias :previous_page :prev_page unless existing_methods.include_method? :previous_page
12
+ alias :total_entries :pagination_record_count unless existing_methods.include_method? :total_entries
13
+
14
+ def out_of_bounds?
15
+ current_page > total_pages
16
+ end
17
+
18
+ # Current offset of the paginated collection
19
+ def offset
20
+ (current_page - 1) * per_page
21
+ end
22
+ end
@@ -0,0 +1,9 @@
1
+ module Leaf
2
+ module VERSION
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -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 => '&#8592; Previous',
23
+ :next_label => 'Next &#8594;',
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,121 @@
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
50
+ options[:renderer].constantize.new
51
+ when Class
52
+ options[:renderer].new
53
+ else
54
+ options[:renderer]
55
+ end
56
+ # render HTML for pagination
57
+ renderer.prepare collection, options, self
58
+ renderer.to_html
59
+ end
60
+
61
+ # Renders a helpful message with numbers of displayed vs. total entries.
62
+ # You can use this as a blueprint for your own, similar helpers.
63
+ #
64
+ # <%= page_entries_info @posts %>
65
+ # #-> Displaying posts 6 - 10 of 26 in total
66
+ #
67
+ # By default, the message will use the humanized class name of objects
68
+ # in collection: for instance, "project types" for ProjectType models.
69
+ # Override this to your liking with the <tt>:entry_name</tt> parameter:
70
+ #
71
+ # <%= page_entries_info @posts, :entry_name => 'item' %>
72
+ # #-> Displaying items 6 - 10 of 26 in total
73
+ #
74
+ # Entry name is entered in singular and pluralized with
75
+ # <tt>String#pluralize</tt> method from ActiveSupport. If it isn't
76
+ # loaded, specify plural with <tt>:plural_name</tt> parameter:
77
+ #
78
+ # <%= page_entries_info @posts, :entry_name => 'item', :plural_name => 'items' %>
79
+ #
80
+ # By default, this method produces HTML output. You can trigger plain
81
+ # text output by passing <tt>:html => false</tt> in options.
82
+ def page_entries_info(collection, options = {})
83
+ entry_name = options[:entry_name] || (collection.empty?? 'entry' :
84
+ collection.first.class.name.underscore.gsub('_', ' '))
85
+
86
+ plural_name = if options[:plural_name]
87
+ options[:plural_name]
88
+ elsif entry_name == 'entry'
89
+ plural_name = 'entries'
90
+ elsif entry_name.respond_to? :pluralize
91
+ plural_name = entry_name.pluralize
92
+ else
93
+ entry_name + 's'
94
+ end
95
+
96
+ unless options[:html] == false
97
+ b = '<b>'
98
+ eb = '</b>'
99
+ sp = '&nbsp;'
100
+ else
101
+ b = eb = ''
102
+ sp = ' '
103
+ end
104
+
105
+ if collection.total_pages < 2
106
+ case collection.size
107
+ when 0; "No #{plural_name} found"
108
+ when 1; "Displaying #{b}1#{eb} #{entry_name}"
109
+ else; "Displaying #{b}all #{collection.size}#{eb} #{plural_name}"
110
+ end
111
+ else
112
+ %{Displaying #{plural_name} #{b}%d#{sp}-#{sp}%d#{eb} of #{b}%d#{eb} in total} % [
113
+ collection.offset + 1,
114
+ collection.offset + collection.length,
115
+ collection.total_entries
116
+ ]
117
+ end
118
+ end
119
+ end
120
+ end
121
+ 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">&hellip;</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,19 @@
1
+ require 'leaf/view_helpers/base'
2
+ require 'leaf/view_helpers/link_renderer'
3
+
4
+ Leaf::ViewHelpers::LinkRenderer.class_eval do
5
+ protected
6
+ def url(page)
7
+ url = @template.request.url
8
+ if page == 1
9
+ # strip out page param and trailing ? if it exists
10
+ url.gsub(/page=[0-9]+/, '').gsub(/\?$/, '')
11
+ else
12
+ if url =~ /page=[0-9]+/
13
+ url.gsub(/page=[0-9]+/, "page=#{page}")
14
+ else
15
+ (url =~ /\?/) ? url + "&page=#{page}" : url + "?page=#{page}"
16
+ end
17
+ end
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: leaf
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Peter Hellberg
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-06-24 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rack
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 31
30
+ segments:
31
+ - 1
32
+ - 2
33
+ - 0
34
+ version: 1.2.0
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ description: A really simple pagination library for Sinatra
38
+ email: peter@c7.se
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files: []
44
+
45
+ files:
46
+ - lib/leaf/array.rb
47
+ - lib/leaf/collection.rb
48
+ - lib/leaf/core_ext.rb
49
+ - lib/leaf/finders/base.rb
50
+ - lib/leaf/finders/sequel.rb
51
+ - lib/leaf/finders.rb
52
+ - lib/leaf/version.rb
53
+ - lib/leaf/view_helpers/base.rb
54
+ - lib/leaf/view_helpers/link_renderer.rb
55
+ - lib/leaf/view_helpers/link_renderer_base.rb
56
+ - lib/leaf/view_helpers/sinatra.rb
57
+ - lib/leaf/view_helpers.rb
58
+ - lib/leaf.rb
59
+ - MIT-LICENSE
60
+ - Rakefile
61
+ - README.markdown
62
+ has_rdoc: true
63
+ homepage: http://github.com/c7/leaf
64
+ licenses:
65
+ - MIT-LICENSE
66
+ post_install_message:
67
+ rdoc_options: []
68
+
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 3
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ hash: 3
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ requirements: []
90
+
91
+ rubyforge_project: leaf
92
+ rubygems_version: 1.3.7
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: Simple Sinatra pagination
96
+ test_files: []
97
+