Ejemplo 2 de Ajax con Prototype: interfaz de usuario

Introducción.

Hace un año largo que publiqué el primer ejemplo de Ajax utilizando Prototype: partes 1, 2, 3, 4 y demostración.  En esa época estaba apenas empezando a aprender acerca de los frameworks de JavaScript y había utilizado principalmente MooTools. Me gusta mucho probar diferentes métodos de hacer las cosas porque encuentro gratificante aprender cosas nuevas.  Ahora, un año después, he desarrollado dos proyectos con MooTools y estoy terminando exitosamente otro con Prototype y he aprendido bastante de estos frameworks como para mejorar sustancialmente este mini-tutorial.

Diseño.

La idea de esta nueva versión es la de implementar una aplicación similar que permita la administración de contactos.  Un contacto relaciona la siguiente información de una persona.

  • Nombres.
  • Apellidos.
  • Dirección de correo electrónico.
  • Fecha de nacimiento.

Las acciones que se podrán realizar sobre los contactos son las siguientes.

  1. Crear a un nuevo contacto.
  2. Editar a un contacto existente.
  3. Remover a un contacto existente.
  4. Listar los contactos registrados permitiendo filtrarlos por campo (nombres o apellidos) y por letra inicial del campo elegido.

Implementación.

Sin ser visualmente atractiva la interfaz de usuario se diseña para que sea funcional y que cumpla con las características del sistema propuesto. El siguiente es el esquema de lo que se desea implementar con la interfaz del usuario de la aplicación AJAX.

Esquema del UI
Esquema del UI

El diseño cuenta con las siguientes secciones.

  1. Cabecera con título, subtítulo y mensaje de estado.
  2. Pie de página.
  3. Formulario para la edición de los datos del contacto.
  4. Botones para elegir la acción a realizarse.
  5. Filtro de listado.
  6. Listado de contactos registrados.

Ese capítulo no requiere de demasiada información o esfuerzo con respecto al tema principal del tutorial, es simplemente un documento XHTML para el contenido con clases CSS para su presentación.  A continuación se referencian algunos apartes de su implementación que serán útiles tener en cuenta para los capítulos siguientes.

Referencia a el archivo con la hoja de estilos.

Los estilos se encuentran especificados en un archivo diferente ubicado en css/style.css, su referencia desde el archivo demo.php que contiene el código XHTML se realiza en la cabecera (head) de la siguiente manera.

<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>
<html xmlns='http://www.w3.org/1999/xhtml'>
<head>
    <!-- Meta Data -->
    <meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />
    <meta name='author' content='Jorge Iván Meza Martínez <jimezam@gmail.com>' />
    <meta name='description' content='Demostración del uso de Ajax con Prototype y PHP' />
    <!-- Site Title -->
    <title>Demostración de Ajax con Prototype y PHP</title>
    <!-- Link to standard Style Sheets -->
    <link href='css/style.css' type='text/css' rel='stylesheet' />
</head>
<body>
[...]
</body>
</html>

Debe tenerse en cuenta que para hechos prácticos la cadena […] reemplaza a una sección específica de código y no debe interpretarse de manera literal.

El mensaje de estado.

El mensaje de estado se utiliza para informarle al usuario que la aplicación web se encuentra realizando algún tipo de procesamiento asíncrono o para informarle el resultado del mismo.  Para esta aplicación se reservará un espacio para los mensajes en la parte inferior de la cabecera de la siguiente forma.

    <div id='mensaje'>
        &nbsp;
    </div>

El formulario de datos.

        <form id='formulario' action='#' method='post'>
            <input type='hidden' id='tId' name='tId' value='' />

            <fieldset>
                <legend class='SeccionTitulo'>Información del contacto</legend>

                <table>
                    <tr>
                        <td class='CampoTitulo'>Nombres</td>
                        <td><input class='CampoEstilo' type='text' id='tNombres' name='tNombres' size='30' maxlength='30' title='Nombres' /></td>
                    </tr>
                    <tr>
                        <td class='CampoTitulo'>Apellidos</td>
                        <td><input class='CampoEstilo' type='text' id='tApellidos' name='tApellidos' size='30' maxlength='30' title='Apellidos' /></td>
                    </tr>
                    <tr>
                        <td class='CampoTitulo'>Correo electrónico</td>
                        <td><input class='CampoEstilo' type='text' id='tCorreo' name='tCorreo' size='30' maxlength='128' title='Correo Electrónico' /></td>
                    </tr>
                    <tr>
                        <td class='CampoTitulo'>Fecha de nacimiento</td>
                        <td><input class='CampoEstilo' type='text' id='tFNacimiento' name='tFNacimiento' size='10' maxlength='10' title='Fecha de Nacimiento' /></td>
                    </tr>
                    <tr>
                        <td class='SeccionBoton' colspan='2'>
                            [...]
                        </td>
                    </tr>
                </table>
            </fieldset>
        </form>

En el formulario deben notarse varios aspectos interesantes para el trabajo con AJAX.

  • La etiqueta <form> no requiere de un action específico, por ello le indicamos la mísma página (#).
  • El identificador del formulario de los datos es formulario.  Este valor se utilizará frecuentemente desde el código JavaScript.
  • El campo para el código del contacto (tId) es oculto ya que es para el manejo interno únicamente.
  • Muchas funciones de Prototype como es el caso de Form.serialize se basan en el atributo name de la etiqueta en lugar del id habitualmente utilizado, por esto es altamente aconsejable definir los dos tal y como se realizó con los input [type=’text’] anteriormente.

Los botones de acción.

Estos botones van en la sección td.SeccionBoton justo donde se dejó una marca […] en el código anterior y se especifican de la siguiente manera.

<input class='Boton' id='bListar'  type='button' value='Listar' />
<input class='Boton' id='bNuevo'   type='button' value='Nuevo' />
<input class='Boton' id='bAccion'  type='button' value='Agregar' />
<input class='Boton' id='bRemover' type='button' value='Remover' />
  • Los botones son button y no submit porque su misión es la de iniciar a una función JavaScript, no la de enviar al formulario como normalmente se hace durante un postback.
  • La definición de los botones no incluye información del manejo de sus eventos (onClick) ya que por la separación de funciones: XHTML -> contenido, CSS -> presentación y JS -> funcionalidad; esta se definirá en el correspondiente archivo de funciones de JavaScript.
  • El botón bAccion servirá para crear registros o para editarlos de acuerdo con la selección del usuario.

El filtro de búsqueda.

Permitirá filtrar al listado de contactos registrados que se visualizarán en la sección de resultados de la búsqueda (6).  Se especifica de la siguiente manera. El primer componente de selección permite elegir la letra con la cual se filtrará el contenido o Todos si no se desea realizar un filtro.

                        <select id='sLetra' name='sLetra'>
                            <option value='todos'>Todos</option>

                        <?php

                            for($i=65; $i<=90; $i++)
                            {
                                $c = chr ($i);

                                echo "<option value='{$c}'>{$c}</option>n";
                            }

                        ?>

                        </select>

El segundo componente de selección permite especificar el campo sobre el cual se realizará el filtro, ya sea el de nombres o el de apellidos.

                        <select id='sCampo' name='sCampo'>
                            <option value='nom'>Nombres</option>
                            <option value='ape'>Apellidos</option>
                        </select>

De manera similar a lo sucedido con los botones de acción, para estos select no se define en este archivo ningún tipo de manejo de eventos.

El listado de contactos registrados.

En esta sección se mostrará el listado de contactos registrados filtrados según los criterios seleccionados en la sección anterior. Por ahora se encuentra vacío.

            <div id='Resultados'>
                &nbsp;
            </div>

Conclusiones.

En este primer capítulo se definió el proyecto a implementarse y se realizó la especificación de la interfaz de usuario sobre la cual se construirá la aplicación asíncrona. Este subproducto se encuentra separado en dos partes: la especificación del contenido (demo.php) y la especificación de su presentación (css/style.css).

En los siguientes capítulos de este tutorial se irá complementando paso a paso la aplicación de demostración, añadiéndosele diferentes funcionalidades hasta alcanzar el objetivo final aquí trazado.

Enlaces.

Bug de IE – radiobutton olvidadizos al ser movidos entre el DOM

Esta semana tuve que invertir (perder) mi tiempo intentando entender y solucionar un comportamiento extraño de IE que resultó ser un bug que aparentemente existe en todas las versiones de este navegador y que fue solucionado según dicen en la versión 8 beta2.

A grandes razgos el problema sucede cuando se tiene una lista de radiobuttons (no sucede con otros componentes incluyendo el text o los checkboxes) y esta es trasladada entre el árbol DOM de la página, como por ejemplo cuando se mueve el formulario de un div a otro.  Cuando esto sucede, a IE se le olvida mágicamente cual era el radio seleccionado.

Para no volver a perder el tiempo con este error de IE hice una pequeña implementación de un formulario que reproduce las condiciones del error y le indica al usuario si el navegador utilizado padece o no del bug.

Inicialmente se tiene un formulario dentro del div seccion1 y otro div seccion2 vacío.

<div id='seccion1'>
    <form id='formulario' action='#' onSubmit='return procesarEnvio()'>

        <input type='radio' id='uno'    name='grupo' value='1' />
        <label for="uno">Primera opción.</label><br />
        <input type='radio' id='dos'    name='grupo' value='2' />
        <label for="dos">Segunda opción.</label><br />
        <input type='radio' id='tres'   name='grupo' value='3' />
        <label for="tres">Tercera opción.</label><br />
        <input type='radio' id='cuatro' name='grupo' value='4' />
        <label for="cuatro">Cuarta opción.</label><br />
        <input type='radio' id='cinco'  name='grupo' value='5' />
        <label for="cinco">Quinta opción.</label><br />

        <br />

        <input type='submit' value='Enviar' />

    </form>
</div>

<div id='seccion2'>
</div>

Cuando el usuario envía el formulario se obtiene el radio seleccionado.

        var seleccion1 = obtSeleccionValor('seccion1', 'formulario', 'grupo');

        alert("[1] El 'radiobutton' seleccionado es: " + seleccion1);

No hay problemas.  Para reproducir las condiciones del bug, se mueve, para este ejemplo con la ayuda de Prototype, al formulario de la seccion1 a la seccion2.

        var formulario = $$('#seccion1 #formulario').first();

        $('seccion2').insert(formulario);

Se vuelve a consultar cual es el radio seleccionado.

        var seleccion2 = obtSeleccionValor('seccion2', 'formulario', 'grupo');

        alert("[2] El 'radiobutton' seleccionado es: " + seleccion2);

Si seleccion1 == seleccion2 significa que se está utilizando un buen navegador, de lo contrario significa que se está utilizando un navegador ampliamente conocido por desconocer estándares y ser la pesadilla de los desarrolladores.

Desafortunadamente no encontré una solución elegante para este problema diferente de almacenar temporalmente cual es el radio seleccionado antes de realizar su movimiento en el DOM y actualizar su valor posteriormente.

Esto es práctico de hacer con Prototype.

        var formulario = $$('#' + secc + ' #' + form).first();

        var seleccion = formulario.getInputs('radio', grp).find(
            function(re)
            {
                return re.checked;
            });

Esta sección de código obtiene los radios del formulario form que se encuentran contenidos en el div secc, que pertenecen al grupo grp y que se encuentran seleccionados.  seleccion será undefined si ninguno de los radios se encuentra actualmente seleccionado.

        var seleccion = formulario.getInputs('radio', grp);

        if(seleccion[sel] != undefined)
              seleccion[sel].checked = true;

Esta sección corrige el valor seleccionado (sel) entre los radios (seleccion).  Dependiendo de la información contenida en los value y referenciada por sel, podrá ser una mejor opción recorrer la seleccion y realizar las comparaciones necesarias para activar (checked) al radio adecuado.

Utilice la aplicación demostración para verificar la presencia del bug en su navegador y para revisar con mayor precisión su código fuente.

Enlace: BugIERadioMovDom 0.1.

Autocompletador con Prototype/Scriptaculous – Parte II

Como complemento a la primera parte, en este artículo se ampliará el concepto del Autocompleter de Prototype/Scriptaculous a su versión mas flexible que hace uso del envío de mensajes asíncronos mejor conocidos como AJAX.

Para su implementación se requieren de dos partes las cuales se analizarán en el orden expuesto a continuación.

  • Un formulario HTML con el campo que incluye la autocompletación.
  • Una aplicación PHP que recibe y procesa la solicitud de información y genera los resultados de manera dinámica.

El formulario es igual al anterior, incluye el text donde el usuario escribirá el criterio que se autocompletará cuyo identificador es t_criterio y un espacio, cuyo identificador es resultado, donde se desplegarán las posibles sugerencias arrojadas por la aplicación PHP basada en el criterio antes especificado.

<div id='contenedor'>
    <div id='criterio'>
        Criterio
        <input type='text' id='t_criterio' size='50' maxlength='200' />
    </div>
    <div id='resultado'>
        No hay resultados que mostrar.
    </div>
</div>
Ejemplo de Ajax.Autocompleter
Ejemplo de Ajax.Autocompleter

Para activar el autocompletado del campo t_criterio se ejecuta el método estático Ajax.Autocompleter tan pronto como el árbol DOM de la página ha sido cargado de la siguiente manera.

new Ajax.Autocompleter('t_criterio',
                       'resultado',
                       'fuente.php',
                       {
                           paramName: 'criterio',
                           method: 'post',
                           parameters: 'estatico1=valor1&estatico2=valor2',
                           callback: function(editor, paramText) {
                               return paramText + '&time=' + new Date().getTime();
                           },
                           select: 'm_nombre',
                           afterUpdateElement: function (inputField, suggestion) {
                               inputField.value = suggestion.down('span.m_nombre').innerHTML + " / " + suggestion.down('span.m_departamento').innerHTML;
                           }
                       });

Los parámetros que recibe el método se describen a continuación, de los cuales sólo los tres primeros son obligatorios.

  • Id del text con el criterio de búsqueda para la autocompletación.
  • Id del área donde se van a mostrar las opciones sugeridas.
  • URL donde se ubica a la aplicación dinámica que va a procesar la búsqueda de sugerencias a través de AJAX.
  • Hash con opciones adicionales.
    • paramName: nombre del parámetro que se enviará a la aplicación dinámica con el contenido del texto especificado por el usuario.  Si no se especifica este parámetro, se utilizará el atributo name del campo de texto del formulario.
    • method: método a través del cual se enviará la información a la aplicación web: post o get.
    • parameters: parámetros estáticos para ser enviados adicionalmente.
    • callback: función ejecutada antes de ser invocada la aplicación PHP y que permite manipular de manera dinámica los parámetros enviados.
    • select: campo de la sugerencia elegida que es mostrado en el campo de texto cuando la sugerencia es seleccionada.  El valor de este campo se relaciona con el selector CSS donde se encuentra ubicada la información.
    • afterUpdateElement: función invocada justo después de que el usuario ha seleccionado a una de las opciones presentadas.  Permite componer de manera dinámica un valor para ser mostrado como la selección del usuario.  Es mas elaborado y sobreescribe lo especificado en el parámetro select.

Adicionalmente las opciones adicionales minChars y frequency se encuentran también disponibles y operan de igual manera que en el autocompletador local.

La aplicación dinámica (fuente.php) por su parte, desarrollada en PHP para este ejemplo, realiza las siguientes acciones.

Obtiene el valor del criterio de búsqueda especificado por el usuario.  La forma de recuperarlo depende del método (method) utilizado por el formulario.

$criterio = $_POST['criterio'];

Se realiza una consulta a la base de datos y/o un procesamiento de la información lo que constituye el carácter dinámico de la aplicación.  Para este ejemplo, se consulta la base de datos (SQLite utilizando PDO) de la división geopolítica de Colombia y se buscan los municipios cuyos nombres contengan a la subcadena especificada.

$link = new PDO('sqlite:data/database.sqlite');

$sql = "SELECT m.nombre AS nombre, d.nombre AS departamento,
               m.latitud AS latitud, m.longitud AS longitud
        FROM municipios m, departamentos d
        WHERE m.id_departamento = d.id_departamento AND
              m.nombre LIKE '%{$criterio}%'";

$result = $link -> query($sql);

El resultado enviado al formulario está constituído por la salida estándar (echo/print/etc.) de la aplicación PHP.  Su estructura es la de una lista no ordenada: <ul><li>…</li>…</ul>.

Para finalizar, se construye la lista no ordenada con la información obtenida de la base de datos.

$str = "<ul>n";

foreach ($result as $row)
{
    $str .= "<li>n";
    $str .= "<div class='titulo'>Título: <span class='m_nombre'>" . utf8_decode($row['nombre']) . "</span></div>";
    $str .= "<div class='titulo'>Departamento: <span class='m_departamento'>" . utf8_decode($row['departamento']) . "</span></div>";
    $str .= "<div class='titulo'>Latitud: <span class='m_latitud'>" . utf8_decode($row['latitud']) . "</span></div>";
    $str .= "<div class='titulo'>Longitud: <span class='m_longitud'>" . utf8_decode($row['longitud']) . "</span></div>";
    $str .= "</li>n";
}

$str .= "</ul>n";

echo utf8_encode($str);

La información almacenada e impresa en la variable $str es la que será mostrada en el área de sugerencias: #resultado.

Enlaces:

Autocompletador con Prototype/Scriptaculous – Parte I

Prototype, al igual que otros frameworks de JavaScript, permite implementar fácilmente comportamientos que que serían dispendiosos de crear a partir del JavaScript básico y convencional.  El hermanito Scriptaculous viene a ayudarnos aún mas con sus efectos y ayudantes.

Un ejemplo de estas posibilidades de rápida implementación es la simulación del autocompletar de un campo de texto.

El objeto Autocompleter acepta dos fuentes de datos diferentes.

  1. Una local como un arreglo de JavaScript.
  2. Una remota y mas dinámica a través de solicitudes AJAX.

En este artículo se va a realizar la demostración del uso de un Autocompleter.local.  Para esto se requieren dos componentes.

  • Un campo de texto donde el usuario escribe el criterio de búsqueda.
  • Una sección de sugerencias donde el Autocompleter despliega las coincidencias basadas en el criterio de búsqueda.
Ejemplo de Autocompleter.local
Ejemplo de Autocompleter.local

Para el ejemplo anterior cuenta con los siguientes elementos.

  • t_criterio: campo de texto para el criterio de búsqueda.
  • resultado: es el div donde se  desplegarán las sugerencias encontradas.
  • departamentos: es un arreglo JavaScript con la información local y estática para las opciones de búsqueda, en este caso el listado de los departamentos de Colombia.

Para activar el autocompletado sobre t_criterio basado en los valores de departamentos, se ejecuta la siguiente instrucción JavaScript.

var ac = new Autocompleter.Local('t_criterio',
                 'resultado',
                 departamentos,
                 {
                     autoSelect     : false,
                     frequency      : 0.4,
                     minChars       : 1,
                     choices        : 10,
                     paritialSearch : false,
                     paritialChars  : 2,
                     fullSearch     : true,
                     ignoreCase     : true
                 });

Los parámetros especificados al método Local son descritos a continuación.

  1. Id del campo de texto con el criterio de búsqueda.
  2. Id del contenedor de los resultados obtenidos.
  3. Arreglo con la información disponible.
  4. Hash con las opciones de configuración del autocompletador.  Todos estos valores son opcionales y los mostrados corresponden con los valores por defecto.

A continuación se describen las opciones de configuración disponibles.

  • autoSelect: Acepta automáticamente la primera sugerencia cuando sólo hay una.
  • frecuency: Tiempo en segundos entre dos intentos de autocompletar.
  • minChars: Cantidad mínima de carácteres necesarios para que se active el autocompletar.
  • choices: Cantidad máxima de opciones sugeridas a mostrarse.
  • paritialSearch: Tomar en cuenta para la búsqueda el comienzo de cada palabra o sólo el comienzo del texto.
  • paritialChars: Cantidad mínima de carácteres necesarios para que se active la búsqueda parcial.
  • fullSearch: Tomar en cuenta cualquier subcadena de las opciones o sólo los comienzos (palabra o línea).
  • ignoreCase: Tomar o no en cuenta la diferencia entre mayúsculas/minúsculas.

Enlaces:

Editor de operaciones con Prototype

Uno de los proyectos que estoy desarrollando para la Fundación consiste en parte en la definición de fórmulas que están formadas a su vez por una o mas operaciones.

Lo primero que se me vino a la mente fue representarlas con un documento XML que simulara al árbol de precedencia de expresiones de la operción con etiquetas anidadas.  El primer problema que apareció cuando lo discutimos es como hacer el usuario final, no ingeniero de sistemas, pudiera posteriormente y con éxito, implementar sus propias fórmulas.  El XML era una buena solución para la persistencia pero no para la interacción con el humano.

Pensando un poco y revisando los muy pocos antecedentes que encontré en el mercado, realicé un diseño para el editor de fórmulas donde cada fila sería una operación que tendría un nombre como identificador y que podría ser utilizado como atributo o parámetro de cualquier otra operación.  Se establecieron las siguientes operaciones.

  1. Operaciones aritméticas (suma, resta, multiplicación y división).
  2. Valor constante.
  3. Valor definido por el usuario.
  4. Sumatoria.
  5. Función externa.

Quería que la interfaz del editor fuera lo mas dinámica posible y elegí para su implementación el uso de Prototype y Scriptaculous.

La primera versión la implementé a principio de este año y era en realidad, mi primera aproximación a estas librerías de JavaScript por lo que su código no explota todas las ventajas de estas librerías.

Versión 0.1.
Versión 0.1.

Como prototipo fue muy interesante y entretenido de desarrollar.  Los colores por supuesto eran para diferenciar rapidamente los diferentes divs de cada operación.

Durante este mes estuve implementando una nueva versión del editor basado en las conclusiones que obtuvimos al mostrarle al cliente nuestro prototipo.  Algunas funcionalidades del prototipo no fueron incluídas en la siguiente versión (al menos en esta iteración) al no aportar grandes beneficios al resultado final.

  • La mayoría de efectos gráficos (muy interesantes).
  • La posibilidad de reordenar a una operación hacia cualquier fila según el índice especificado.
  • El reordenamiento de parámetros de una función.
  • La especificación del tipo de una operación a medida que se determinan sus elementos.

Esta nueva versión incluye varios cambios a nivel conceptual pero en mayor medida incluye muchas mejoras a nivel de código, lo que la hace ahora compatible con IE7.  En especial gracias a las facilidades que traen consigo estas librerías para trabajar manipulando los elementos DOM sobre clases CSS en lugar de los IDs específicos de los elementos.

Versión 0.2.
Versión 0.2.

Esta nueva versión incluye algunas características como las siguientes.

  • Interfaz de usuario mas limpia y clara.
  • Colores para identificar facilmente el tipo de operación.
  • Fácil reordenamiento de operaciones con sólo arrastrar y soltar las mismas.
  • Agregar operaciones (de un tipo definido) y remover las operaciones seleccionadas.
  • Actualización automática de las opciones de atributos y parámetros tan pronto como se desea su modificación y algún nombre de operación se ha modificado o introducido nuevo.
  • Las funciones pueden tener cero o mas parámetros los cuales pueden ser especificados dinámicamente.

El proyecto que estoy desarrollando es muy interesante y aún falta mucho de su implementación, inclusive en mi imaginación tengo muchas ideas para mejorar a este editor: validadores de datos (que tienen un problema interesante de resolver), asistentes WYSIWYG para los diferentes tipos de valores de datos, buscador de funciones (definidas del lado del servidor), ayuda en línea para las funciones, etc.  Desafortunadamente el editor es sólo una pequeña parte de un cuarto del proyecto por lo que tendré que postergar a estas mejoras para lo que espero sea una próxima iteración.

JavaScript, como todo lo que aprendo y disfruto en la vida, entre mas aprendo, mas me doy cuenta que nada se, sin embargo también he notado que estos frameworks de JavaScript facilitan enormemente el trabajo encapsulando la mayor parte sucia, en particular, facilitandome el trabajo con la independencia de navegadores.

Hasta ahora he probado a MooTools y a Prototype/Scriptaculous con amplio éxito.  Hay muchos otros por experimentar: JQuery, ExtJS, Dojo, YUI, etc.  Como siempre, las ganas sobran, el tiempo es el que falta 😉

Agregación y remoción de registros de tablas con campos validables

Como podía esperarse, el siguiente paso era obvio: la integración entre las tablas manipuladas con DOM para agregar y remover fácilmente registros y la validación de campos utilizando Really easy field validation library y Prototype.

Las tablas pueden ser validadas de dos maneras: la interna, incluida en la versión 0.1 de la distribución verifica que se intenta ingresar en el campo y permite o impide su digitación según el validador especificado, no muestra mensajes de error.  El segundo tipo de validación, la externa, se basa en RWFVL y verifica el contenido del campo cuando se pasa el foco sobre él, mostrando mensajes de error si no se cumple la validación.

Para determinar el método de validación a usarse, se verifica la existencia de la clase Validation del validador externo, en caso de encontrarse se utiliza este método, en caso contrario se utiliza el interno.

    
    

Tan pronto como se cargue el árbol DOM se debe especificar cuales de los formularios (con las tablas incluídas) serán expuestos al sistema de validación.

    document.observe('dom:loaded', function()
    {
        /* Determina cuales 
van a ser incluidos para la validación (externa) */         if(typeof(TDActivarValidacion) != undefined)             TDActivarValidacion(new Array('formulario1', 'formulario2'));     });

El resto de información configurable en la página cliente no ha cambiado desde la versión anterior.  Se deben definir las siguientes funciones.

  • TDObtEstructuraRegistro(tablaId): determina la estructura de las filas o registros de la tabla cuyo id es tablaId.
  • TDObtPropiedadesCampo(tablaId): determina las propiedades o atributos de un campo de la tabla cuyo id es tablaId.
  • TDObtPropiedadesFila(tablaId): determina las propiedades o atributos de una fila de la tabla cuyo id es tablaId..

La validación de los campos se activa incluyendo los parámetros apropiados en la variable validador del campo específico en el retorno de la función TDObtEstructuraRegistro.  Los posibles valores de esta variable son los siguientes.

Método de validación interno.

  • requerido
  • enteros
  • reales
  • no_numeros
  • letras_minusculas
  • letras_mayusculas
  • letras_todas
  • numeros_letras
  • email
  • telefono
  • url
  • fecha

Método de validación externo.

  • required (no vacío)
  • validate-number (un número válido)
  • validate-digits (sólo digitos)
  • validate-alpha (sólo letras)
  • validate-alphanum (sólo letras y números)
  • validate-date (una fecha válida)
  • validate-email (una dirección de correo válida)
  • validate-url (un URL válido)
  • validate-date-au (una fecha con formato; dd/mm/yyyy)
  • validate-currency-dollar (un valor de moneda)
  • validate-selection (una opción válida seleccionada)
  • validate-one-required (al menos un textbox/radio seleccionado)

Las opciones de validación externa pueden combinarse como por ejemplo en “required validate-number“.  Ver campo Edad del formulario de demostración.

Enlace:

Manipulación fácil del DOM de una tabla con Prototype

El día de hoy realicé la migración del pequeño script de manipulación DOM de tablas de su versión de Mootools a Prototype.  La versión 0.1 está basada en la versión 0.2 del primero y fue desarrollado con la versión 1.6.0.2 de la librería.

Básicamente es el mismo JavaScript, permite agregar y remover filas de una tabla dinámicamente desde el lado del cliente.

La distribución incluye a las siguientes funciones.

  • TDAgregarRegistro(tablaId): agrega una nueva fila a la tabla identificada con el id tablaId.
  • TDRemoverRegistro(tablaId): remueve a las filas seleccionadas de la tabla identificada con el id tablaId.
  • TDValidarDato(valor, tipo, opciones): determina si un dato es válido según el tipo de verificación elegido.

La implementación de esta funcionalidad requiere que se implementen las siguientes funciones en la página cliente.

  • TDObtEstructuraRegistro(tablaId): determina la estructura de las filas o registros de la tabla cuyo id es tablaId.
  • TDObtPropiedadesCampo(tablaId): determina las propiedades o atributos de un campo de la tabla cuyo id es tablaId.
  • TDObtPropiedadesFila(tablaId): determina las propiedades o atributos de una fila de la tabla cuyo id es tablaId..

Al igual que la versión 0.2 con Mootools, incluí un validador sencillo para los datos de entrada basado en el campo validador de la especificación y que utiliza directamente a la función TDValidarDato.  Ver el campo edad del ejemplo de demostración.

Por ahora la validación continúa con un problema que no he logrado solucionar: el evento incluye el código de la tecla presionada sin importar si era en mayúsculas o minúsculas, dificultando validar esta sensibilidad.  Esto es particularmente importante para el caso de los números ya que 2 y @ estarían significando lo mismo para el validador.

Enlaces:

Introducción a PrototypeJS

  • Prototype es creado en marzo de 2005 por Sam Stephenson.
  • Parte de Ruby on Rails, sin embargo puede ser utilizado con cualquier lenguaje/ambiente de desarrollo.
  • Su misión es la de mejorar, reemplazar y ampliar las capacidades de Javascript.
  • Es interpretado del lado del cliente por el navegador web, es parte de la vista.
  • Incluye facilidades para el manejo del DOM, eventos, elementos, arreglos, formularios, AJAX, JSON, etc.
  • Permite desarrollar con independencia de navegador web.
  • Se acopla muy bien a Scriptaculous, librería de efectos creada por Thomas Fuchs en junio de 2005.

  • Introducción.
  • Inclusión de la librería.
  • Función $().
  • Función $w().
  • Función $A().
  • Función $F().
  • Función $H().
  • Función $R().
  • Función $$().
  • Enlaces de interés.

Enlace: Prototype Javascript Framework.

Introspección de Objetos con Prototype

La introspección de objetos permite consultar información del objeto mismo. En Prototype los métodos descritos a continuación pertenecen a la clase Object.

Object.inspect(e) : string Retorna la representanción de cadena del elemento, útil para labores de depuración.
Object.isArray(e) : boolean Retorna verdadero si el elemento es un arreglo, falso de lo contrario.
Object.isElement(e) : boolean Retorna verdadero si el elemento es un objeto DOM, falso de lo contrario.
Object.isFunction(e) : boolean Retrona verdadero si el elemento es una función, falso de lo contrario.
Object.isHash(e) : boolean Retorna verdadero si el elemento es un hash, falso de lo contrario.
Object.isNumber(e) : boolean Retorna verdadero si el elemento es un número, falso de lo contrario.
Object:isString(e) : boolean Retorna verdadero si el elemento es una cadena, falso de lo contrario.
Object.isUndefined(e) : boolean Retorna verdadero si el valor del elemento es undefined, falso de lo contrario.
Object.keys(e) : [string, …] Retorna un arreglo con las llaves del elemento.
Object.values(e) : [string, …] Retorna un arreglo con los valores del elemento.
Object.toQueryString(e) : string Retorna la representación de consulta en cadena codificada para URL del elemento.

La función inspect realiza el siguiente procedimiento para calcular la representación en cadena de un objeto.

  • Si el objeto es null o undefined utiliza los nombres de estos valores.
  • Si el objeto presenta el método inspect, este es llamado.
  • De lo contrario se utiliza el método toString presente en todos los objetos.

Revise el siguiente ejemplo para conocer como utilizar estos métodos de introspección.

Para mas información acerca de estos métodos consulte el API referente a la clase Object.

Funciones básicas de Prototype

Para incluír Prototype en las páginas web se debe agregar la siguiente línea a la sección de head, reemplazando la ruta src con la adecuada.

La función $() reemplaza a la expresión document.getElementById del JavaScript estándar revisando el árbol de elementos DOM y retornando una versión ampliada del elemento en cuestión. Esta función recibe como parámetro la etiqueta del ID del elemento o directamente la referencia al objeto DOM para obtener su versión ampliada. Más información.

a = document.getElementById('seccion_1');         // JavaScript convencional.
b = $('seccion_1');     // Estilo Prototype.

a.style.display = 'none';    // JavaScript convencional.
b.hide();     // Gracias a la extensión de los elementos que provee Prototype.

Esta función puede utilizarse con varios parámetros en un único llamado.

secciones = $('seccion_1', 'seccion_2', 'seccion_3');

Esto equivale a decir lo siguiente.

secciones = [];
secciones[0] = $('seccion_1');secciones[1] = $('seccion_2');
secciones[2] = $('seccion_3');

La función $w() convierte cadenas con palabras separadas por espacios en arreglos cuyas celdas corresponden a estas palabras. Más información.

numeros = $w('uno dos tres cuatro');

El contenido de numeros será entonces [‘uno’, ‘dos’, ‘tres’, ‘cuatro’].

La función $$() realiza búsquedas basadas en los selectores de CSS, el resultado de esta función será siempre un arreglo toda vez que la búsqueda podrá arrojar múltiples resultados. Más información.

$$('div')     // Todos los divs.
$$('#seccion_1')     // Igual que $('seccion_1') pero en forma de arreglo.
$$('li.resaltado')     // Todos los LI que tengan la clase 'resaltado'.
$$('#seccion_1 p')     // Todos los P dentro de seccion_1.

Existen múltiples opciones para esta función flexible, para consultar mas información al respecto revise el API correspondiente.

La función $A() convierte (promueve a) los elementos estilo colección (o arreglo) en objetos Array de Prototype. La ventaja de utilizar este método es que enriqucerá el resultado con los método de la clase Array como each, indexOf y toJSON, facilitando el procesamiento de la información contenida en él. Más información.

Esta función opera bajo las siguientes premisas:

  • Si el valor es null, undefined o false, el arreglo resultante es vació.
  • Si el objeto presenta el método toArray() entonces este método es utilizado.
  • De lo contrario los valores del objeto son recorridos y manipulados de la manera convencional.

La función $F() toma un elemento campo de un formulario, ya sea el objeto DOM o su respectivo ID y retorna el valor contenido en el campo. Es una versión mejorada de la sentencia document.getElementByID(‘elemento’).value. Más información.

Esta función opera bajo las siguientes premisas:

  • Para las listas de selección múltiple se obtiene un arreglo con cada uno de los valores selccionados.
  • Para los checkboxes y radiobuttons no seleccionados se obtiene null.
  • Para cualquier otro elemento se obtiene una cadena de texto con el valor del campo.

La función $H() convierte (promueve a) un hash de JavaScript un objeto Hash de Prototype con sus consiguientes métodos ampliados. Más información.

opciones = $H({color: 'rojo', sabor: 'salado', tipo: 'alimento'});

opciones.keys();    // Retorna ['color', 'sabor', 'tipo'].
opciones.values();    // Retorna ['rojo', 'salado', 'alimento'].

La función $R() crea objetos rango (ObjectRange) de Prototype con sus respectivos métodos y usos útiles. Más información.

$R(1, 10).each(function(i)
{
// Se ejecuta 10 veces iterando a i desde 1 hasta 10.
});

$A($R(1, 10));    // Retorna el arreglo [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].