# encoding: utf-8 # # = Pagination # # Simple class to hold the minimal info needed to paginate a set of objects by # page number and/or letter. This class knows nothing about how to query the # results (see Query#paginate), nor does it know anything about how to render # the results or pagination controls (see ApplicationHelper::Paginator). # # You give it a page number and number of results; it in return can tell you # which results you should display. # # It also stores a few other parameters which though it never uses, the query # and/or render mechanisms need access to, such as the URL parameter used to # select page and letter, and the list of letters for which there are results. # # == Query and ApplicationHelper::Paginator # # Together these three classes and modules make it possible to gather a set of # results to an arbitrary query, and render them on an HTML view together with # all the necessary controls. The basic usage is: # # # In your Rails controller: # def index # @pages = MOPaginator.new( # :number_arg => :page, # :number => params[:page], # :num_per_page => 100, # ) # query = Query.lookup(:Model, :flavor) # @results = query.paginate(@pages) # end # # # This paginates by letter and number: # def index_by_letter # @pages = MOPaginator.new( # :letter_arg => :letter, # :number_arg => :page, # :letter => params[:letter], # :number => params[:page], # :num_per_page => 100, # ) # query = Query.lookup(:Model, :flavor) # @results = query.paginate(@pages) # end # # # This is how you might use it without Query: # def custom_index # @results = Model.find(...) # @pages.num_total = @results.length # @subset = @results[@pages.from..@pages.to] # end # # # Use the same code in your view template for either case: # <% paginate_block(@pages) do %> # <% for object in @results # <%= link_to(object.name, :action => 'show_object', :id => object.id) %>
# <% end %> # <% end %> # # == Attributes # # letter_arg:: URL parameter used to select letter. # number_arg:: URL parameter used to select page number. (= +page_arg) # letter:: Letter selected (or +nil+ if none). # number:: Page number selected (or +nil+ if none). (= +page+) # num_per_page:: Number of results to show per page. # num_total:: Number of results available. (= +length) # used_letters:: Array of letters that have results. # # == Class Methods # # new:: Instantiate, setting one or more attributes at the same time. # # == Instance Methods # # show_index:: Set page number so that it's showing the given result. # num_pages:: Calculate number of pages of results available. # from:: Index of the first result in selected page. # to:: Index of the last result in selected page. # from_to:: Same as pages.from..pages.to. # ################################################################################ class MOPaginator attr_accessor :letter_arg # Name of parameter to use for letter (if any). attr_accessor :number_arg # Name of parameter to use for page number. attr_reader :letter # Current letter (if any). attr_reader :number # Current page number. attr_reader :num_per_page # Number of results per page (default is 100). attr_reader :num_total # Total number of results. attr_reader :used_letters # List of letters that have results. alias page_arg number_arg alias page number alias length num_total def blank?; num_total == 0; end def empty?; num_total == 0; end # Create and initialize new instance. def initialize(args={}) args.each do |key, val| send("#{key}=", val) end @number ||= 1 @num_per_page ||= 100 @num_total ||= 0 end # Validate the page number selection. def number=(num) if num @number = num.to_i @number = 1 if @number < 1 else @number = 1 end return @number end alias page= number= # Validate the letter selection. def letter=(char) if char @letter = char.to_s[0,1].upcase @letter = nil if !@letter.match(/[A-Z]/) else @letter = nil end return @letter end # Validate the number of results. def num_total=(num) if num @num_total = num.to_i @num_total = 0 if @num_total < 1 else @num_total = 0 end return @num_total end alias length= num_total= # Validate the number per page. def num_per_page=(num) @num_per_page = num.to_i raise "Invalid num_per_page: #{num.inspect}" if @num_per_page < 1 return @num_per_page end # Validate +used_letters+ array. Force them all to uppercase, and remove # duplicates and non-letters. (Set to +nil+ to mean assume all letters have # results.) def used_letters=(list) if list @used_letters = list.map {|l| l.to_s[0,1].upcase}.uniq. select {|l| l.match(/[A-Z]/)}.sort else @used_letters = nil end end # Number of pages of results available. def num_pages (num_total.to_f / num_per_page).ceil end # First index on current page. def from n = ((number || 0) - 1) * num_per_page n = 0 if n < 0 return n end # Last index on current page. def to n = from + num_per_page - 1 n = num_total - 1 if n > num_total - 1 n = 0 if n < 0 return n end # Same as pages.from..pages.to. def from_to from..to end # Set page number so that it shows the given result (given by index, with # zero being the first result). def show_index(index) self.number = (index.to_f / num_per_page).floor + 1 end end