module Ramaze # The BSD License # # Copyright (c) 2004-2007, George K. Moschovitis. (http://www.gmosx.com) # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of Nitro nor the names of its contributors may be # used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # Displays a collection of entitities in multiple pages. # # === Design # # This pager is carefully designed for scaleability. It stores only the items # for one page. The key parameter is needed, multiple pagers can coexist in a # single page. The pager leverages the SQL LIMIT option to optimize database # interaction. # # # === Example # # class MyController # def index # objs = (0..200).to_a # @entries, @pager = paginate(objs, :limit => 20) # end # end # # # # Pager # # #
#{@pager.navigation}
# # # # # # === Styling # # The following classes can be used for styling with CSS (provided you put the # pager in a element with class 'pager' like shown above): # # .pager {} # .pager .first {} # .pager .previous {} # .pager .next {} # .pager .last {} # .pager ul {} # .pager li {} # .pager li.active {} class Pager include Ramaze::Helper::Link # Items per page. trait :limit => 10 # The request key. trait :key => '_page' # The current page. attr_reader :page # Items per page. attr_reader :limit # The total number of pages. attr_reader :page_count # Total count of items. attr_reader :total_count # Create a new Pager object. # # request:: Ramaze::Request object providing access to GET parameters # limit:: how many elements go to one page # total_count:: total element count # key:: key used for getting the current page from GET paramaters # # Note: You never have to create this class yourself, use the `paginate()` # convenience method from the Helper::Pager. def initialize(request, limit, total_count, key = trait[:key]) raise 'limit should be > 0' unless limit > 0 @request, @key = request, key @page = (request.params[key] || 1).to_i @limit = limit set_count(total_count) @start_idx = (@page - 1) * limit end # Return the first page index. def first_page 1 end # Is the first page displayed? def first_page? @page == 1 end # Return the last page index. def last_page return @page_count end # Is the last page displayed? def last_page? @page == @page_count end # Return the index of the previous page. def prev_page [@page - 1, 1].max end # Return the index of the next page. def next_page [@page + 1, @page_count].min end # Returns each element for the current page def each(&block) @page_items.each(&block) end # Iterator # Returns 1-based index. def each_with_index(&block) @page_items.each_with_index(&block) end # Is the pager empty, ie has one page only? def empty? @page_count < 2 end # Returns true if a navigation is necessary (meaning there is more than one # page) def navigation? !empty? end # Returns the amount of all elements in all pages. def size @total_count end # Override this method in your application if needed. #-- # TODO: better markup. #++ def navigation nav = "" unless first_page? nav << %{
First
} end unless last_page? nav << %{
Last
} end nav << %{} return nav end # To be used with Og queries. def limit if @start_idx > 0 { :limit => @limit, :offset => @start_idx } else { :limit => @limit } end end # Returns the index of the first element to go into the current page def offset @start_idx end private # Generate the target URI. def target_uri(page) params = @request.params.merge(@key => page) Rs(Action.current.name, params) end # Generate link for the first page. def link_first_page; target_uri(first_page); end # Generate link for the last page. def link_last_page; target_uri(last_page); end # Generate link for the previous page. def link_prev_page; target_uri(prev_page); end # Generate link for the next page. def link_next_page; target_uri(next_page); end # Returns the range of the current page. def page_range s = @idx e = [@idx + @items_limit - 1, all_total_count].min return [s, e] end # Returns the range of # Override if needed. def nav_range # effective range = 10 pages. s = [@page - 5, 1].max e = [@page + 9, @page_count].min d = 9 - (e - s) e += d if d < 0 return (s..e) end # generates total and page count of the pager. def set_count(total_count) @total_count = total_count @page_count = (@total_count.to_f / @limit).ceil end end # Pager related helper methods. module Helper::Pager # Helper method that generates a collection of items and the # associated pager object. # # === Example # # items = [ 'item1', 'item2', ... ] # entries, pager = paginate(items, :limit => 10) # # # #{pager.navigation} # # === Og Example # # entries, pager = paginate(Article, :where => 'title LIKE..', :limit => 10) # # or # # entries, pager = paginate(article.comments, :limit => 10) def paginate(items, options = {}) limit = options.delete(:limit) || Pager.trait[:limit] pager_key = options.delete(:pager_key) || Pager.trait[:key] case items when Array pager = Pager.new(request, limit, items.size, pager_key) items = items.slice(pager.offset, pager.limit[:limit]) return items, pager end if defined?(Og) && items.is_a?(Og::Collection) pager = Pager.new(request, limit, items.count, pager_key) options.update(pager.limit) items = items.reload(options) return items, pager elsif defined?(Og::EntityMixin) && items.is_a?(Og::EntityMixin) || defined?(Og::Mixin) && items.is_a?(Og::Mixin) # Og <= 0.41 pager = Pager.new(request, limit, items.count(options), pager_key) options.update(pager.limit) items = items.all(options) return items, pager end raise "No suitable pagination method for #{items.inspect}" end end end