#263 Client Side Validations
- Download:
- source codeProject Files in Zip (282 KB)
- mp4Full Size H.264 Video (21.1 MB)
- m4vSmaller H.264 Video (11.5 MB)
- webmFull Size VP8 Video (24.1 MB)
- ogvFull Size Theora Video (26.2 MB)
Puede ser muy frustrante rellenar un largo formulario en una web sólo para que éste regrese con varios errores de validación después de enviarlo. Como experiencia es mucho mejor que los errores vayan apareciendo en cada campo según vayamos completándolos, pero esto puede implicar escribir mucho código de validación JavaScript.
En este episodio vamos a ver una gema que nos permitirá crear automáticamente validaciones del lado de cliente. Supongamos que tenemos una aplicación con un formulario de registro que sirve para crear un nuevo modelo de usuarios con cuatro campos como el siguiente:
El modelo User
tiene varias validaciones de campos obligatorios por lo que si enviamos el formulario sin rellenar alguno de dichos campos veremos una lista de errores de validación.
Como dijimos anteriormente sería mucho mejor poder mostrar los errores que tiene un campo determinado tan pronto como el usuario cambia de campo pero el inconveniente de esto es que tendríamos que escribir mucho JavaScript, especialmente si tenemos varios modelos que debemos validar de esta forma o si las validaciones son complicadas. Para ahorrarnos este trabajo vamos a utilizar una gema llamada client_side_validations, que nos ahorrará todo este trabajo. La gema lee las validaciones de un modelo y genera el código JavaScript necesario para implementar las mismas validaciones del lado del cliente que se activan cuando el usuario rellena un formulario de dicho modelo. El único requisito no estándar de la gema es el uso de jQuery por lo que si estamos usando Prototype para nuestras aplicaciones Rails tendremos que encontrar una solución altenativa.
Instalación de client_side_validations
Para instalar la gema tenemos que añadir una referencia en el fichero Gemfile
de la aplicación y luego ejecutar bundle
.
source 'http://rubygems.org' gem 'rails', '3.0.7' gem 'sqlite3' gem 'nifty-generators' gem 'bcrypt-ruby', :require => 'bcrypt' gem 'jquery-rails' gem 'client_side_validations'
Obsérvese que ya hemos incluido jQuery en la aplicación con la gema jquery-rails. Una vez que tengamos la gema client_site_validations instalada tenemos que ejecutar el generador que añadirá varios archivos a nuestra aplicación: un fichero de configuración en el directorio config/initializers
y un fichero JavaScript que debemos incluir desde el fichero de layout.
$ rails g client_side_validations:install create config/initializers/client_side_validations.rb create public/javascripts/rails.validations.js
A continuación modificaremos el fichero de layout, tan sólo tenemos que añadir rails.validations
a la lista de fichero JavaScript que se incluyen.
<%= javascript_include_tag :defaults, "rails.validations" %>
También debemos modificar el fichero de configuración de client_side_validations. La configuración por defecto utiliza SimpleForm y Formtastic pero como nosotros no vamos a usar ninguno de ellos tendremos que descomentar la sección del final del archivo y sobreescribir el método field_error_proc
.
# ClientSideValidations Initializer require 'client_side_validations/simple_form' if defined?(::SimpleForm) require 'client_side_validations/formtastic' if defined?(::Formtastic) # Uncomment the following block if you want each input field to have the validation messages attached. ActionView::Base.field_error_proc = Proc.new do |html_tag, instance| unless html_tag =~ /^<label/ %{<div class="field_with_errors">#{html_tag}<label for="#{instance.send(:tag_id)}" class="message">#{instance.error_message.first}</label> </div>}.html_safe else %{<div class="field_with_errors">#{html_tag}</div>}.html_safe end end
Este método adjunta un elemento label
con la clase message
conteniendo el mensaje de error a los campos que no sean válidos. Si la forma por defecto de mostrar errores no casa en nuestra aplicación podemos personalizar el fichero.
Aún nos queda un último paso por tomar, tenemos que añadir la opción :validate => true
a la llamada a form_for
del formulario al que queramos añadir las validaciones. Ya lo tendremos todo en su sitio para que las validaciones funcionen en línea.
Si recargamos la página de alta y salimos del campo del nombre de usuario sin introducir un valor aparecerá un mensaje de error inmediatamente, y lo mismo ocurrirá con el resto de campos con errores.
Validaciones más complejas
Queda claro que las validaciones sencillas funcionarán tal cual pero ¿qué pasa con las que son más complicadas? En el modelo User
tenemos algunas otras validaciones, una para el formato del campo username
que se asegura de que no haya caracteres extraños, otra que comprueba la longitud de la clave y otra que comprueba que la clave y su confirmación coinciden.
validates_presence_of :username, :email validates_presence_of :password, :on => :create validates_presence_of :username, :with => /^[-\w\._@]+$/i, :allow_blank => true, :message => "should only contain letters, numbers, or .-_@" validates_length_of :password, :minimum => 4, :allow_blank => true validates_confirmation_of :password
Todas estas validaciones client_side_validations las traduce tal cual a JavaScript por lo que si introducimos un nombre de usuario con caracteres malos o una clave demasiado corta lo veremos de inmediato.
Mientras queden errores visibles el formulario no se puede enviar, sólo podremos enviarlo una vez que hayamos introducido valores válidos en cada campo.
Hay cierto tipo de validaciones, como por ejemplo validates_uniqueness_of
tienen que leer datos de la base de datos y en nuestro caso queremos que los campos username
y email
sean únicos por lo que añadiremos esta validación y veremos lo que ocurre.
validates_uniqueness_of :username, :email
Ya hemos creado una cuenta para “eifion” y si tratamos de crear otra veremos que el error se recoge tan pronto como salimos del campo del nombre de usuario.
Esto funciona porque client_side_validations realiza una petición AJAX cuando salimos del campo username
para poder comprobar en el servidor si el nombre de usuario ya está siendo usado.
Estilos
Vamos a echar un vistazo a la CSS para personalizar el aspecto de los mensajes de error. Primero le daremos estilo a la clase field_with_errores
que añade Rails a los campos no válidos de forma que estos elementos se muestren inline
. El mensaje de error para un campo es un elemento label
con la clase message
a lo que añadiremos estilos para que parezcan mensajes de error: le pondremos color rojo y un poco de padding a la izquierda para retirarlos del elemento de formulario al que están asociados.
.field_with_errors { display: inline; } .field_with_errors .message { color: #D00; padding-left: 5px; font-size: 12px; }
Al recargar la página de registro y provocar algunos errores veremos los nuevos estilos.
Validaciones personalizadas
En el episodio 211 [verlo, leerlo] creamos una clase de validación personalizada llamada EmailFormatValidator
para validar el formato de las direcciones de correo. ¿Cómo podríamos trasladar esta clase de validación en Ruby y convertirla en JavaScript para utilizarla en el cliente como el resto de validaciones en Rails?
Por suerte en el wiki de client_side_validations hay una página que explica exactamente cómo hacer esto, así que en esta aplicación vamos a recrear el validador EmailFormatValidator
y usaremos la información de la página del wiki para hacer que funcione también en el cliente.
El primer paso es poner el validador en un archivo del directorio /lib
de la aplicación.
class EmailFormatValidator < ActiveModel::EachValidator def validate_each(object, attribute, value) unless value =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i object.errors[attribute] << (options[:message] || "is not formatted properly") end end end
La clase comprobará que el valor recibido coincide con cierta expresión regular y añade un mensaje de error a la lista de errores del modelo si dicha comparación no se cumple. En este caso bien podríamos haber usado validates_format_of
pero hemos escrito nuestro propio validador para que sirva como ejemplo.
En Rails 3 los ficheros del directorio /lib
no se incluyen automáticamente en la aplicación, por lo que tenemos que añadir este directorio a autoload_paths
en el fichero application.rb
de nuestra aplicación.
require File.expand_path('../boot', __FILE__) require 'rails/all' # If you have a Gemfile, require the gems listed there, including any gems # you've limited to :test, :development, or :production. Bundler.require(:default, Rails.env) if defined?(Bundler) module Validator class Application < Rails::Application config.autoload_paths << "#{config.root}/lib" # Configure the default encoding used in templates for Ruby 1.9. config.encoding = "utf-8" # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password] end end
El validador tiene que tener su mensaje de error por defecto definido en el fichero de locales
. El mensaje tiene que estar anidado en errors/messages
y tener una clave que coincida con el nombre del validador, en este caso email_format
.
# Sample localization file for English. Add more files in this directory for other locales. # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. en: errors: messages: email_format: "is not formatted properly."
Ya podemos añadir nuestro nuevo validador al modelo User
, utilizando validates
y pasándole el nombre del campo que deseamos validar y el nombre del validador.
validates :email, :email_format => true
En el código anterior podemos cambiar true
por un hash de opciones para, por ejemplo, especificar un mensaje de error por defecto pero en este caso lo dejaremos tal cual.
Si introducimos una dirección de correo incorrecta en el formulario anterior y lo enviamos veremos que aparece el mensaje de error de nuestro validador personalizado pero dicho error no aparece cuando salimos del campo del formulario porque la validación se produce en el servidor. Queremos traducir el código de EmailFormatValidator
a JavaScript y conectarlo con client_side_validation para que se active cuando el campo de la dirección de correo pierde el foco.
Pondremos el código de este validador en un nuevo fichero de /public/javascripts
llamado rails.validations.custom.js
, dado que el plugin espera recibir un nombre en este formato. En el archivo escribiremos el código para validar el campo en el cliente.
clientSideValidations.validators.local["email_format"] = function (element, options) { if(!/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i.test(element.val())) { return options.message; } }
En el código anterior añadimos nuestra validación creando un nuevo validador local
llamado email_format
en el objeto clientSideValidations.validators
. Haciéndolo local
quiere decir que toda la validación se ejecuta en el cliente; si lo añadiésemos a remote
tendría que hacer una llamada AJAX al servidor de la misma forma que lo hacía validates_uniqueness_of
.
El código de validador comprueba el formato del email igual que el código Ruby, utilizando una expresión regular y si la comparación falla se devuelve el mensaje de error. Para añadir el nuevo validador en JavaScript a nuestra aplicación tan sólo tenemos que incluir una referencia en el layout de nuestra aplicación.
<%= javascript_include_tag :defaults, "rails.validations", "rails.validations.custom" %>
Con todo esto en su sitio ya podemos probarlo. Si ahora recargamos la página e introducimos una dirección de correo mal formateada el mensaje de error aparecerá tan pronto como el campo pierda el foco.
Ya disponemos de validación en el lado de cliente para todos los campos del formulario, y además todas las validaciones se seguirán efectuando en el lado del servidor para aquellos usuarios que no tengan JavaScript activado en su navegador.
Con esto concluimos este episodio sobre las validaciones en el lado del cliente. Escribir validaciones por defecto parece bastante trabajo, pero tras escribir la primera las demás son bastante fáciles y con todo esto se mejora la experiencia de usuario en nuestros fomrularios.