A veces es útil poder “interceptar” el comportamiento normal de “submit” de un formulario para poder validarlo, enviarlo al servidor (a través de ajax) y luego mostrar el resultado al usuario sin recargar la pagina en el navegador. En este artículo esbozamos una manera de conseguir esto con jquery en formularios dinámicos generados en plantillas de Expression Engine 1.6.X.

Funcionalidad Deseada

Para este formulario de comentarios no quería proporcionar ningún tipo de pantalla de vista previa ya que la idea del formulario es presentar el campo principal del comentario tal y como aparecerá cuanto esté publicado. Además debido a que se moderan los comentarios (para evitar spam), y por ello no se le muestran al usuario inmediatamente, el código no trata el contenido de la respuesta del servidor. De hecho, la única respuesta que ve el usuario es si la petición ha sido procesada con éxito o no (tal y como determine jquery y nuestro código javascript).

Ficheros de javascript necesarios

Para utilizar el código de jQuery y jQuery validation plugin tienes que definir los scripts con algo parecido a lo siguiente (Cabe observar que aquí estamos usando un par de CDNs pero sería igual de fácil enlazar con ficheros de un servidor local si fuera necesario):

<script type="text/javascript" 
  src="http://code.jquery.com/jquery-1.4.2.min.js">
</script>
<script type="text/javascript" 
  src="http://ajax.microsoft.com/ajax/jquery.validate/1.7/jquery.validate.min.js">
</script>

Código de la plantilla de Expression Engine 1.6.X

Este es el código que usamos para generar el formulario de comentarios. Por brevedad solamente incluimos abajo la parte relevante de la plantilla. Se ha quitado el HTML que lo envuelve, los tags del multiidioma y otros tags superfluos de EE.

Puntos de interés en el extracto de la plantilla:

  • Los tags de Expression Engine aparecen sin estar envueltos en sus {} (porque parece que estos causan un problema al mostrar el código)
  • El extracto de abajo está contenido dentro de un tag de exp:weblog:entries, lo cual nos permite acceder al valor booleano de allow_comments (linea 1) para este weblog entry en concreto.
  • El formulario de comentarios se crea con el tag de exp:comment:form (linea 4) usando la información contenida en la URL para identificar el weblog entry actual.
  • Todos los elementos de label contienen un atributo de for con su valor igual al atributo name del campo de input correspondiente. Esto significa que cuando pinches en la etiqueta, label, el foco va al campo de tipo input referenciado.
  • El javascript usa tres elementos p ocultos e identificados por su atributo id para mostrar el mensaje apropiado al usuario.
  • Todas las cadenas puestas a fuego en elementos de header, label, button y en los mensajes de éxito/error se podrían cambiar fácilmente a tags dinámicos para permitir que la pagina se generase en función del idioma de cada usuario y del contenido de cada weblog entry.
  • Las clases CSS ( required, email and url) que se aplican en el atributo de class de los campos de input las usa el plugin de validación de jquery.

El extracto de la plantilla:

if allow_comments
<div id="add-comment" class="group">
  <h3>Add a comment</h3>
  exp:comment:form
    <div id="add-comment-fieldsets" class="group">
      <div class="left">
        <fieldset>
          <label for="name">Name:<em>*</em></label>
          <input type="text" name="name" 
            value="" id="name" size="37" class="required"/>
        </fieldset>
        <fieldset>
          <label for="email">Email:<em>*</em></label>
          <input type="text" name="email" 
            value="" id="email" size="37" class="required email"/>
        </fieldset>
        <fieldset>
          <label for="url">URL:</label>
          <input type="text" name="url" 
            value="" id="url" size="50" class="url"/>
        </fieldset>
      </div> <!-- /.left -->
      <div class="right">
        <fieldset>
          <label for="comment">Comment:<em>*</em></label>
          <textarea name="comment" id="comment" 
            rows="10" cols="35" class="required"></textarea>
        </fieldset>
      </div> <!-- /.right -->
    </div> <!-- /#add-comment-fieldsets -->
    <p id="flash-message-ok" 
      style="display:none">Add comment ok</p>
    <p id="flash-message-oops" 
      style="display:none">Add comment oops</p>
    <p id="flash-message-timeout" 
      style="display:none">Add comment timeout</p>
    <input type="submit" name="send" value="Send comment"/>
  /exp:comment:form
</div> <!-- /#add-comment -->
/if

Como comentario general sobre formularios, es una buena idea asegurar que el atributo name del botón submit que cualquier formulario que crees no sea submit. Esta es una buena práctica a seguir porque si usas javascript (ya sea tu propio código o una librería de terceros) para realizar procesamiento pre-submit del formulario en la parte del cliente es muy posible que al final de este procesamiento quieras enviar el formulario.

Para poder hacer esto se suele llamar a algo como form.submit(), pero si el formulario contiene también un elemento cuyos atributo name es submit javascript puede confundir el método del formulario submit() con su elemento con el nombre de submit. El resultado de todo esto puede ser que los dos acaben recibiendo una llamada y el formulario se enviaría al servidor dos veces. La mejor manera de evitar esto es usar siempre para el atributo name del botón de envío de formulario cualquier nombre que no sea submit.

Javascript con jQuery

La función fadeInFadeOut muestra el elemento jQueryElement pasado como el primer parámetro y a continuación usa el método de setTimeout para esconderlo al final del timeout del segundo parámetro.

La validación del comment_form se asocia al elemento #comment_form, identificado por su id ya que en una página dada sólo habrá un único comment_form. Observa que el guion bajo en el id es HTML que genera Expression Engine mientras que el resto del HTML usa un guion para separar las palabras en los identificadores y los nombres de las clases.

Esta validación usa los atributos de class de cada elemento de input (tal y como se dice arriba) para validar su contenido.

Como parte de la llamada a validate definimos una función submitHandler que nos permita recibir como un parámetro el formulario que se esté validando de forma que se pueda controlar su envío.

Dentro de este manejador usaremos la función ajax de jQuery para enviar el formulario. Esta función ajax recibe como parámetro un Hash de pares clave y valor (en formato json) que se usan para configurar la petición que se envía y cómo se maneja la respuesta.

Los parámetros son:

  • La url se extrae del atributo action del formulario bajo validación.
  • El type de petición que el navegador usa para enviar la petición es POST
  • La clave de data contiene la información del formulario que se quiere enviar en esta petición.
  • La clave de success contiene una función de tipo callback - más sobre esta función un poco más abajo.
  • La clave de error también contiene un callback que se usa para mostrar el mensaje de Oops al usuario.

El código javascript es:

$(function(){
  // Show an element and then fade it out with the timeout given
  var fadeInFadeOut = function(jQueryElement, timeout) {
    jQueryElement.fadeIn(300);
    setTimeout(function() { jQueryElement.fadeOut("slow"); }, timeout);
  }; 

  // Validate the comment / message forms
  $("#comment_form").validate({
    submitHandler: function(form) {
      $.ajax({
        url: $(form).attr("action"),
        type: "POST",
        data: $(form).serialize(),
        success: function(response) {
          if(response
            .match(/you are only allowed to post comments every 90 seconds/i)) {
              fadeInFadeOut($("#flash-message-timeout"), 2500);
          } else if (response.match(/&lt;title&gt;error/i)) {
            fadeInFadeOut($("#flash-message-oops"), 2000);
          } else {
            //Add a success message     
            //Show it and then configure a timeout to delete it
            fadeInFadeOut($("#flash-message-ok"), 2000);
            //Delete the input fields only if the message has been sent
            $(":input[type^='text']", form).attr('value', '');
            //Replace the EE XID with a new one
            $.get($(form).attr("action"), function(responseXID) {
              $("#comment_form :input[name='XID']").val($(responseXID)
                .find("#comment_form :input[name='XID']").val());
            });
          }
        },
        error: function() {
          //Add an oops message
          //Show it and then configure a timeout to delete it
          fadeInFadeOut($("#flash-message-oops"), 2000);
        }
      });
    }
  });
});

En el ejemplo de arriba, para obtener el valor de la clave data en el Hash de parámetros que se pasa a la función de ajax usamos la función serialize de jQuery (que se encuentra en el objeto que jquery usa para representar un conjunto de elementos de un formulario) para serializar todos los datos contenidos dentro del formulario que se está validando. Esto nos permite enviar no solamente los valores añadidos por el usuario en los campos de texto pero también cualquier campo oculto que pudiera estar presente ahora mismo o en el futuro.

También incluiríamos aquí dentro del parámetro indexado por la clave data el código necesario si quisieramos añadir algo parecido a {"method": "DELETE"} o {"method": "PUT"} (el formato exacto depende del framework que se esté usando) para indicar el tipo de la petición cuando enviamos a un API que está implementada de forma restful.

En la siguiente captura de pantalla se pueden ver tanto los elementos escondidos (hidden) que se añade Expression Engine en el elemento div class="hiddenFields" como los p de los mensajes con sus ids de #flash-message-ok y #flash-message-oops.

Para entender la lógica en la función que se usa como el valor de la clave success es necesario entender algunas de las opciones de seguridad que proporciona Expression Engine y cómo están relacionadas con los códigos de estado HTTP que se usan para devolver información cuando se ha enviado un formulario y cómo jQuery identifica si una petición ha tenido éxito.

Cuando la respuesta del servidor contiene un código HTTP 200 OK o 302 Found jQuery llama a la función asociado con la clave success (se puede modificar este comportamiento de jQuery pero aquí no nos hace falta hacerlo). Dado que EE devuelve una pagina de error con un estado 200 OK cuando algo no haya permitido que EE procesase el comentario, la función de la clave success también se llama en estos casos. Esto significa que para poder presentar un mensaje preciso al usuario tendremos que mirar en el contenido de la respuesta para identificar lo que ha sucedido y para ello usaremos expresiones regulares de Javascript.

La opción "Comment resubmission time interval" que proporciona EE bajo Admin > Section Administration > Section Management > Edit Section Preferences dentro de la sección de "Comment posting preferences" permite un administrador a definir un intervalo con el tiempo que debe transcurrir entre el envío de dos comentarios desde el mismo dirección IP (esto es solo una de las opciones de "throttling" que proporciona EE). Cuando no haya transcurrido este intervalo, EE devuelve un código de estado HTTP 200 OK y una pagina de error que contiene el texto "you are only allowed to post comments every $VALUE seconds" (donde $VALUE es el tiempo que está definido como intervalo de "Comment resubmission time interval").

Por consiguiente, la primera condición if contiene una comprobación de esta cadena y si está presente mostraremos el mensaje contenido en el elemento oculto #flash-message-timeout. La segunda condición if comprueba el elemento title para capturar cualquier página genérica de error y cuando sea así mostrará el mensaje de error genérico que se encuentra en el elemento #flash-message-oops. Esta segunda condición resulta útil si tienes habilitada la opción de "Deny Duplicate Data" que se encuentra en la sección Admin > System Preferences > Security and Session Preferences.

La sección de Admin > System Preferences > Security and Session Preferences también contiene la opción "Process form data in Secure Mode" que, cuando esté activada, rellena un campo llamado XID en el div .hiddenFields que EE añade al formulario de comentarios. El valor de este XID tiene que ser único para cada petición que se envía a EE y dado que queremos permitir el envío de varios comentarios sin la necesidad de tener que recargar la pagina para obtener un XID nuevo, usaremos la función get de jQuery para obtener un valor de XID nuevo y reemplazar el previo en el formulario de comentarios (son las tres lineas que van después del comentario

//Replace the EE XID with a new one

en el código anterior).

Cuando un usuario que está registrado en el sistema crea un comentario Expression Engine devuelve un código de estado HTTP 302 Found y una versión actualizada de la pagina actual, que contiene un formulario con un XID nuevo. Ahora mismo esto ocurre tan poco (ya que sólo los administradores pueden registrarse en el sistema) que no se ha hecho ninún intento de uso de la información contenida en esta respuesta en el código Javascript. Sin embargo, esto sería algo a tener en consideración si en un futuro se fueran a añadir las cuentas de usuario ya que entonces valdría la pena usar parte del contenido de esta respuesta para ahorrarnos una llamada a la función get de jQuery.

Aplicaciones de esta técnica

Esta técnica permite al programador enriquecer un formulario que ya funciona aplicando javascript de forma no intrusiva. (i.e. el código de javascript no se mezcla con el código HTML). El hecho de usar javascript de esta forma con un formulario que ya funcionase significa que tendremos un formulario que funcionará correctamente en navegadores que no tengan javascript habilitado y que proporcionará una experiencia de usuario mejorada (¡esperemos!) en equipos con javascript habilitado. Esta técnica puede adaptarse para añadir funcionalidad a cualquier formulario. Por ejemplo, se podía cambiar fácilmente la función de callback de success para utilizar la respuesta del servidor para añadir, actualizar o reemplazar elementos visibles en el DOM.

Almacenado en: lenguajes, web

comentarios

por nice

11 Octubre, 2010 a las 0:46

nice post