homeASCIIcasts

199: Dispositivos móviles 

(view original Railscast)

Other translations: En Fr It

Other formats:

Written by Juan Lupión

Cada vez es más y más frecuente navegar por sitios web utilizando dispositivos móviles, que tienen capacidades de pantalla mucho más limitadas que sus primos los equipos portátiles o de escritorio. Debido a esto es muy importante probar nuestras aplicaciones web en estos dispositivos para comprobar cómo funcionan.

La mejor forma de hacerlo es probar la aplicación en el dispositivo real. Por supuesto, no podemos navegar a "localhost" con un móvil así que tendremos o bien que utilizar la IP de la máquina en la que corre nuestra aplicación o bien su nombre de dominio local, asumiendo que el móvil se encuentra en la misma red local. Por ejemplo, la máquina en la que estamos ejecutando la aplicación que veremos en este episodio se llama noonoo por lo que podemos visitarla desde cualquier otra máquina de la misma red utilizando http://noonoo.local:3000/.

La página de projectos de nuestra aplicación.

Si no tenemos acceso a un dispositivo físico para pruebas podríamos descargar un simulador de este dispositivo. Si queremos testear en un iPhone podemos descargar el simulador que está disponible en el sitio para desarrolladores de Apple. Igualmente existe un emulador disponible como parte del SDK para Palm Pré.

Otra alternativa para hacer pruebas en iPhone es iPhoney. Proporciona una emulación casi perfecta de un iPhone -aunque no tan perfecta como la del simulador de Apple- con la ventaja de que no tenemos que descargar todo el SDK de iPhone para poder utilizarlo. Si lo usamos para probar un sitio web que debe tener un comportamiento diferente para dispositivos móviles tendremos que acordarnos de configurar el User Agent adecuado en el menú de iPhoney para que se muestre el contenido correcto.

El sitio visto en el simulador iPhoney.

Nuestra aplicación en iPhoney.

La apariencia de nuestra aplicación es francamente mejorable en estos dispositivos móviles y durante el resto de este episodio veremos cómo hacerlo.

Para empezar añadiremos al fichero de layout de nuestra aplicación una hoja de estilos que sólo se incluirá si accedemos a la aplicación desde un dispositivo móvil.

/app/views/products/_fields.html.erb

  
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
  <head>
    <title><%= h(yield(:title) || "Untitled") %></title>
    <%= stylesheet_link_tag 'application' %>
    <%= stylesheet_link_tag 'mobile' if mobile_device? %>
    <%= yield(:head) %>
  </head>
  <body>
  <!-- content omitted -->
  </body>
</html>

La nueva hoja de estilos para móviles sólo se incluira si cierto método denominado mobile_device? devuelve true. Podríamos haber usado el atributo media del elemento link para restringir qué dispositivos podrían hacer uso de esta hoja de estilos, pero los navegadores de móviles a veces interpretan este atributo de diferentes maneras. Si utilizamos nuestro propio método para determinar si incluimos o no esta hoja de estilos podremos controlar qué dispositivos la incluyen simplemente leyendo la cadena de User Agent enviada por el navegador.

Por supuesto nuestra próxima tarea es escribir el método mobile_device?. Lo pondremos en ApplicationController para que todos los controladores puedan acceder a este método.

/app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  helper :all
  protect_from_forgery

  private
  def mobile_device?
    request.user_agent =~ /Mobile|webOS/
  end
  helper_method :mobile_device?
end

En el método comprobamos la cadena de agente de usuario para ver si contiene o bien la palabra "Mobile" (lo cual detectará dispositivos iPhone y Android) o bien la palabra "webOS" (que detectará dispositivos Palm Pré). Por supuesto podemos personalizar la expresión regular para detectar cualquier otro dispositivo al que le queramos mostrar la hoja de estilos alternativa (existe un listado exhaustivo de cadenas que podemos buscar para detectar otros dispositivos móviles). Por último, marcamos el método como helper para que pueda ser utilizado desde las vistas.

Ahora que hemos escrito este método podemos crear la hoja de estilos que se enviará a los dispositivos móviles.

/public/stylesheets/mobile.css

Podemos utilizar la funcionalidad de cambio de User Agent en Safari para ver el aspecto de la página con la hoja de estilos para móviles. En el menú Develop > User Agent hay una lista de cadenas de User Agent que podemos escoger, incluyendo varias versiones de Mobile Safari. Si escogemos cualquiera de ellas y recargamos la página esta vez la veremos con la hoja de estilos para móviles.

The site with the mobile stylesheet applied.

Cambiando de sitio

Una vez que nuestro helper mobile_device? hace lo que queremos podemos utilizarlo para cambiar el comportamiento de nuestro sitio dependiendo del dispositivo con el que se accede. Tendremos que añadir un enlace para que los usuarios puedan ver el sitio en su versión completa o en su versión móvil. Para ello vamos a cambiar el fichero de layout de nuestra aplicación y pondremos el siguiente código al principio de la etiqueta body.

/app/views/layouts/application.html.erb

  
<p>
  <% if mobile_device? %>
    <%= link_to "Full Site", :mobile => 0 %>
  <% else %>
    <%= link_to "Mobile Site", :mobile => 1 %>
  <% end %>
</p>

Con esto veremos un enlace a la versión completa del sitio si estamos viendo la versión móvil, y viceversa. Este enlace redirige a la página actual con un parámetro llamado mobile que determinará qué versión del sitio mostrar.

En nuestro ApplicationController podemos añadir un before_filter que establecerá una variable de sesión de forma que una vez que se haya hecho clic en el enlace se siga mostrando la versión escogida al usuario cuando continúe su navegación por nuestro sitio. El método before_filter establecerá una variable de sesión si aparece el parámetro mobile en la cadena de la petición. Vamos también a cambiar nuestro método mobile_device? de forma que compruebe si existe dicha variable de sesión y actúe en consecuencia.

/app/controllers/application_controller.rb

  
class ApplicationController < ActionController::Base
  helper :all
  protect_from_forgery
  before_filter :prepare_for_mobile

  private
  def mobile_device?
    if session[:mobile_param]
      session[:mobile_param] == "1"
    else
      request.user_agent =~ /Mobile|webOS/
    end
  end
  helper_method :mobile_device?

  def prepare_for_mobile
    session[:mobile_param] = params[:mobile] if params[:mobile]
  end
end

Si ahora recargamos la página veremos que hay un enlace a la versión completa del sitio y si hacemos clic en él veremos la versión completa aún cuando estamos accediendo utilizando un navegador cuya cadena de User Agent contiene la cadena "mobile".

Ahora aparece el enlace a la versión para móviles.

Esta preferencia será persistente de forma que si se sigue cualquier enlace seguiremos estando en la versión completa de la aplicación.

Vistas separadas para dispositivos móviles

Lo que hemos hecho hasta ahora nos funcionará para los casos en los que queramos hacer algunos retoques a la aplicación cuando se vea en dispositivos móviles. Pero, ¿qué pasa si tenemos planes más ambiciosos y lo que queremos es cambiar la aplicación de forma que tenga un aspecto y un comportamiento similares a una aplicación nativa cuando se acceda desde un dispositivo móvil? Para hacerlo tendríamos que cambiar prácticamente cada una de las vistas de nuestra aplicación...

El truco aquí es crear un nuevo tipo MIME para nuestra aplicación, y Rails proporciona el sitio adecuado para hacerlo en el fichero config/initializers/mime_types.rb. Dicho fichero contiene un ejemplo comentado de cómo proporcionar un nuevo tipo iPhone en el que nos basaremos para crear uno llamado mobile, que nos devolverá un formato HTML alternativo para dispositivos móviles.

/config/initializers/mime_types.rb

  # Be sure to restart your server when you modify this file.

  # Add new mime types for use in respond_to blocks:
  # Mime::Type.register "text/richtext", :rtf
  # Mime::Type.register_alias "text/html", :iphone
  Mime::Type.register_alias "text/html", :mobile

Aún tenemos que establecer dicho tipo MIME y para hacerlo tenemos que regresar al before_filter de nuestra aplicación y establecer el formato MIME a :mobile si hay que mostrar la versión móvil del sitio.

/app/controllers/application_controller.rb

  
def prepare_for_mobile
  session[:mobile_param] = params[:mobile] if params[:mobile]
  request.format = :mobile if mobile_device?
end

Una vez que tengamos esto podemos utilizarlo en las acciones de nuestros controladores para cambiar el comportamiento de cada acción utilizando respond_to, tal y como vemos en la acción index del controlador de proyectos.

/app/controllers/projects_controller.rb

  
def index
  @projects = Project.all
  respond_to do |format|
    format.html
    format.mobile
  end
end

Sin embargo, el bloque respond_to no es necesario si lo que estamos haciendo es únicamente proporcionar una vista alternativa basada en el formato de la petición. En este caso tan sólo tenemos que escribir una nueva plantilla con el nombre del formato donde normalmente aparecería html Para la vista de índice crearemos un fichero llamado /app/views/projects/index.mobile.erb y para empezar simplemente escribiremos en él algún texto.

/app/views/projects/index.mobile.erb

  
This is a mobile version!

Si visitamos la versión móvil de la página ahora veremos que la vista se está mostrando con tipo MIME mobile.

Ahora se
muestra la versión de la plantilla para móviles.

Basándonos en esto ahora podemos crear una interfaz de usuario que dará una aspecto mucho más parecido al de una aplicación nativa. Hay un par de librerías que podemos utilizar para hacerlo más fácil, iui y jQTouch, nosotros utilizaremos esta última. jQTouch hace que sea mucho más fácil crear una aplicación web que se comporte como una aplicación iPhone nativa.

Después de descargar y descomprimir jQTouch tendrá una estructura de carpetas como:

The directory  structure for jQTouch.

Para que nos sea más fácil trabajar con jQTouch vamos a mover las carpetas de temas y extensiones a la carpeta jqtouch y luego arrastraremos dicha carpeta al directorio public de nuestra aplicación.

A continuación crearemos un nuevo fichero de layout para la versión móvil de nuestro sitio.

/app/views/layouts/application.mobile.erb

  
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
  <head>
    <title><%= h(yield(:title) || "Untitled") %></title>
    <%= stylesheet_link_tag "/jqtouch/jqtouch.min.css", "/jqtouch/themes/apple/theme.min.css" %>
    <%= javascript_include_tag "/jqtouch/jquery.1.3.2.min.js", "/jqtouch/jqtouch.min.js", "mobile" %>
    <%= yield(:head) %>
  </head>
  <body>
    <div class="current">
      <%- if show_title? -%>
      <div class="toolbar">
        <%= link_to "Back", nil, :class => "back" unless current_page? root_path %>
        <h1><%=h yield(:title) %></h1>
        <%= yield(:toolbar) %>
      </div>
      <%- end -%>

      <% unless flash.empty? %>
        <div class="info">
        <%- flash.each do |name, msg| -%>
          <%= content_tag :div, msg, :id => "flash_#{name}" %>
        <%- end -%>
        </div>
      <% end %>

      <%= yield %>
    </div>
  </body>
</html>

En este fichero incluimos referencias a un par de hojas de estilo que proporciona jQTouch así como al JavaScript de jQuery y de la propia jQTouch. También se hace referencia a un archivo mobile.js que escribiremos más adelante. En el layout el contenido de la página está rodeado de un div con la clase current. Si la página debe tener un título lo mostraría rodeado por otro div con la clase toolbar. Así mismo si hay cualquier texto informativo que visualizar lo colocaríamos en otro div esta vez con la clase info. Después de ésto, simplemente se hace un yield mostrar lo que venga en la plantilla actual.

A continuación crearemos el nuevo fichero mobile.js. Lo que tenemos que hacer aquí es invocar al inicializador de jQTouch.

/public/javascripts/mobile.js

  
$.jQTouch({});

La función de inicialización recibe un hash de opciones aunque nosotros no configuraremos ninguna. Cuando recarguemos la página móvil de proyectos esta vez veremos que se aplican los estilos de jQTouch, si bien la página tendrá un aspecto poco atractivo porque aún no tenemos ningún contenido en la plantilla de la página.

Ya se aplica el CSS de jQTouch.

Si volvemos al código de la vista para móviles de la página podemos reemplazar lo que teníamos con el código necesario para listar todos los proyectos y un contador del número de tareas que tiene cada uno de ellos, así como un enlace para crear un nuevo proyecto.

/app/views/projects/index.mobile.erb

  
<% title "Projects" %>
  <ul>
    <% for project in @projects %>
    <li class="arrow">
      <%= link_to h(project.name), project %>
      <small class="counter"><%= project.tasks.size %></small>
    </li>
    <% end %>
  </ul>
  <ul><li class="arrow"><%= link_to "New Project", new_project_path %></li></ul>

Si recargamos la página otra vez veremos un interfaz totalmente equiparable al de una aplicación nativa.

La versión móvil de la página principal ahora parece una aplicación nativa.

Obviamente la interfaz será mucho más estrecha cuando se vea en un iPhone de verdad, pero funcionará correctamente en un navegador de escritorio.

Habrá que hacer una versión para móvil de todas y cada una de las vistas de nuestra aplicación. Se trata de demasiado código como para mostrarlo aquí, pero los archivos están disponibles para su descarga en la página de GitHub de Ryan Bates. Una vez que lo hagamos tendremos una aplicación para móvil totalmente funcional que tiene el aspecto de una aplicación nativa.

La página de alta de proyecto en la versión móvil.

Si bien hemos mejorado mucho el aspecto de la versión móvil de nuestro sitio hemos perdido la posibilidad de volver a la versión completa. Añadiremos un botón a la derecha de la barra superior de herramientas para volver a tener esta funcionalidad.

La barra de herramientas está definida en el fichero de layout y es muy fácil añadir nuevos controles en ella porque los elementos en jQTouch se definen con etiquetas HTML. Podemos añadir un nuevo botón creando un nuevo enlace dentro de la barra de herramientas con la clase button.

/app/views/layouts/application.mobile.erb

  
<div class="toolbar">
  <%= link_to "Back", nil, :class => "back" unless current_page? root_path %>
  <h1><%=h yield(:title) %></h1>
  <%= link_to "Full Site", root_url(:mobile => 0), :class => "button", :rel => "external" %>
   <%= yield(:toolbar) %>
</div>

También tenemos que proporcionar el atributo rel con valor external para que jQTouch trate el enlace como un enlace a otro sitio, si no hiciésemos así lanzaría una petición AJAX, que no es lo que pretendemos.

Si recargamos la página por última vez tendremos un botón en cada página que nos permite volver a la versión completa de nuestra aplicación.

La versión móvil
ahora tiene un enlace a la versión completa.