#324 Passing Data to JavaScript
- Download:
- source codeProject Files in Zip (87.5 KB)
- mp4Full Size H.264 Video (14.6 MB)
- m4vSmaller H.264 Video (7.43 MB)
- webmFull Size VP8 Video (9.2 MB)
- ogvFull Size Theora Video (16.3 MB)
En las aplicaciones Rails donde JavaScript juega un papel importante suele ser necesario pasar datos desde la aplicación en el servidor a JavaScript donde pueda ser usado por el código en cliente. En este episodio vamos a explorar algunas técnicas que sirven para hacer esto. A continuación se muestra una página de una aplicación sencilla en Rails y la idea es que queremos controlar mediante JavaScript la recuperación y visualización de los productos. Para hacerlo tenemos que pasar cierta información desde nuestra aplicación hacia el cliente de JavaScript que se ejecuta en el navegador.
La plantilla de esta página es muy sencilla.
<h1>Products</h1> <div id="products"> Loading products... </div>
Supongamos que el JavaScript de nuestra aplicación tiene que saber la URL a llamar para recuperar el listado de productos. Como no queremos escribir a fuego esta dirección en el código JavaScript vamos a generarla dinámicamente desde nuestra aplicación Rails. Una forma de hacer esto es con la función javascript_tag
de la vista.
<%= javascript_tag do %> window.productsURL = '<%= j products_url %>'; <% end %>
Nótese que utilizamos el método Rails j
que nos asegura que la URL aparecerá codificada para poder ser usada en JavaScript. Ahora podemos usar esta variable en cualquiera de los ficheros JavaScript o CoffeeScript que hagan referencia a esta página.
jQuery -> alert productsURL
Si queremos que en la página aparezca precargado un conjunto inicial de productos en lugar de tener que obtenerlos mediante una petición separada podríamos, por ejemplo, obtener los primeros diez productos en JSON y hacer que JavaScript muestre esta información directamente. Podemos hacer esto así:
<%= javascript_tag do %> window.productsURL = '<%= j products_url %>'; window.products = <%= raw Product.limit(10).to_json %> <% end %>
Rails intentar escapar para HTML el listado de productos por lo que tendremos que invocar raw
sobre el resultado en JSON.
Este enfoque puede hacerse pesado muy deprisa, y por lo general es mejor utilizar atributos de datos de HTML. Podemos pasar la URL de productos de la siguiente manera:
<h1>Products</h1> <div id="products" data-url="<%= products_url %>"> Loading products... </div>
Podemos recuperar fácilmente la información usando código jQuery:
jQuery -> alert $('#products').data('url')
Esta técnica surte el mismo efecto pero la sensación es mucho más limpia. Para evitar tener que anidar etiquetas ERb dentro de atributos HTML podemos utilizar content_tag
que es una mejor manera de insertar datos dinámicos dentro de una etiqueta HTML.
<h1>Products</h1> <%= content_tag "div", id: "products", data: {url: products_url} do %> Loading products... <% end %>
Desde la version 3.1 de Rails en adelante podemos usar un hash llamado data
para definir atributos de datos, con lo que todo este enfoque resulta aún más sencillo. Si a este hash le pasamos objetos en Ruby, se invoca to_json
para convertir automáticamente el objeto a su representación en JSON.
<h1>Products</h1> <%= content_tag "div", id: "products", data: {url: Product.limit(10) } do %> Loading products... <% end %>
Al recuperar estos datos, se convertirán automáticamente a objetos JavaScript.
jQuery -> console.log $('#products').data('url')
Si ahora recargamos la página veremos el listado de productos en la consola.
Gon
Pero si tenemos que pasar muchos datos hacia JavaScript esta técnica también puede hacerse engorrosa. Por suerte hay una solución que podemos usar: la gema Gon. Nos permite establecer variables en nuestros controladores para luego acceder a ellos desde JavaScript. Gon se instala de la manera habitual, añadiéndolo al Gemfile
y ejecutando bundle
.
source 'https://rubygems.org' gem 'rails', '3.2.1' gem 'sqlite3' # Gems used only for assets and not required # in production environments by default. 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' gem 'uglifier', '>= 1.0.3' end gem 'jquery-rails' gem 'gon'
A continuación tenemos que actualizar el fichero de layout de nuestra aplicación añadiendo include_gon
dentro de la sección head
.
<head> <title>Store</title> <%= include_gon %> <%= stylesheet_link_tag "application", media: "all" %> <%= javascript_include_tag "application" %> <%= csrf_meta_tag %> </head>
Si hacemos esto antes de cargar ningún otro archivo JavaScript podremos acceder a las variables que hayamos establecido mediante Gon sin tener que esperar a que el DOM termine de cargar.
Podemos establecer variables en el objeto gon
dentro de una acción de nuestro controlador.
class ProductsController < ApplicationController def index gon.products = Product.limit(10) end end
Este listado de productos se pasará automáticamente a JSON y estará accesible para el JavaScript de nuestra aplicación. Podemos acceder al listado de productos de la siguiente manera:
console.log gon.products
Nótese que no tenemos que esperar a que el documento termine de cargar para acceder a estos datos. Si recargamos la página volveremos a ver el listado de productos en la consola.
Si abrimos el código fuente de la página podremos comprobar cómo funciona:
<!DOCTYPE html> <html> <head> <title>Store</title> <script>window.gon = {};gon.products=[{"created_at":"2012-02-18T20:24:45Z","id":1,"name":"Settlers of Catan","price":"29.95","updated_at":"2012-02-18T20:24:45Z"},{"created_at":"2012-02-18T20:24:45Z","id":2,"name":"DVD Player","price":"79.98999999999999","updated_at":"2012-02-18T20:24:45Z"},{"created_at":"2012-02-18T20:24:45Z","id":3,"name":"Red Shirt","price":"12.49","updated_at":"2012-02-18T20:24:45Z"}];</script> // Other products omitted. <link href="/assets/application.css?body=1" media="all" rel="stylesheet" type="text/css" /> <link href="/assets/products.css?body=1" media="all" rel="stylesheet" type="text/css" /> <script src="/assets/jquery.js?body=1" type="text/javascript"></script> <script src="/assets/jquery_ujs.js?body=1" type="text/javascript"></script> <script src="/assets/products.js?body=1" type="text/javascript"></script> <script src="/assets/application.js?body=1" type="text/javascript"></script> <meta content="authenticity_token" name="csrf-param" /> <meta content="KktOQAWKvaRho+IdWOiaBud0W7Vuv31rdn0LF38hBag=" name="csrf-token" /> </head> <body> <!-- Body omitted --> </body> </html>
Gon simplemente crea una etiqueta script
y a continuación construye una variable llamada gon
con los datos que hayamos establecido en el controlador. Gon permite personalizar el JSON devuelto al cliente para plantillas escritas con RABL y Jbuilder, que ya hemos visto en capítulos recientes. Para personalizar los datos devueltos por el listado de productos podemos crear un fichero index.json.rabl
y definir los atributos que queramos devolver.
collection Product.limit(10) attributes :id, :name, :price
Podemos utilizar gon.rable
para decirle a Gon que utilice esta plantilla.
class ProductsController < ApplicationController def index gon.rabl "app/views/products/index.json.rabl", as: "products" end end
Esto almacenará la respuesta de la plantilla en una variable llamada products
. Al recargar la página veremos el mismo listado de productos pero los atributos serán aquellos que se hayan definido en la plantilla RABL.
Hay un pequeño problema potencial cuando se usa Gon. Si no definimos variables de gon
en la acción del controlador e intentamos llamar a gon
en el JAvaScript recibiremos un error porque el objeto gon
no habrá sido creado. Lo mejor es asegurarse de que el objeto exista antes de hacer ninguna llamada.
console.log gon.products if gon
Hay más información acerca de Gon en su documentación que merece la pena leer si pensamos usarlo en nuestras aplicaciones.