sábado, 15 de octubre de 2011

Datepicker and timepicker more friendly

Si tu usuarios se quejan de tener estás cajas:
 Y te reclaman un sistema más "moderno" esto te puede interesar.

Problemas identificado de estás cajas:
  1. No hay garantía que la fecha se haya introducido correctamente. Y si no se introduce correctamente (ejemplo 30/02/2011) la base de datos la guardará como nulo (por defecto sin avisar).
  2. Es lento (desplegar y seleccionar requiere de 5 a 10 segundos, hacer la prueba con vuestros usuarios) 
  3. No tenemos forma de orientarnos,  la fecha seleccionada es hoy?... el martes que viene? ...
  4. Estamos informando un solo campo (fecha o fecha con hora) pero tenemos de 3 a 6 desplegables, con lo que  inconscientemente le estamos dando un peso (o importancia) mayor del que debería.
  5. Las 3-6 cajitas con sus separadores, sus flechas desplegables y su contenido ocupan un espacio que podríamos simplificar o ocupar con otro contenido.
Solución propuesta:
  1. Convertimos los campos de "form_field_on" o "form_field_at" a "text_field".
  2. Añadimos un selector que nos facilite la vida.
Resultado (desplegado):


Como lo conseguimos:
Si vas a trabajar con formatos de fecha no americanos, es posible que antes de empezar te interese adaptarlo (http://railsdynamics.blogspot.com/2011/09/cambiando-el-formato-de-fecha.html).
Para los objectos date (de aquí en adelante "_on") tenemos el DatePicker de JQueryUI http://jqueryui.com/demos/datepicker/
Para los objetos time (de aquí en adelante "_at") tenemos  el addon TimePikcer http://trentrichardson.com/examples/timepicker/ o https://github.com/trentrichardson/jQuery-Timepicker-Addon

Pasos:
  1. Colocamos los javascript
    app/assets/javascripts/jquery-ui-1.8.16.custom.min.js
    app/assets/javascripts/jquery-ui-timepicker-addon.js
  2. Si queremos internacionalizar también añadimos los javascript de I18N
    app/assets/javascripts/jquery-ui-i18n.js
    app/assets/javascripts/jquery-ui-timepicker-addon.js
  3. Añadimos el CSS para que se vea correctament
    app/assets/stylesheets/jquery-ui-timepicker-addon.css
  4. Extendemos la clase form helper para añadir dos nuevos métodos: form_field_at y form_field_on
    app/helpers/form_helper.rb
    module FormHelper
      def self.included(base)
        ActionView::Helpers::FormBuilder.instance_eval do
          include FormBuilderMethods
        end
      end

      module FormBuilderMethods  
        def form_field_at(method, default_value = nil)
          script = '<script>'
          script << ' $(function(){$( "#' + @object_name.to_s + '_' + method.to_s + '" ).datetimepicker({ showOn: \'button\' });});'
          script << '</script>' + "\n"
              
          value = @object.send(method).to_s
          value = default_value.to_s if value.blank?
        
          form_field_at = @template.text_field @object_name, method,
                :value => value , :size => 16, :ondblclick => 'this.value="' + Time.now.to_s + '"'
        
          script.to_s.html_safe + '  ' + form_field_at.to_s.html_safe
        end

        def form_field_on(method, default_value = nil)
          script = '<script>'
          script << ' $(function(){$( "#' + @object_name.to_s + '_' + method.to_s + '" ).datepicker({ showOn: \'button\' });});'
          script << '</script>' + "\n"
              
          value = @object.send(method).to_s
          value = default_value.to_s if value.blank?
        
          form_field_on = @template.text_field @object_name, method,
                :value => value , :size => 10, :ondblclick => 'this.value="' + Time.now.to_date.to_s + '"'
        
          script.to_s.html_safe + '  ' + form_field_on.to_s.html_safe
        end  

      end
     
    end
  5. Cambiamos en la vista las cajas de texto de text_field a form_field_at y form_field_on
    app/views/events/_form.html.erb
      <div class="field">
        <%= f.label :start_at %><br />
        <%= f.form_field_at :start_at %>
      </div>
      <div class="field">
        <%= f.label :finish_on %><br />
        <%= f.form_field_on :finish_on %>
      </div>
Con esto tendremos los date y time picker funcionando, ahora si queremos añadir validación también tenemos que:
  1. Añadir un helper para la fecha
    app/helpers/validation_helper.rb
    module ValidationHelper
      def conversion_to_date_at(method,field_at_before_type_cast)
        errors.add(method, ' (' + field_at_before_type_cast + ') formato incorrecto ' + DATE_AT_FORMAT_EXPLANATION + ' o valor fuera de rango') if is_invalid_date_at(field_at_before_type_cast)
      end

      def is_invalid_date_at(field)    
        if field == nil or field == ''
          false   #Null is not an invalid date. Valid date!
        else
          begin
            if defined?(DATE_AT_FORMAT)
              format_to_validate = DATE_AT_FORMAT
            else
              format_to_validate = I18n.t("time.formats.default")
            end
            DateTime.strptime(field, format_to_validate)              
            false  #Converstion without problems! Valid date!
          rescue
            true  #Converstion with problems! Invalid date!
          end
        end
      
      end

      def conversion_to_date_on(method,field_on_before_type_cast)
        errors.add(method, ' (' + field_on_before_type_cast + ') formato incorrecto ' + DATE_ON_FORMAT_EXPLANATION + ' o valor fuera de rango') if is_invalid_date_on(field_on_before_type_cast)
      end

      def is_invalid_date_on(field)    
        if field == nil or field == ''
          false   #Null is not an invalid date. Valid date!
        else
          begin
            if defined?(DATE_ON_FORMAT)
              format_to_validate = DATE_ON_FORMAT
            else
              format_to_validate = I18n.t("date.formats.default")
            end      
            DateTime.strptime(field, format_to_validate)              
            false  #Converstion without problems! Valid date!
          rescue
            true  #Converstion with problems! Invalid date!
          end
        end
      end  
    end
  2. Añadir la validación en el modelo
    app/models/event.rb
    class Event < ActiveRecord::Base
      include ValidationHelper
      require 'date'
      validate {conversion_to_date_at(:start_at, start_at_before_type_cast)}
      validate {conversion_to_date_on(:finish_on, finish_on_before_type_cast)}
    end



No hay comentarios:

Publicar un comentario