Posts en la categoría: HTML, CSS & JavaScript

Generación dinámica de librerías Javascript internacionalizadas con GetText

La internacionalización es uno de los requisitos del proyecto que estamos desarrollando actualmente en Trabe Soluciones con Ruby on Rails. Para cumplirlo estamos utilizando GetText y Ruby GetText para la internacionalización de cadenas y código propio para internacionalizar ActiveRecord (un enfoque habitual cuando uno se enfrenta a este problema).

La aplicación además, cuenta con una adminsitración que, por su interfaz, utiliza una cantidad considerable de Javascript. Dicho Javascript también debe estar internacionalizado. Lo primero que se nos puede pasar por la cabeza es separar las cadenas que requieran traducción a unos ficheros separados y que nuestro layout enlace uno u otro en función del idioma de la petición (o el idioma preferido del usuario). Si lo hacemos así, estamos desaprovechando todas las ventajas que nos aporta el uso de GetText, en especial las facilidades para gestionar la traducción de cadenas (utilizando herramientas como poEdit).

Lo que vamos a hacer es generar los Javascripts dinámicamente utilizando Ruby embebido, como un rhtml cualquiera, con lo que podremos incrustar nuestras llamadas a GetText con total impunidad. Resumiendo, que lo que queremos es utilizar algo como ésto en nuestro layout:

<%= javascript_include_tag "/dyn_javascripts/#{Locale.get}/application" %>

Para ello necesitamos un poco de código.

Creamos un controlador que responda a las peticiones y que simplemente delegue en la vista correspondiente. Estas vistas estarán en la carpeta app/views/dyn_javascripts y tendrán la extensión .djs (por ejemplo).

class DynJavascriptsController < ApplicationController                

  layout false                

  def method_missing(symbol, *args)
    if symbol.to_s =~ /(.*)\.js$/ 
      render :action => $1
    end
  end  
end

Añadimos la ruta correspondiente al fichero de rutas (routes.rb). En ella pasamos el idioma como parametro porque nuestros filtros de GetText lo buscan en la URL, no por vicio.

map.connect 'dyn_javascripts/:language/:action.js', :controller => 'dyn_javascripts'

Ahora creamos un fichero que requeriremos al iniciar la aplicación (en el fichero environment.rb). Este fichero incluye una clase que procesa nuestras plantillas .djs y que debemos registrar con el métodod register_template_handler de ActionView::Base::. Además incluimos el código necesario para que la aplicación encuentre el controlador que hemos definido previamente (la última línea de código) de tal modo que se recargue con cada petición en modo desarrollo.

require 'erb'

module DynJavascripts  
  class View

    def initialize(action_view) #:nodoc:
      @action_view = action_view
    end

    def render(template, local_assigns={})
      @action_view.controller.headers["Content-Type"] = 'text/javascript'      
      @action_view.instance_eval { ERB.new(template).result(binding) }      
    end    

  end 

end

ActionView::Base::register_template_handler 'r_js', DynJavascripts::View

# Nuestro controladoe está situado en 
# lib/common/dyn_js/dyn_javascripts_controller.rb
Dependencies.load_paths << "#{RAILS_ROOT}/lib/common/dyn_js" 

Hecho todo esto ya podemos escribir código simpático como el siguiente en nuestra carpeta app/view/dyn_javasctripts:

# Archivo app/views/dyn_javascripts/application.djs
function changeALink(whichLink) {
    var the_link = document.getElementById(whichLink);
    the_link.href =  "<%= url_for :controller => 'a_controller'  %>"; 
    the_link.innerHTML = "_('Some text')";
}

El último detalle es conseguir que Ruby-GetText extraiga las cadenas de nuestros ficheros .djs. Para ello, vamos a indicarle al parser de rhtml que “sabe” procesar nuestros ficheros djs. La tarea “Rake”: de actualización de los ficheros .po quedaría tal que así:

desc "Update pot/po files."
task :updatepo do
  require 'gettext/utils'

  # 
  # ¡¡¡ Abracadabra!!!
  # 
  GetText::ErbParser.instance_variable_set(
      '@config', :extnames => ['.rhtml', '.r_js'])

   GetText.update_pofiles("app_domain",
   Dir.glob("{app,lib,bin}/**/*.{rb,rhtml,rjs,r_js}"), "App 1.0.0"")
end

Sencillo, ¿no?

Favicon en aplicaciones Rails en desarrollo

Cuando generamos una aplicación Rails, en la carpeta public se crea el fichero favicon.ico. Si lo cambiamos por nuestro propio icono podremos verlo una vez hayamos desplegado la aplicación en nuestro servidor. Lo normal es que lo hagamos utilizando un Apache o un lighty y estos se encargarán de enviarle automáticamente el icono al navegador. En desarrollo utilizando un webrick o un mongrel esto no es así y no debemos olvidarnos de que si queremos ver el icono debemos enlazarlo expresamente en el header de nuestras páginas HTML con algo como:

<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />

Algo muy sencillo y que no nos cuesta nada escribir para poder ver como quedan nuestros favicons.

La propiedad table-layout en CSS

Como podéis ver en la especificación de CSS2, existe una propiedad CSS llamada table-layout que nos permite especificar al user agent (que para el 99% de nosotros es el navegador, para qué nos vamos a engañar) qué algoritmo debe utilizar a la hora de calcular el tamaño de una tabla para mostrarla. Los posibles valores para esta propiedad son fixed y auto.

table-layout: fixed

Cuando se especifica fixed el navegador deberá utilizar un algoritmo rápido, que calcula el ancho de la tabla basándose en el ancho de las celdas de la primera fila. De este modo, llega con que esta primera fila esté cargada: en ese momento el navegador ya puede determinar el layout de la tabla y pintar sus contenidos.

table-layout: auto

En este caso el navegador puede utilizar cualquier algoritmo, siendo lo más normal que emplee uno que determina el layout de la tabla en función del ancho de sus columnas (de toda la columna, no sólo de las celdas de la primera fila, como en el caso del algorito fixed). En este caso, hasta que no dispone de todo el contenido de la tabla, el navegador no puede determinar su layout definitivo.

Y saber esto… ¿de qué me vale?

Pues para un par de cosas, al menos. La primera y más evidente tiene que ver con la eficiencia. Si tenemos que mostrar grandes tablas de datos (grandes quiere decir grandes, muy grandes, con cientos o miles de filas; ya sé que existe la paginación, pero también sé que hay gente que prefiere no darse por enterada y quiere esas gigantescas tablas en sus aplicaciones), cambiar el algoritmo de auto (su valor por defecto) a fixed puede suponer la diferencia entre que el navegador se cuelgue o podamos ver la tabla.

La segunda ya no es tan evidente, pero en algunas ocasiones puede ser incluso más importante, y tiene que ver con la carga de los datos y su presentación al usuario. En nuestro caso concreto, acordarnos de la existencia de la propiedad table-layout resultó de gran utilidad mientras trabajábamos en la vista de Mendeleweb . Si visitáis la página podréis ver que lo primero que se muestra es una tabla periódica en la que cada celda contiene una foto de algún personaje (“elemento” en la jerga de Mendeleweb). Ahora mismo cada imagen se carga correctamente en su celda, posicionada en el lugar que le corresponde sobre la imagen de fondo, que representa los bordes de la tabla. Pero en las etapas iniciales del desarrollo las imágenes iban apareciendo a medida que se iban cargando y se iban colocando como un acordeón, hasta posicionarse correctamente en su lugar sólo cuando toda la tabla estaba cargada. Evidentemente, esto nos dio una pista muy buena e hizo que la propiedad table-layout viniese a nuestra memoria :o). Con sólo añadir esta propiedad y establecer su valor a fixed en las CSS conseguimos que los “elementos” de la tabla periódica apareciesen desde un primer momento en el lugar que Mendeleiev les asignó.

Microsoft Office 2003 Professional Edition Adobe Creative Suite 2 Premium Macromedia Dreamweaver 8 [Mac] Microsoft Office Visio Professional 2007 Microsoft Windows XP Professional SP3 Adobe Flash Media Server 3.0 Adobe Font Folio 11 Microsoft Office 2008 [Mac] Adobe Acrobat 8 Professional [Mac] Adobe InDesign CS3 [Mac]