From f42811d89f7e727f389041cd842898df89bfcdb9 Mon Sep 17 00:00:00 2001 From: Domizio Demichelis Date: Tue, 29 May 2018 16:42:26 +0700 Subject: [PATCH] implemented :page_param (#20) --- docs/api/backend.md | 19 +++++---- docs/api/frontend.md | 10 ++--- docs/api/pagy.md | 52 ++++++++++++------------- docs/how-to.md | 53 ++++++++++++++++---------- lib/pagy.rb | 2 +- lib/pagy/backend.rb | 11 +++--- lib/pagy/extras/array.rb | 12 +++--- lib/pagy/extras/initializer_example.rb | 5 ++- lib/pagy/frontend.rb | 6 +-- 9 files changed, 93 insertions(+), 77 deletions(-) diff --git a/docs/api/backend.md b/docs/api/backend.md index d736ba76b..d9368c615 100644 --- a/docs/api/backend.md +++ b/docs/api/backend.md @@ -15,12 +15,12 @@ __Notice__: Currently, the only available backend extra is the [array extra](../ ## Synopsys ```ruby -# typically in your controller +# in your controller include Pagy::Backend -# optional overriding of some sub-method (e.g. get the page number from the :seite param) -def pagy_get_vars(collection) - { count: collection.count, page: params[:seite] } +# optional overriding of some sub-method +def pagy_get_vars(collection, vars) + #... end # use it in some action @@ -54,18 +54,21 @@ def pagy_custom(collection, vars={}) end ``` -### pagy_get_vars(collection) +### pagy_get_vars(collection, vars) Sub-method called only by the `pagy` method, it returns the hash of variables used to initialize the pagy object. Here is its source: ```ruby -def pagy_get_vars(collection) - { count: collection.count, page: params[:page] } +# sub-method called only by #pagy: here for easy customization of variables by overriding +def pagy_get_vars(collection, vars) + # return the merged variables to initialize the pagy object + { count: collection.count, + page: params[vars[:page_param]||VARS[:page_param]] }.merge!(vars) end ``` -Override it if you need to add or change some variable. For example you may want to add the `:item_path` or the `:item_name` to customize the `pagy_info` output, or get the `:page` from a different param, or even cache the `count`. +Override it if you need to add or change some variable. For example you may want to add the `:item_path` or the `:item_name` to customize the `pagy_info` output, or even cache the `count`. _IMPORTANT_: `:count` and `:page` are the only 2 required pagy core variables, so be careful not to remove them from the returned hash. diff --git a/docs/api/frontend.md b/docs/api/frontend.md index 89bc006cb..e21ea6bdd 100644 --- a/docs/api/frontend.md +++ b/docs/api/frontend.md @@ -28,7 +28,7 @@ use some of its method in some view: ## Methods -All the methods in this module are prefixed with the `"pagy_"` string, to avoid any possible conflict with your own methods when you include the module in your helper. The methods prefixed with the `"pagy_get_"` string are sub-methods/getter methods that are intended to be overridden and not used directly. +All the methods in this module are prefixed with the `"pagy_"` string in order to avoid any possible conflict with your own methods when you include the module in your helper. The methods prefixed with the `"pagy_get_"` string are sub-methods/getter methods that are intended to be overridden and not used directly. Please, keep in mind that overriding any method is very easy with pagy. Indeed you can do it right where you are using it: no need of monkey-patching or subclassing or tricky gymnic. @@ -69,7 +69,7 @@ Displaying Products 476-500 of 1000 in total See also [Using the pagy_info helper](../how-to.md#using-the-pagy_info-helper). -### pagy_url_for(n) +### pagy_url_for(n, page_param) This method is called internally in order to produce the url of a page by passing it its number. For standard usage it works out of the box and you can just ignore it. @@ -102,7 +102,7 @@ You need this section only if you are going to override a `pagy_nav*` helper or **Warning**: This is a peculiar way to create page links and it works only for that purpose. It is not intended to be used for any other generic links to any URLs different than a page link. -This method returns a specialized proc that you call to produce the page links. The reason it is a 2 steps process instead of a single method call is performance. +This method returns a specialized proc that you call to produce the page links. The reason it is a 2 steps process instead of a single method call is performance. Indeed the method calls the potentially expensive `pagy_url_for` only once and generates the proc, then calling the proc will just interpolates the strings passed to it. Here is how you should use it: in your helper or template call the method to get the proc (just once): ``` @@ -118,7 +118,7 @@ link.call( page_number [, text [, extra_attributes_string ]]) If you need to add some HTML attribute to the page links, you can pass some extra attribute string at many levels, depending on the scope you want your attributes to be added. -**Important**: For performance reasons, the extra attributes strings must be formatted as valid HTML attribute/value pairs. _All_ the stringa spassed at any level will get inserted verbatim in the HTML of the link. +**Important**: For performance reasons, the extra attributes strings must be formatted as valid HTML attribute/value pairs. _All_ the string spassed at any level will get inserted verbatim in the HTML of the link. 1. For all pagy objects: set the global variable `:link_extra`: ```ruby @@ -168,7 +168,7 @@ _(see I18n below)_ Pagy is I18n ready. That means that all the UI strings that pagy uses are stored in a [dictionaray YAML file](https://github.com/ddnexus/pagy/blob/master/lib/locales/pagy.yml), ready to be customized and/or translated/pluralized. -The YAML file is available at `Pagy.root.join('locales', 'pagy.yml')`. It contains a few entries used in the the UI by helpers and templates through the [pagy_t method](api/frontend.md#pagy_tpath-vars) (eqivalent to the `I18n.t` or rails `t` helper). +The YAML file is available at `Pagy.root.join('locales', 'pagy.yml')`. It contains a few entries used in the the UI by helpers and templates through the [pagy_t method](#pagy_tpath-vars) (eqivalent to the `I18n.t` or rails `t` helper). By default, the `pagy_t` method uses the pagy implementation of I18n, which does not depend on the `I18n` gem in any way. It's _5x faster_ and uses _3.5x less memory_, but it provides only pluralization/interpolation without translation, so it's only useful with single language apps (i.e. only `fr` or only `en` or only ...) diff --git a/docs/api/pagy.md b/docs/api/pagy.md index 85d13c30a..e7539e624 100644 --- a/docs/api/pagy.md +++ b/docs/api/pagy.md @@ -60,31 +60,31 @@ _Notice_: If you use the `Pagy::Backend` its `pagy` method will instantiate and The `Pagy.new` method accepts a single hash of variables that will be merged with the `Pagy::Vars` hash and will be used to create the object. The only mandatory variable is the `:count` of the collection to paginate: all the other variables are optional and have sensible defaults. Of course you will also have to pass the `page` or you will always get the default page number 1. All the other variables not explicitly in the list of core-variables (the non-core variables) passed/merged to the new method will be kept in the object and passed around with it. +__Notice__: Pagy replaces the blank values of the passed variables with their default values. It also applies `to_i` on the values expected to be integers, so you can use values from request `params` without problems. For example: `pagy(some_scope, items: params[:items])` will work without any additional cleanup. + ### Core Variables These are the core-variables (i.e. instance variables that define the pagination object itself) consumed by the `new` method. They are all integers: -| Variable | Description | Default | -|----------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------| -| `:count` | the total count of the collection to paginate (mandatory argument) | `nil` | -| `:page` | the requested page number | `1` | -| `:items` | the _requested_ number of items for the page | `20` | -| `:outset` | the initial offset of the collection to paginate: pass it only if the collection was pre-offset(ted) | `0` | -| `:size` | the size of the page links to show: array of initial pages, before current page, after current page, final pages. _(see also [Control the page links](../how-to.md#controlling-the-page-links))_ | `[1,4,4,1]` | - -__Notice__: Pagy replaces the blank values of scalar core variables (so excluding `:size`) with their default values. It also applies `to_i` on the values expected to be integers, so you can use values from request `params` without problems. For example: `pagy(some_scope, items: params[:items])` will work without any additional cleanup. - +| Variable | Description | Default | +|----------:|:-----------------------------------------------------------------------------------------------|:--------| +| `:count` | the total count of the collection to paginate (mandatory argument) | `nil` | +| `:page` | the requested page number | `1` | +| `:items` | the _requested_ number of items for the page | `20` | +| `:outset` | the initial offset of the collection to paginate: pass it only if the collection had an offset | `0` | ### Non-core Variables -These are the non-core variables used in the `Pagy::Frontend`: as for the core-variables they can be set globally by using the `Pagy::VARS` hash or passed to the `Pagy.new` method, but they don't become instance variables. Instead they are items of the `@vars` instance variable hash and are accessible through the `vars` reader. +These are the non-core variables: as for the core-variables they can be set globally by using the `Pagy::VARS` hash or passed to the `Pagy.new` method, but they don't become instance variables. Instead they are items of the `@vars` instance variable hash and are accessible through the `vars` reader. -| Variable | Description | Default | -|--------------:|:------------------------------------------------------------------------------------------------------|:--------| -| `:link_extra` | the extra attributes string (formatted as a valid HTML attribute/value pairs) added to the page links | `nil` | -| `:item_path` | the dictionary path used by the `pagy_info` method to lookup the item/model name | `nil` | +| Variable | Description | Default | +|--------------:|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------| +| `:size` | the size of the page links to show: array of initial pages, before current page, after current page, final pages. (see also [Control the page links](../how-to.md#controlling-the-page-links))_ | `[1,4,4,1]` | +| `:page_param` | the name of the page param name used in the url. (see [Customizing the page param](../how-to.md#customizing-the-page-param) | `:page` | +| `:link_extra` | the extra attributes string (formatted as a valid HTML attribute/value pairs) added to the page links | `nil` | +| `:item_path` | the dictionary path used by the `pagy_info` method to lookup the item/model name | `nil` | There is no specific default nor validation for non-core variables, which are just optional strings. @@ -126,17 +126,17 @@ That is self-contained, simple and efficient. The lowest possible limit of the pagination is reached when the collection has `0` count. In that case the pagy object created has the following peculiar attributes: | Attribute | Value | -|:----------|:--------| -| `count` | `0` | -| `page` | `1` | -| `items` | `0` | -| `pages` | `1` | -| `last` | `1` | -| `from` | `0` | -| `to` | `0` | -| `prev` | `nil` | -| `next` | `nil` | -| `series` | `["1"]` | +|----------:|:--------| +| `count` | `0` | +| `page` | `1` | +| `items` | `0` | +| `pages` | `1` | +| `last` | `1` | +| `from` | `0` | +| `to` | `0` | +| `prev` | `nil` | +| `next` | `nil` | +| `series` | `["1"]` | Which means: diff --git a/docs/how-to.md b/docs/how-to.md index 68afcc5c5..56ec00c93 100644 --- a/docs/how-to.md +++ b/docs/how-to.md @@ -49,7 +49,7 @@ Pagy should work out of the box for most Rack based apps (e.g. Rails) even witho You can control the items per page with the `items` variable. (Default `20`) -You can set its default in an initializer (applied to all instances). For example: +You can set its default in the pagy initializer. For example: ```ruby Pagy::VARS[:items] = 25 ``` @@ -62,7 +62,7 @@ You can also pass it as an instance variable to the `Pagy.new` method or to the You can control the number and position of page links in the navigation through the `:size` variable. It is an array of 4 integers that specify which and how many page link to show. -The default is `[1,4,4,1]`, which means that you will get `1` initial page, `4` pages before the current page, `4` pages after the current page, and `1` final page +The default is `[1,4,4,1]`, which means that you will get `1` initial page, `4` pages before the current page, `4` pages after the current page, and `1` final page. As usual you can set the `:size` variable as a global default by using the `Pagy::VARS` hash or pass it directly to the `pagy` method. @@ -84,9 +84,7 @@ You can easily try different options (also asymmetrical) in a console by changin ## Paginate Any Collection -Pagy doesn't need to know anything about the kind of collection you paginate, it doesn't need to "teach" it how to paginate itself by adding code to it: pagy just needs to know its count. - -With pagy you can paginate any collection, because every collection knows its count and every collection has a way to paginate itself by extracting a chunk of its items given an `offset` and a `limit` (i.e. skipping a few items from the beginning and getting a limited number from there). It does not matter if it is an `Array` or an `ActiveRecord` scope or something else: the simple mechanism is the same: +Pagy doesn't need to know anything about the kind of collection you paginate, it can paginate any collection, because every collection knows its count and has a way to extract a chunk of items given an `offset` and a `limit`. It does not matter if it is an `Array` or an `ActiveRecord` scope or something else: the simple mechanism is the same: 1. Create a pagy object using the `count` of the collection to paginate 2. Get the page of items from the collection using `pagy.offset` and `pagy.items` @@ -121,9 +119,9 @@ See the [Pagy::Backend source](https://github.com/ddnexus/pagy/blob/master/lib/p You have many ways to paginate an array with pagy: -- implementing the above _how-to_ (probably not very convenient besides being a good example) -- using `pagy` and overriding `pagy_get_items` _(see [pagy_get_items](api/backend.md#pagy_get_itemscollection-pagy)_ -- using `pagy_array` offered by the `array` extra _(see [array extra](extras/array.md))_ +1. Implementing the above _how-to_ (probably not very convenient besides being a good example) +2. Using the `pagy` method and overriding `pagy_get_items` _(see [pagy_get_items](api/backend.md#pagy_get_itemscollection-pagy)_ +3. Using `pagy_array` offered by the `array` extra _(see [array extra](extras/array.md))_ ## Paginate a pre-offsetted and pre-limited collection @@ -138,15 +136,25 @@ Assuming the `:items` default of `20`, you will get the pages with the right rec ## Passing the page number -You don't need to explicitly pass the usual `params[:page]` page number to the `pagy` method, because it is pulled in by the `pagy_get_vars` (which is called internally by the `pagy` method). However you can force a `page` number by just passing it to the `pagy` method. For example: +You don't need to explicitly pass the page number to the `pagy` method, because it is pulled in by the `pagy_get_vars` (which is called internally by the `pagy` method). However you can force a `page` number by just passing it to the `pagy` method. For example: ```ruby @pagy, @records = pagy(my_scope, page: 3) ``` -That will explicitly set the `:page` variable, overriding the `params[:page]` default. +That will explicitly set the `:page` variable, overriding the default behavior (which usually pulls the page number from the `params[:page]`). + +## Customizing the page param + +Pagy uses the `:page_param` variable to determine the param it should get the page number from and create the URL for. Its default is set as `Pagy::VARS = :page`, hence it will get the page number from the `params[:page]` and will create page URLs like `./?page=3` by default. + +You may want to customize that, for example to make it more readable in your language, or becuse you need to paginate different collections in the same action. Depending on the scope of the customization, you have couple of options: + +1. `Pagy::VARS[:page_param] = :custom_param` will be used as global default +2. `pagy(scope, page_param: :custom_param)` or `Pagy.new(count:100, page_param: :custom_param)` will be used for a single instance (overriding the global default) + +You can also override the `pagy_get_vars` if you need some special way to get the page number. -__Notice__: If you need to get the page number from another param or in some different way, just override the `pagy_get_vars` method right in your controller. ## Using the pagy_nav* helpers @@ -157,8 +165,8 @@ These helpers take the pagy object and returns the HTML string with the paginati ``` **Notice**: the [extras](extras.md) add a few other helpers that you an use the same way, in order to get added features (e.g. bootstrap compatibility, responsiveness, compact layouts, etc.) -| Extra | Helpers | -|:--------------------------------------|:-------------------------------------------------------| +| Extra | Helpers | +|:-----------------------------------|:-------------------------------------------------------| | [bootstrap](extras/bootstrap.md) | `pagy_nav_bootstrap` | | [responsive](extras/responsive.md) | `pagy_nav_responsive`, `pagy_nav_bootstrap_responsive` | | [compact](extras/compact.md) | `pagy_nav_compact`, `pagy_nav_bootstrap_compact` | @@ -204,20 +212,20 @@ When you need somethig more radical with the URL than just massaging the params, The following is a Rails-specific alternative that supports fancy-routes (e.g. `get 'your_route(/:page)' ...` that produce paths like `your_route/23` instead of `your_route?page=23`): ```ruby -def pagy_url_for(n) - params = request.query_parameters.merge(only_path: true, page: n) +def pagy_url_for(n, page_param) + params = request.query_parameters.merge(:only_path => true, page_param => n) url_for(params) end ``` -Notice that this overridden method is quite slower than the original because it passes through the rails helpers. However that gets mitigated by the internal usage of `pagy_link_proc` which calls the method only once even in presence of many pages. +Notice that this overridden method is quite slower than the original because it passes through the rails helpers. However that gets mitigated by the internal usage of `pagy_link_proc` which calls the method only once even in the presence of many pages. #### POST with page links You may need to POST a very complex search form that would generate an URL potentially too long to be handled by a browser, and your page links may need to use POST and not GET. In that case you can try this simple solution: ```ruby -def pagy_url_for(n) +def pagy_url_for(n, _) n end ``` @@ -265,8 +273,9 @@ Pagy gets the collection count through its `pagy_get_vars` method, so you can ov ```ruby # in your controller: override the pagy_get_vars method so it will call your cache_count method -def pagy_get_vars(collection) - {count: cache_count(collection), page: params[:page]} +def pagy_get_vars(collection, vars) + { count: cache_count(collection), + page: params[vars[:page_param]||Pagy::VARS[:page_param]] }.merge!(vars) end # add Rails.cache wrapper around the count call @@ -296,8 +305,10 @@ You can do so by setting the `:item_path` variable to the path to lookup in the 1. by overriding the `pagy_get_vars` method in your controller (valid for all the pagy instances) adding the `:item_path`. For example (with ActiveRecord): ```ruby - def pagy_get_vars(collection) - {count: collection.count, page: params[:page], item_path: "activerecord.models.#{collection.model_name.i18n_key}" } + def pagy_get_vars(collection, vars) + { count: collection.count, + page: params[vars[:page_param]||Pagy::VARS[:page_param]], + item_path: "activerecord.models.#{collection.model_name.i18n_key}" }.merge!(vars) end ``` diff --git a/lib/pagy.rb b/lib/pagy.rb index d987123ef..b40c41df3 100644 --- a/lib/pagy.rb +++ b/lib/pagy.rb @@ -10,7 +10,7 @@ class OutOfRangeError < StandardError; end def self.root; Pathname.new(__FILE__).dirname end # default core vars - VARS = { page:1, items:20, outset:0, size:[1,4,4,1] } + VARS = { page:1, items:20, outset:0, size:[1,4,4,1], page_param: :page } # default I18n vars zero_one = [:zero, :one] ; I18N = { file: Pagy.root.join('locales', 'pagy.yml').to_s, plurals: -> (c) {(zero_one[c] || :other).to_s.freeze} } diff --git a/lib/pagy/backend.rb b/lib/pagy/backend.rb index 336ab215b..ffc2217f7 100644 --- a/lib/pagy/backend.rb +++ b/lib/pagy/backend.rb @@ -10,15 +10,16 @@ class Pagy module Backend ; private # the whole module is private so no problem with including it in a controller # return pagy object and items - def pagy(collection, vars=nil) - pagy = Pagy.new(vars ? pagy_get_vars(collection).merge!(vars) : pagy_get_vars(collection)) # conditional merge is faster and saves memory + def pagy(collection, vars={}) + pagy = Pagy.new(pagy_get_vars(collection, vars)) return pagy, pagy_get_items(collection, pagy) end # sub-method called only by #pagy: here for easy customization of variables by overriding - def pagy_get_vars(collection) - # return the variables to initialize the pagy object - { count: collection.count, page: params[:page] } + def pagy_get_vars(collection, vars) + # return the merged variables to initialize the pagy object + { count: collection.count, + page: params[vars[:page_param]||VARS[:page_param]] }.merge!(vars) end # sub-method called only by #pagy: here for easy customization of record-extraction by overriding diff --git a/lib/pagy/extras/array.rb b/lib/pagy/extras/array.rb index 5b80f3d1a..6c5099ab7 100644 --- a/lib/pagy/extras/array.rb +++ b/lib/pagy/extras/array.rb @@ -5,15 +5,15 @@ class Pagy module Backend ; private # return pagy object and items - def pagy_array(array, vars=nil) - pagy = Pagy.new(vars ? pagy_array_get_vars(array).merge!(vars) : pagy_array_get_vars(array)) # conditional merge is faster and saves memory + def pagy_array(array, vars={}) + pagy = Pagy.new(pagy_array_get_vars(array, vars)) return pagy, array[pagy.offset, pagy.items] end - # sub-method called only by #pagy_array: here for easy customization of variables by overriding - def pagy_array_get_vars(array) - # return the variables to initialize the pagy object - { count: array.count, page: params[:page] } + def pagy_array_get_vars(array, vars) + # return the merged variables to initialize the pagy object + { count: array.count, + page: params[vars[:page_param]||VARS[:page_param]] }.merge!(vars) end end diff --git a/lib/pagy/extras/initializer_example.rb b/lib/pagy/extras/initializer_example.rb index f4f0a0f79..bba9e98c0 100644 --- a/lib/pagy/extras/initializer_example.rb +++ b/lib/pagy/extras/initializer_example.rb @@ -18,7 +18,7 @@ # require 'pagy/extras/compact' # I18n: Uses the `I18n` gem instead of the pagy implementation -# https://ddnexus.github.io/pagy/extras/i18n +# See https://ddnexus.github.io/pagy/extras/i18n # require 'pagy/extras/i18n' # Responsive: On resize, the number of page links will adapt in real-time to the available window or container width @@ -32,9 +32,10 @@ # Core variables (See https://ddnexus.github.io/pagy/api/pagy#core-variables) # Pagy::VARS[:items] = 20 # default -# Pagy::VARS[:size] = [1,4,4,1] # default # Non Core Variables (See https://ddnexus.github.io/pagy/api/pagy#non-core-variables) +# Pagy::VARS[:size] = [1,4,4,1] # default +# Pagy::Vars[:page_param] = :page # default # Pagy::VARS[:link_extra] = 'data-remote="true"' # example # Pagy::VARS[:item_path] = 'activerecord.models.product' # example diff --git a/lib/pagy/frontend.rb b/lib/pagy/frontend.rb index 69ba3b2fd..955a47b73 100644 --- a/lib/pagy/frontend.rb +++ b/lib/pagy/frontend.rb @@ -35,8 +35,8 @@ def pagy_info(pagy) # this works with all Rack-based frameworks (Sinatra, Padrino, Rails, ...) - def pagy_url_for(page) - params = request.GET.merge('page'.freeze => page.to_s) + def pagy_url_for(page, page_param) + params = request.GET.merge(page_param => page) "#{request.path}?#{Rack::Utils.build_nested_query(pagy_get_params(params))}" end @@ -50,7 +50,7 @@ def pagy_get_params(params) params end # returns a specialized proc to generate the HTML links def pagy_link_proc(pagy, lx=''.freeze) # "lx" means "link extra" p_prev, p_next, p_lx = pagy.prev, pagy.next, pagy.vars[:link_extra] - a, b = %( (n, text=n, x=''.freeze) { "#{a}#{n}#{b}#{ if n == p_prev ; ' rel="prev"'.freeze elsif n == p_next ; ' rel="next"'.freeze else ''.freeze end }#{x.empty? ? x : %( #{x})}>#{text}" }