#340 DataTables
- Download:
- source codeProject Files in Zip (84.3 KB)
- mp4Full Size H.264 Video (30.1 MB)
- m4vSmaller H.264 Video (14.9 MB)
- webmFull Size VP8 Video (13.7 MB)
- ogvFull Size Theora Video (37 MB)
Con DataTables podemos transformar fácilmente una tabla HTML en un listado con paginación, ordenación y búsqueda mediante JavaScript. En este episodio veremos cómo configurar DataTables para que capture datos de una aplicación Rails. Partiremos de la página que se muestra a continuación muestra un listado de productos, sobre la que añadiremos DataTables.
Cómo incorporar DataTables en nuestra aplicación
Podríamos descargar el código fuente y utilizarlo en nuestra aplicación Rails pero es difícil de integrar con el conducto de estáticos porque el código incluye referencias a imágenes con rutas relativas que tendríamos que corregir. Pero por suerte para nosotros existe una gema llamada jquery-datatables-rails
que ya ha resuelto todo esto. La añadiremos al grupo assets
de nuestro Gemfile
. Como el autor actualiza frecuentemente la gema y queremos usar la última versión, simplemente utilizaremos la opción github
y pasaremos el nombre del repositorio.
group :assets do gem 'sass-rails', '~> 3.2.3' gem 'coffee-rails', '~> 3.2.1' # See https://github.com/sstephenson/execjs#readme for more supported runtimes # gem 'therubyracer', :platform => :ruby gem 'uglifier', '>= 1.0.3' gem 'jquery-datatables-rails', github: 'rweng/jquery-datatables-rails' end
La opción github
es nueva en Bundler 1.1. Si no disponemos de esta versión tendremos que pasar la ruta completa de git. Como siempre que añadimos una gema tendremos que ejecutar bundle
para instalarla, tras lo que podremos integrar DataTables con el conducto de estáticos. Primero tenemos que ir al fichero application.js
e insertar una llamada a dataTables/jquery.dataTables
en el manifiesto.
//= require jquery //= require jquery_ujs //= require dataTables/jquery.dataTables //= require_tree .
También tenemos que incluir ciertas hojas de estilo, por lo que modificaremos también el fichero
application.css
.
/* * This is a manifest file that'll automatically include all the stylesheets available in this directory * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at * the top of the compiled file, but it's generally better to create a new file per style scope. *= require_self *= require dataTables/jquery.dataTables *= require_tree . */
Cómo añadir DataTables a una tabla ya existente
Ahora que hemos instalado DataTables en nuestra aplicación podemos usarlo para mejorar nuestra tabla con productos. A continuación vemos el código de la vista de dicha tabla y puede comprobarse que es bastante sencilo; lo único que tenemos es un bucle que recorre todos los productos y los muestran en una tabla.
<h1>Products</h1> <table> <tr> <th>Product Name</th> <th>Category</th> <th>Release Date</th> <th>Price</th> </tr> <% @products.each do |product| %> <tr> <td><%= link_to(product.name, product) %></td> <td><%= product.category %></td> <td><%= product.released_on.strftime("%B %e, %Y") %></td> <td><%= number_to_currency(product.price) %></td> </tr> <% end %> </table>
Para preparar esta tabla y que funcione con DataTables tenemos que poner la primera fila dentro de un elemento thead
y el cuerpo de la tabla en un elemento tbody
. También tenemos que poder referenciar la tabla desde JavaScript por lo que le daremos un id
.
<h1>Products</h1> <table id="products"> <thead> <tr> <th>Product Name</th> <th>Category</th> <th>Release Date</th> <th>Price</th> </tr> </thead> <tbody> <% @products.each do |product| %> <tr> <td><%= link_to(product.name, product) %></td> <td><%= product.category %></td> <td><%= product.released_on.strftime("%B %e, %Y") %></td> <td><%= number_to_currency(product.price) %></td> </tr> <% end %> <tbody> </table>
Ya podemos dotar a esta tabla de la funcionalidad de DataTables con una pizca de CoffeeScript. Lo haremos en el fichero products.js
. Lo único que tenemos que tener en cuenta es que el DOM debe haber finalizado su carga cuando invocamos a dataTable
sobre el objeto tabla.
jQuery -> $('#products').dataTable()
Si ahora recargamos la página veremos que la tabla tiene un aspecto muy diferente. Ahora tenemos paginación, ordenación y búsquedas.
Personalización de la vista
DataTables admite muchas opciones de configuración con las que podemos modificar su aparencia y comportamiento. Por ejemplo, se puede cambiar la paginación con sPaginationType
. Si asignamos su valor a full_numbers
veremos un enlace a cada página. El manual del sitio web de DataTables tiene una lista completa de todas las opciones de personalización.
jQuery -> $('#products').dataTable sPaginationType: "full_numbers"
Podremos ver el nuevo estilo de paginación tras recargar la página.
Uso de estilos para mejorar el aspecto
El aspecto de la tabla no es perfecto, pero puede cambiarse. La página de DataTables muestra una tabla de ejemplo que tiene un aspecto más bonito que por supuesto podemos usar en nuestra tabla. La tabla de ejemplo utiliza jQuery UI, que viene incluido en la gema jquery-rails
que ya tenemos en el Gemfile
. Sin embargo los estilos de CSS que necesitamos para la tabla no vienen incuidos, pero hay otra gema jquery-ui-rails
que sí que los trae. Tan sólo tendremos que incluirla en el Gemfile
y ejecutar bundle
de nuevo.
group :assets do gem 'sass-rails', '~> 3.2.3' gem 'coffee-rails', '~> 3.2.1' # See https://github.com/sstephenson/execjs#readme for more supported runtimes # gem 'therubyracer', :platform => :ruby gem 'uglifier', '>= 1.0.3' gem 'jquery-datatables-rails', github: 'rweng/jquery-datatables-rails' gem 'jquery-ui-rails' end
Tenemos que añadir los fichero jquery.ui.core
y jquery.ui.theme
en los ficheros CSS de nuestra aplicación, con lo que tendremos disponible el tema Smoothness. También tenemos que cambiar el fichero de CSS de DataTables por uno que haya sido diseñado para jQuery UI. Resulta que viene uno en el directorio src
llamado demo_table_jui
, por lo que tendremos que cambiar la hoja de estilos de jQuery.dataTables
por este otro.
/* * This is a manifest file that'll automatically include all the stylesheets available in this directory * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at * the top of the compiled file, but it's generally better to create a new file per style scope. *= require_self *= require jquery.ui.core *= require jquery.ui.theme *= require dataTables/src/demo_table_jui *= require_tree . */
Ahora tenemos que pasar la opción bJQueryUI
al invocar a dataTable
.
jQuery -> $('#products').dataTable sPaginationType: "full_numbers" bJQueryUI: true
Por último a la tabla le tenemos que dar la clase display
.
<table id="products" class="display">
Veremos que al recargar la página la tabla tiene un aspecto mucho mejor.
Actualización de los datos de la tabla desde el servidor
La tabla dispone de paginación, ordenación y búsquedas, pero estas ocurren en el cliente. Nuestra página HTML muestra todos los productos que hay en la base de datos y DataTables escoge cuáles tiene que mostrar basándose en la página, ordenación y término de búsqueda. Esto funciona bien con grupos de registros pequeños, digamos hasta 200, pero si tenemos miles de registros evidentemente no los queremos enviar todos para generar la tabla en HTML en el cliente. En estos casos es mejor enviar sólo los registros necesarios al cliente y que DataTable se comunique con el servidor cuando se modifican la página, el término de búsqueda o la ordenación. La pagína de procesamiento en el lado del servidor de la documentación de DataTables nos indica los parámetros que se envían al servidor y qué podemos esperar en la respuesta en forma de JSON. Tenemos que configurar nuestra aplicación Rails para poder trabajar de esta forma paginando en el servidor, así que añadiremos la gema will_paginate
a nuestro Gemfile
y ejecutaremos bundle
de nuevo para instalarlo.
gem 'will_paginate'
Ya no tenemos que mostrar todos los productos de nuestra plantilla porque ahora se recuperan del servidor. Así que tendremos que definir la URL de la que se puedan ir recogiendo los productos, y la pondremos como atributo de datos en la etiqueta table
. Usaremos el helper de ruta products_url
indicando que hay que utilizar el formato JSON.
<h1>Products</h1> <table id="products" class="display" data-source="<%= products_url(format: "json") %>"> <thead> <tr> <th>Product Name</th> <th>Category</th> <th>Release Date</th> <th>Price</th> </tr> </thead> <tbody> <tbody> </table>
En nuestro fichero Coffeescript tendremos que añadir varias opciones en la llamada a dataTable
para decirle que tiene que obtener los datos desde el servidor.
jQuery -> $('#products').dataTable sPaginationType: "full_numbers" bJQueryUI: true bProcessing: true bServerSide: true sAjaxSource: $('#products').data('source')
La opción bServerSide
indica que hay que recuperar los datos del servidor, y la opción bProcessing
muestra un mensaje temporalmente mientras se obtienen los datos del servidor. Por último sAjaxSource
define la URL de la que se recuperarán los datos, y nosotros hemos hecho que sea la misma que el valor del atributo data-source
de la tabla. DataTable atacará la acción index
de ProductsController
, en espera de una respuesta JSON. Utilizaremos el bloque respond_to
para enviarla. La parte difícil consiste en responder con el JSON adecuado. Una opción sería utilizar JBuilder o RABL, pero estas soluciones se pueden complicar rápidamente porque hay mucha lógica de por medio para generar la respuesta correcta.
No debemos temer crear nuestras propias clases si la estructura MVC de Rails nos limita y vemos que no encaja con lo que queremos hacer sobre todo si como resultado el código queda más limpio. Crearemos una clase ProductsDatatable
para generar el JSON y haremos que reciba un contexto de la vista para que tenga acceso a los métodos helper, con lo que lo hemos convertido en una especie de mini-presentador para servir respuestas en JSON. Crearemos la clase en un nuevo directorio llamado datatables
debajo del directorio de la aplicación. Hay demasiado código en la clase como para mostrarlo aquí, pero el resultado final se puede ver en Github. Démosle un repaso rápido para ver lo más destacado.
El initializer
recibe un contexto de vista y se lo asigna a una variable de instancia, delegando sobre ésta algunos métodos helper que nos interesará invocar más adelante.
delegate :params, :h, :link_to, :number_to_currency, to: :@view def initialize(view) @view = view end
El método as_json
es activado por la llamada a render_json
del controlador, y devuelve todos los datos que espera DataTables, incluyendo las filas relevantes de la base de datos. Estos datos vienen de un método llamado data
que itera sobre los productos y devuelve un array bidimensional que contiene la información de cada celda de la tabla.
def as_json(options = {}) { sEcho: params[:sEcho].to_i, iTotalRecords: Product.count, iTotalDisplayRecords: products.total_entries, aaData: data } end private def data products.map do |product| [ link_to(product.name, product), h(product.category), h(product.released_on.strftime("%B %e, %Y")), number_to_currency(product.price) ] end end
Los productos se recuperan con el método fetch_products
, que recupera la página de datos correcta en el orden adecuado y filtrando por el término de búsqueda (si está presente) La consuelta en sí misma es sencilla pero podríamos enriquecerla utilizando un motor de búsqueda de texto completo como Sphinx o Solr.
def fetch_products products = Product.order("#{sort_column} #{sort_direction}") products = products.page(page).per_page(per_page) if params[:sSearch].present? products = products.where("name like :search or category like :search", search: "%#{params[:sSearch]}%") end products end
El resto de la clase se compone de métodos que ayudan con la paginación y ordenación. Ahora que tenemos esta clase podemos emplearla en nuestra acción del controlador.
def index respond_to do |format| format.html format.json { render json: ProductsDatatable.new(view_context) } end end
Tras recargar la página (probablemente tengamos que reiniciar primero el servidor) veremos que todo funciona igual que antes. Seguimos teniendo la misma funcionalidad de paginación, ordenación y búsquedas pero los datos ahora los envía la aplicación Rails.
Con esto finaliza nuestro episodio dedicado a DataTables, que requiere cierto esfuerzo para configurarlo en el lado del servidor pero el resultado final merece la pena. Aquellos que estén interesados en una solución alternativa escrita desde cero pueden consultar el episodio 240.