#254 Pagination with Kaminari
- Download:
- source codeProject Files in Zip (128 KB)
- mp4Full Size H.264 Video (16.4 MB)
- m4vSmaller H.264 Video (10.1 MB)
- webmFull Size VP8 Video (25 MB)
- ogvFull Size Theora Video (22.2 MB)
A continuación aparece una captura de pantalla de una aplicación Rails 3 que muestra un listado de productos bastante largo. En lugar de mostrar los elementos como una lista única queremos que este listado quede paginado.
La primera opción para implementar esta paginación en Rails es will_paginate, sin embargo la versión actual no soporta Rails 3. Existe una versión de prelanzamiento que funciona pero que no ha sido actualizada desde hace varios meses. Si esta gema ya no está en desarrollo activo, ¿qué otras opciones nos quedan?
Una alternativa es Kaminari. Por lo que parece proporciona una implementación más limpia y ofrece algunas funcionalidades extra, así que vamos a intentar incorporarla en nuestra aplicación. Kaminari se instala de la forma habitual: primero añadiendo una referencia en el fichero Gemfile
de la aplicación y luego ejecutando bundle
para instalar la gema en nuestro sistema.
gem "kaminari"
Uso de Kaminari
Kaminari nos da un ámbito llamado page
que podemos aplicar sobre cualquier modelo de ActiveRecord. Se puede utilizar en la acción index
del controlador ProductsController
para paginar el listado de productos, pasándole la página que queremos mostrar, en nuestro caso pasaremos el parámetro page
recibido en la cadena de petición.
def index @products = Product.order("name").page(params[:page]) end
En la plantilla index
donde mostramos el listado de productos podemos invocar al método paginate
que da Kaminari pasándole la lista sobre la que estamos paginando.
<% title "Products" %> <%= paginate @products %> <div id="products"> <%= render @products %> </div> <p><%= link_to "New Product", new_product_path %></p>
Al cargar de nuevo la página veremos los enlaces de paginación en la parte superior.
Kaminari muestra por defecto 25 elementos por página pero esto se puede cambiar fácilmente mediante el uso de otro ámbito llamado
per
. que usaremos para cambiar el número de elementos por página a 5.
def index @products = Product.order("name").page(params[:page]).per(5) end
Con esto tendremos cinco productos por página.
Esta sintaxis encaja muy bien con las nuevas consultas ActiveRecord de Rails 3. Podemos mover la llamada order
a cualquier sitio de la consulta y seguiría funcionando igual, porque lo que estamos haciendo es encadenar ámbitos contra nuestro modelo. Podríamos incluso mover las llamadas a page
y per
a otros ámbitos definidos en el modelo, y funcionarían como es de esperar. La única excepción a esto es que per
debe colocarse después de page
. El motivo es que page
añade automáticamente el ámbito per
, si se llama a per
antes de page
obtendremos un error porque su ámbito todavía ha sido creado.
Cambio del aspecto
Existen varias formas de cambiar el aspecto de la paginación. Si queremos cambiar las palabras “prev” y “next” en el listado de páginas así como las flechas que las acompañanan podemos hacerlo añadiendo entradas en el fichero de localización. De esta forma es muy fácil cambiar estos elementos, en particular si nuestra aplicación debe soportar distintos lenguajes. Los elementos que tenemos que añadir deben ir en view/pagination
y los textos pueden cambiarse con las claves previous
y next
. Para cambiar el texto que se muestra cuando se trunca el listado se puede editar la clave truncate
. Obsérvese que hay que escapar el HTML, porque el texto se mostrará en la página tal cual.
en: hello: "Hello world" views: pagination: previous: "< Previous" next: "Next >" truncate: "…”
Si ahora recargamos la página veremos el texto modificado.
Podemos meter más cambios añadiendo CSS para las clases que utilizan las diferentes partes de la aplicación. Como nota interesante, la navegación utiliza el elemento nav
de HTML 5.
<nav class='pagination'> <span class="prev"> <a href="/products" class="prev" rel="prev">< Previous</a> </span> <span class="page first"> <a href="/products">1</a> </span> <span class="page current">2</span> ...
Si bien con CSS se puede modificar gran parte del aspecto de la navegación, hay otros cambios que no son posibles de esta manera. Por ejemplo, puede ser que queramos que el enlace a “Previous” aparezca en la primera página pero con color gris e inhabilitado, en lugar de no aparecer como es el comportamiento por defecto.
Kaminari es un Engine de Rails, y viene con un número de vistas que se pueden personalizar para ajustarlas a nuestra aplicación. Para ayudar con esto podemos utilizar un generador que viene con Kaminari y al que tenemos que pasar el nombre de un tema como argumento, si utilizamos default
utilizará el tema por defecto.
$ rails g kaminari:views default create app/views/kaminari/_current_page.html.erb create app/views/kaminari/_first_page_link.html.erb create app/views/kaminari/_last_page_link.html.erb create app/views/kaminari/_next_link.html.erb create app/views/kaminari/_next_span.html.erb create app/views/kaminari/_page_link.html.erb create app/views/kaminari/_paginator.html.erb create app/views/kaminari/_prev_link.html.erb create app/views/kaminari/_prev_span.html.erb create app/views/kaminari/_truncated_span.html.erb
Hay otros temas disponibles (véase Kaminari Themes project en GitHub). Ahora mismo hay pocos disponibles pero no cabe duda de que pronto aparecerán más según los usuarios empiecen a desarrollar sus propios temas.
El generador crea un nuevo directorio kaminari
en app/views
, donde colocará un número de archivos parciales. Podemos cambiarlos para personalizar el funcionamiento de la paginación. El fichero principal es _paginator.html.erb
, y si bien esto parece algo complicado, es muy fácil de entender y modificar.
/app/views/kaminari/_paginator.html.erb
<%# The container tag
- available local variables
current_page: the page number of currently displayed page
num_pages: total number of pages
per_page: number of items to fetch per page
remote: data-remote
paginator: the paginator that renders the pagination tags inside
-%>
<%= paginator.render do -%>
<nav class='pagination'>
<%= current_page > 1 ? prev_link_tag : prev_span_tag %>
<% each_page do |page| -%>
<% if page.current? -%>
<%= current_page_tag %>
<% elsif page.left_outer? || page.right_outer? || page.inside_window? -%>
<% if page.first? -%>
<%= first_page_link_tag %>
<% elsif page.last? -%>
<%= last_page_link_tag %>
<% else -%>
<%= page_link_tag %>
<% end -%>
<% elsif !page.was_truncated? -%>
<%= truncated_span_tag %>
<% end -%>
<% end -%>
<%=num_pages > current_page ? next_link_tag : next_span_tag %>
</nav>
<% end -%>
Es fácil ver el elemento nav
en la página y el código que viene justo después que muestra el enlace
“previous”. Este código llamará a prev_link_tag
o prev_span_tag
dependiendo si la página es la primera o no. Cada uno de estos métodos invoca a un parcial y como queremos cambiar lo que ocurre en la primera página cambiaremos _prev_span.html.erb
.
<%# "Previous" without link - available local variables current_page: the page number of currently displayed page num_pages: total number of pages per_page: number of items to fetch per page remote: data-remote -%> <span class="prev"></span>
Lo único que hay que hacer es añadir un poco de código al elemento span
, si miramos _prev_link.html.erb
veremos que muestra un enlace. Podemos copiar el código que genera el texto para el enlace y pegarlo en el elemento span
.
<%# "Previous" without link - available local variables current_page: the page number of currently displayed page num_pages: total number of pages per_page: number of items to fetch per page remote: data-remote -%> <span class="prev disabled"> <%= raw(t 'views.pagination.previous') %> </span>
El código que muestra el texto utiliza el método t
para recuperar el fragmento de texto traducido a partir del archivo de traducciones que vimos anteriormente, y le pasa dicho texto al método raw
para evitar que sea escapado. También hemos añadido la clase disabled
al elemento span
para que pueda aparecer en gris añadiendo lo siguiente al CSS:
.disabled { color: #999; }
Al volver a cargar la primera página de los productos veremos el texto en gris.
Está claro que queremos hacer lo mismo con el enlace a “Next” en la última página, pero eso queda como ejercicio para el lector.
Con esto terminamos nuestro repaso a la gema Kaminari, que merece la pena considerar si necesitamos implementar una paginación en una aplicación Rails 3. No hemos cubierto las opciones que acepta el método de paginación, como la posibilidad de cambiar cómo se muestra el listado de páginas, o añadir parámetros a los enlaces de paginación o que estos enlaces funcionen con AJAX.