Ejemplo 2 de Ajax con Prototype: estableciendo la persistencia

Introducción.

Después de crear la estructura general de la aplicación web en JavaScript, el siguiente paso es determinar e implementar como se va a realizar la persistencia de los datos de los contactos del lado del servidor.

Para la aplicación de demostración sólo se necesitará almacenar la información de una única entidad llamada Contacto que incluirá los siguientes campos.

  1. Identificador – código del Contacto, servirá como llave primaria.
  2. Nombres.
  3. Apellidos.
  4. Correo electrónico.
  5. Fecha de nacimiento.

Como motor de base de datos se va a utilizar SQLite el cual para este caso práctico nos trae varias ventajas entre las cuales podemos contar a muy buena integración con PHP, soporte la lenguaje SQL y por ende al modelo relacional y la facilidad de instalación y administración, que incluye evitarnos la necesidad de instalar otro software cliente/servidor como sería el caso con MySQL que es un motor de base de datos de un perfil mucho mayor.

Creación de la base de datos.

Para crear la base de datos se realizan los pasos descritos a continuación.

Se crea el directorio /data bajo la carpeta del proyecto web.

Se descarga la aplicación sqlite.exe (para este ejemplo se utilizó la versión 3.6.6.2) y se copia este archivo en el directorio recién creado.

Se establece al comando la sentencia DDL para la creación de la tabla tal y como se describe a continuación.

CREATE TABLE contacto
(
    id          INTEGER      PRIMARY KEY,
    nombres     VARCHAR(30)  NOT NULL,
    apellidos   VARCHAR(30)  NOT NULL,
    correo      VARCHAR(128) NOT NULL,
    fnacimiento DATE         NOT NULL
)

El comando anterior es grabado en el archivo /data/script.sql para su posterior ejecución.

En una ventana de Símbolo del Sistema de Windows (o Shell de *nix) accedemos al directorio de datos donde se encuentra ahora sqlite.exe y el script.sql y ejecutamos los siguientes comandos del sistema operativo.

cd <ruta>data
sqlite contactos.sqlite < script.sql

La ejecución de la línea anterior se encarga de crear el archivo de la base de datos (contactos.sqlite) y de interpretar el contenido del archivo script.sql con la definición de la base de datos.

El procedimiento descrito anteriormente puede realizarse también utilizando herramientas mas elaboradas que facilitan el desarrollo y administración de bases de datos SQLite, entre las cuales destaco SQLite Manager que es un plugin muy util para Firefox.

Implementación del acceso a la base de datos.

Para realizar el acceso a la base de datos SQLite desde PHP se van a utilizar las clases PDO (PHP Data Objects) y se va a implementar la clase BaseDeDatos para simplificar su acceso desde la aplicación.

Para hacer esto creamos el archivo <ruta>basededatos.php en el directorio raíz del proyecto en el cual se define la clase BaseDeDatos de la siguiente manera.

<?php

class BaseDeDatos
{
    private $enlace;

    public function __construct ($dsn)
    {
        $this -> conectar ($dsn);
    }

    // Resto de la implementación.
}

?>

El atributo de clase $enlace almacenará la referencia a la conexion a la base de datos, mientras que el constructor se encargará de recibir la cadena de conexión (DSN) y él mismo se encarga de gestionar la conexión.

Para hacer esto se apoya en el método estático crearDSN diseñado para facilitar su preparación.

    public static function crearDSN ($informacion)
    {
        return 'sqlite:' . $informacion['ruta'];
    }

Este método espera recibir la $informacion de conexión de la siguiente manera la cual por conveniencia de centralizarla se ubicó en el archivo config.php.

array('ruta' => 'data/contactos.sqlite')

La clase BaseDeDatos incluye también métodos para conectarse (a partir de la información proporcionada por en el DSN) y desconectarse de la fuente de datos.

    public function conectar ($dsn)
    {
        $this -> enlace = new PDO ($dsn);

        return $this -> enlace;
    }

    public function desconectar ()
    {
        $this -> enlace = null;
    }

E incluye además métodos para realizar consultas sobre los datos almacenados.

    public function ejecutar ($consulta)
    {
        return $this -> enlace -> exec ($consulta);
    }

    public function consultar ($consulta)
    {
        return $this -> enlace -> query ($consulta);
    }

La diferencia entre consultar y ejecutar radica en que el primero de ellos se utiliza para obtener información (SELECT) de la base de datos y por ende retorna un conjunto de resultados, mientras que el segundo de ellos se utiliza para modificar información (INSERT, UPDATE, DELETE) y retorna el número de filas afectadas por el comando ejecutado.

Conclusiones.

En este capítulo se estableció todo lo referente a la persistencia de la información de los contactos del proyyecto, incluyendo a una capa adicional de software (la clase BaseDeDatos) que facilitará el manejo del acceso a los datos y proveerá simplicidad si se desea posteriormente modificar el motor de base de datos.

El siguiente paso consistirá en la implementación de la entidad Contacto como tal que utilizará como sustrato lo implementado hasta ahora.

Enlaces.

Ejemplo 2 de Ajax con Prototype: preparando el escenario con JavaScript

Creación de los archivos JavaScript.

Después de crearel nivel de presentación con XHTML y CSS, continuamos con la preparación de la infraestructura funcional de la aplicación utilizando JavaScript.

Para esto descargamos la última versión de Prototype, la cual para este caso era la 1.6.0.3 y lo ubicamos en un nuevo directorio llamado /js bajo el directorio de la aplicación.

En ese mismo directorio creamos el archivo funciones.js donde se van a agregar a continuación las funciones que determinarán el comportamiento del sistema.

Referencia desde el archivo XHTML.

Es necesario incluir a estos dos nuevos archivos (prototype-1.6.0.3.js y funciones.js) en la aplicación para que el navegador los descargue cuando el usuario la acceda.  Para hacer esto se incluye su referencia en la cabecera del archivo demo.php de la siguiente manera.

    <script src="js/prototype-1.6.0.3.js" type="text/javascript"></script>
    <script src="js/funciones.js" type="text/javascript"></script>

Estableciendo el estado inicial.

Lo primero que se debe hacer establecer el manejo de eventos de la aplicación, si se recuerda toda la información que conectaba los componentes XHTML a sus eventos (onclick, onchange, etc.) fue omitida voluntariamente para promover la separación de contenido.  Esto se debe realizar tan pronto como la aplicación es cargada para que cuando el usuario interactúe con ella los eventos ya se encuentren disponibles.  Para esto se utiliza el evento on DOM loaded el cual sucede tan pronto como la estructura (XHTML) de la página ha sido cargada.  Se aprovecha también para llamar a la función que listará a los contactos registrados (6) para que aparezcan tan pronto como el usuario accede al sitio.  Esta función como otras será implementada próximamente.

document.observe('dom:loaded', function()
{
    establecerManejoEventos();

    enActualizarBusqueda(null);
});

Requerimientos funcionales.

Como se mencionó anteriormente, la función establecerManejoEventos especifica que funciones se van a invocar ante cual evento generado y determinar con esto el flujo de la aplicación.  El esquema general de este flujo se basa en las siguientes premisas.

  • Si el usuario desea crear a un nuevo registro (bNuevo) se deberá limpiar el formulario para que se ingresen los nuevos datos y la acción siguiente será la de agregar el registro.
  • Si el usuario presiona el botón de acción (bAccion) su funcionamiento dependerá de si se está agregando a un registro nuevo o si por el contrario se está editando a un registro ya existente.  El texto de la etiqueta define su funcionalidad.
  • Si el usuario presiona el botón de remover (bRemover) se deberá solicitar la remoción del registro y actualizar la lista de contactos registrados la cual ya no deberá incluír al mensaje antes seleccionado.
  • Si el usuario presiona el botón de listar (bListar) se deberá actualizar la lista de contactos registrados.
  • Si el usuario modifica alguno de los filtros de búsqueda (sLetra y sCampo) se deberá actualizar el listado de los contactos registrados mostrando únicamente los que cumplan con el criterio de búsqueda seleccionado.
  • Si el usuario decide editar a un registro específico haciendo click sobre su enlace desde el listado de contactos registrados, sus datos deben ubicarse en el formulario de edición y la próxima acción deberá ser la de editar a dicho registro.
  • En cualquier caso se deberá mostrar un mensaje de éxito (verde) o de error (rojo) al usuario indicándole el estado de su solicitud.

Establecimiento del manejo de eventos.

Para manipular formalmente el flujo de la aplicación a partir de los eventos sucitados, se establecen observadores sobre los diferentes componentes los cuales relacionan a un determinado tipo de eventos sobre ellos con una función JavaScript que será ejecutada cuando sucedan.  Los tipos de eventos utilizados por esta aplicación de demostración son onClick sobre los botones y onChange en los campos de selección.

function establecerManejoEventos()
{
    $('bNuevo').observe('click',   enNuevo);
    $('bAccion').observe('click',  enCrearActualizar);
    $('bRemover').observe('click', enRemover);
    $('bListar').observe('click',  enActualizarBusqueda);
    $('sLetra').observe('change',  enActualizarBusqueda);
    $('sCampo').observe('change',  enActualizarBusqueda);
}

Mostrar mensajes de estado.

El mensaje de estado es un texto que le indica al usuario final el éxito o fracaso de la solicitud que le hizo al sistema.  Para tal fin se definió a mensaje el cual es un div que se encuentra en la parte superior del formulario después de la cabecera con el título.

Las siguientes funciones se utilizan para escribir un texto en el mensaje de estado y para limpiarlo.

function ponMensajeEstado(mensaje)
{
    $('mensaje').update(mensaje);
}

function limpiarMensajeEstado()
{
    ponMensajeEstado('');
}

Mensajes de estado buenos y malos.

Los mensajes de estado indican del éxito o fracaso de un procedimiento, para diferenciarlos visualmente se decidió que los mensajes de éxito se presenten de color verde y los mensajes de error sean de color rojo.  Para esto se crearon dos funciones por separado que facilitarán su presentacion y que a su vez se basan en las funciones recientemente creadas.

function ponMensajeBien(mensaje)
{
    $('mensaje').addClassName('MensajeBien');
    $('mensaje').removeClassName('MensajeMal');

    ponMensajeEstado(mensaje + '.');
}

function ponMensajeMal(mensaje)
{
    $('mensaje').addClassName('MensajeMal');
    $('mensaje').removeClassName('MensajeBien');

    ponMensajeEstado(mensaje + '.');
}

MensajeBien y MensajeMal son dos clases CSS definidas en css/style.css que determinan el formato del texto a desplegarse.  Consulte dicho archivo para complementar su utilización.

Crear un nuevo registro.

Como se mencionó anteriormente, la creación de un nuevo registro involucra tres actividades: limpiar el formulario para que el usuario ingrese la nueva información, modificar el tipo de acción próximo a Agregar y limpiar el mensaje de estado para no confundir al usuario con cualquier mensaje previo.

function enNuevo(evento)
{
    $('bAccion').value      = 'Agregar';

    $('tId').value          = '';
    $('tNombres').value     = '';
    $('tApellidos').value   = '';
    $('tCorreo').value      = '';
    $('tFNacimiento').value = '';

    limpiarMensajeEstado();
}

Modificar la información.

El manejo de una solicitud de acción es levemente mas elaborado ya que puede ser de dos tipos: Agregar a un nuevo registro o Editar a uno ya existente.  Por claridad, la función que maneja el evento va a determinar el tipo de acción solicitada y realizará la invocación de una función específica, con su apropiada implementación.

function enCrearActualizar(evento)
{
    if($('bAccion').value == 'Agregar')
        return enAgregarRegistro(evento);

    if($('bAccion').value == 'Editar')
        return enEditarRegistro(evento);

    alert('La acción especificada es desconocida!');

    limpiarMensajeEstado();

    return false;
}

Manipulación y acceso a los datos.

Las funciones restantes: enAgregarRegistro, enEditarRegistro, enActualizarBusqueda, enCargarRegistro y enRemover que permiten manipular la información de los registros serán implementadas utilizando AJAX, sin embargo su implementación tendrá que esperar un poco ya que antes es necesario determinar e implementar el modelo de datos de la aplicación.

Conclusiones.

En este capítulo se realizó la implementación de la estructura que provee el flujo de eventos de la aplicación en general, esto se realizó en JavaScript y utilizando las facilidades de la librería de Prototype.

Algunas de las funciones de este nivel, las relacionadas con la manipulación de información, quedarán pendientes de implementación hasta que se realice la implementación de la persistencia en un próximo capítulo del tutorial.

La aplicación a este nivel ya es funcional, teniendo en cuenta que por ahora, las funciones sin implementación se limitarán a mostrar en pantalla un mensaje informativo.

Enlaces.

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.