/lib/dm-pager/pager.rb

https://github.com/rwaldhoff/dm-pagination · Ruby · 193 lines · 94 code · 42 blank · 57 comment · 22 complexity · e678db2fd356093e3c3af12919444f79 MD5 · raw file

  1. module DataMapper
  2. class Pager
  3. ##
  4. # Total number of un-limited records.
  5. attr_reader :total
  6. ##
  7. # Records per page.
  8. attr_reader :per_page
  9. ##
  10. # Current page number.
  11. attr_reader :current_page
  12. ##
  13. # Previous page or nil when no previous page is available.
  14. attr_reader :previous_page
  15. ##
  16. # Next page or nil when no more pages are available.
  17. attr_reader :next_page
  18. ##
  19. # Total number of pages.
  20. attr_reader :total_pages
  21. ##
  22. # Initialize with _options_.
  23. def initialize options = {}
  24. @page_param = options.delete(:page_param) || :page
  25. @total = options.delete :total
  26. @per_page = options.delete :limit
  27. @current_page = options.delete @page_param
  28. @total_pages = total.quo(per_page).ceil
  29. @next_page = current_page + 1 unless current_page >= total_pages
  30. @previous_page = current_page - 1 unless current_page <= 1
  31. end
  32. ##
  33. # Render the pager with the given _uri_ and _options_.
  34. #
  35. # === Examples
  36. #
  37. # User.page(2).pager.to_html('/users')
  38. # User.page(2).pager.to_html('/users', :size => 3)
  39. #
  40. # === Options
  41. #
  42. # :size Number of intermediate page number links to be shown; Defaults to 7
  43. #
  44. def to_html uri, options = {}
  45. return unless total_pages > 1
  46. @uri, @options = uri, options
  47. @size = option :size
  48. raise ArgumentError, 'invalid :size; must be an odd number' if @size % 2 == 0
  49. @size /= 2
  50. [%(<ul class="#{Pagination.defaults[:pager_class]}">),
  51. first_link,
  52. previous_link,
  53. more(:before),
  54. intermediate_links.join("\n"),
  55. more(:after),
  56. next_link,
  57. last_link,
  58. '</ul>'].join
  59. end
  60. private
  61. ##
  62. # Fetch _key_ from the options passed to #to_html, or
  63. # its default value.
  64. def option key
  65. @options.fetch key, Pagination.defaults[key]
  66. end
  67. ##
  68. # Link to _page_ with optional anchor tag _contents_.
  69. def link_to page, contents = nil
  70. %(<a href="#{uri_for(page)}">#{contents || page}</a>)
  71. end
  72. ##
  73. # More pages indicator for _position_.
  74. def more position
  75. return '' if position == :before && (current_page <= 1 || first <= 1)
  76. return '' if position == :after && (current_page >= total_pages || last >= total_pages)
  77. li 'more', option(:more_text)
  78. end
  79. ##
  80. # Intermediate page links array.
  81. def intermediate_links
  82. (first..last).map do |page|
  83. classes = ["page-#{page}"]
  84. classes << 'active' if current_page == page
  85. li classes.join(' '), link_to(page)
  86. end
  87. end
  88. ##
  89. # Previous link.
  90. def previous_link
  91. li 'previous jump', link_to(previous_page, option(:previous_text)) if previous_page
  92. end
  93. ##
  94. # Next link.
  95. def next_link
  96. li 'next jump', link_to(next_page, option(:next_text)) if next_page
  97. end
  98. ##
  99. # Last link.
  100. def last_link
  101. li 'last jump', link_to(total_pages, option(:last_text)) if next_page && !option(:last_text).empty?
  102. end
  103. ##
  104. # First link.
  105. def first_link
  106. li 'first jump', link_to(1, option(:first_text)) if previous_page && !option(:first_text).empty?
  107. end
  108. ##
  109. # Determine first intermediate page.
  110. def first
  111. @first ||= begin
  112. first = [current_page - @size, 1].max
  113. if (current_page - total_pages).abs < @size
  114. first = [first - (@size - (current_page - total_pages).abs), 1].max
  115. end
  116. first
  117. end
  118. end
  119. ##
  120. # Determine last intermediate page.
  121. def last
  122. @last ||= begin
  123. last = [current_page + @size, total_pages].min
  124. if @size >= current_page
  125. last = [last + (@size - current_page) + 1, total_pages].min
  126. end
  127. last
  128. end
  129. end
  130. ##
  131. # Renders a <li> with the given _css_class_ and _contents_.
  132. def li css_class = nil, contents = nil
  133. "<li#{%( class="#{css_class}") if css_class}>#{contents}</li>\n"
  134. end
  135. ##
  136. # Uri for _page_. The following conversions are made
  137. # to the _uri_ previously passed to #to_html:
  138. #
  139. # /items # Appends query string => /items?page=2
  140. # /items?page=1 # Adjusts current page => /items?page=2
  141. # /items?foo=bar # Appends page pair => /items?foo=bar&page=1
  142. #
  143. def uri_for page
  144. case @uri
  145. when /\b#{@page_param}=/ ; @uri.gsub /\b#{@page_param}=\d+/, "#{@page_param}=#{page}"
  146. when /\?/ ; @uri += "&#{@page_param}=#{page}"
  147. else ; @uri += "?#{@page_param}=#{page}"
  148. end
  149. end
  150. end
  151. end