Alphasite

The programmers site

Estadísticas web

Estadisticas web

Publicidad

Languages

Inicio de sesión

Google AdSense

Encuesta

En línea

En este momento hay 0 usuarios y 17 invitados en línea.

Parte I. Contenedores.


Introducción

Para aprender un poco de Ajax yo estoy siguiendo el libro Ajax in Action de forma que cuando trato de explicar lo que voy haciendo sigo el hilo que va marcando el libro (por si alguién quiere comprarselo, que a mi me parece un libro estupendo).

El patron modelo, vista, controlador

El patron MVC consiste en la separación de ciertos elementos de una página web en tres elementos lógicos. Es algo similar a la separación de contenido y forma que ya mencionabamos en el articulo anterior.

El modelo describe el contenido de la página, como su nombre indica, lo que modela la página. Por ejemplo, una página que muestra un mapa (como google maps) modela localizaciones y puntos de interes mientras que la página de un periódico modela noticias, articulos y, posiblemente, anuncios.

La vista es la forma de mostrar el contenido al usuario y de recibir las acciones que este realiza. Representa el interfaz del usuario. En un buen diseño deberíamos ser capaces de cambiar la vista (el interfaz que le presentamos al usuario) sin tocar el modelo o el controlador. Es más, sería (o debería ser) perfectamente factible implementar un nuevo modelo de vista (por ejemplo un frontrend flash) sin tener que cambiar nada excepto la vista.

El controlador es la pasta que mantiene el sistema unido. Cuando el usuario interactua sobre la vista (por ejemplo haciendo click en un elemento), esta nofica al controlador de que se ha producído un evento (el usuario ha pinchado en un elemento) y este toma las acciones necesarias para responder a dicha interacción (por ejemplo iniciar una actualización de los datos del modelo). A su vez, cuando se produce un cambio en el modelo (por ejemplo cuando terminan de cargarse los datos pedidos) este notifica el cambio al controlador, el cual se encarga de modificar la vista para que muestre los datos al usuario.

Antes de empezar. Sobre el formato

Aunque no siempre es necesario, y muchas webs agrupan todas sus funciones en uno o dos ficheros javascript, yo siempre he preferido seguir el modelo de "un fichero por clase" de forma que cada fichero javascript haga referencia a una clase o, en su defecto aun conjunto de tres o cuatro clases como máximo que esten fuertemente relacionadas entre si.

Por otro lado comentar es una tarea fundamental en cualquier lenguaje de programación, pero en javascript es especialmente importante puesto que, al no ser un lenguaje totalmente orientado a objetos, carece de algunas de las facilidades proporcionadas por otros lenguajes (como por ejemplo la capacidad de definir clases abstractas e interfaces). El ejemplo más claro lo veremos más adelante al definir los contenedores ya que estos tendrá una referencia a un objeto hijo que esperamos que provea un meotodo específico (que en Delphi, por ejemplo, es el equivalente a decir que provea un determinado interfaz) pero nada nos garantiza que dicho metodo vaya a estar ahí.

Contenedores (o ventanas)

Si pensamos en una aplicación de escritorio, o más globalmente aún en nuestro escritorio de nuestro sistema operativo favorito (solo para aquellos S.S.O.O. con sistema de ventanas !!) podemos observar que tenemos un espacio de trabajo en el cual encontramos ventanas que representan distintas aplicaciones. De esta forma podemos producir una separación entre aplicaciones que realizan tareas distintas o, dentro de una misma aplicación separar contenidos y permitir que el usuario organize cada uno de ellos como lo haría en una mesa de escritorio normal, poniendo un cuaderno aqui, un post-it allá, etc ...

En una página web normal y corriente esto no suele ocurrir, el contenido es fijo y esta organizado de antemano. Esto algunas veces es conveniente o incluso necesario pero en otras ocasiones (como la que nos ocupa) nos interesa aumentar el nivel de interactividad entre el usuario y hacerle que se sienta comodo. Esto es especialmente importante en las aplicaciones Ajax, por ninguna razón concreta excepto que, ya que vas a hacer una página web asincrona con el objetivo de aumentar la interactividad !! hazla bien ¡¡

La clase contenedor

Nuestra clase contenedor será la encargada de permitir al usuario manejar el contenido que se presenta en pantalla exactamente igual que hacen las ventans en windows. La idea es que dentro de un contenedor podamos meter cualquier tipo de contenido y permitir que el usuario interactue con él.

Vamos a empezar por la parte básica, el constructor de la clase y alguna de sus propiedades.

/**
 * Clase que encapsula un contenedor de la página web.
 */

function CContainer(nombre)
{
    this.nombre = nombre;
    this.top = container_default_top;
    this.left = container_default_left;
    this.width = container_default_width;   
    this.content = null;
    this.main = null;
    this.header = null;
    this.contentDiv = null;
    this.active = false;
}

En el constructor tan solo vamos a definir las propiedades básicas del objeto. Nombre nos va a permitir localizar el objeto. Top left y width controlarán la posición del container y active indicará si un contenedor esta activo (en primer plano), por último main, header y contentDiv es un puntero al elemento HTML que representa el contenedor principal(div), el titulo del contenedor y el div en el que ubicaremos el content.

La propiedad content es algo especial pero fundamental. Va a representar el puntero al contenido que se mostrará dentro del contenedor. Para ello, puesto que javascript no tiene clases abstractas sencillamente asumiremos que todo objeto que asignemos a content va a tener un metodo GetHtmlCode() al que podamos llamar. De esta forma podremos ubicar cualquier tipo de contenido siempre que implemente el metodo.

Bien, hasta ahora no tenemos nada, prometedor eh!. Vamos a empezar a implementar varios metodos del objeto, el primero de ellos el que va a definirlo en la página como un elemento HTML.

CContainer.prototype.Insert = function CContainer_Insert()
{
    this.main = document.getElementById(this.nombre);
    if (this.main == null)
    {       
        // Configurar las propiedades del contenedor   
        // Crear el div que constituye el contenedor
        // y añadirlo al documento
        this.main = document.createElement('div');       
        this.main.className = 'container';
        this.main.id = this.nombre;
        // Establecer las propiedades del contenedor       
        this.main.style.top = this.top + 'px';
        this.main.style.left = this.left + 'px';               
        this.main.style.width = this.width + 'px';
        this.main.onmousedown = this.BeginDrag;
        this.main.onmouseover = this.HandleMouseOver;
        this.main.container = this;
        // Insertar el contenedor en la página
        document.body.appendChild(this.main);
        // Añadir los dos divs (cabecera y contenido)
        this.header = document.createElement('div');
        this.header.id = this.main.id + '_header';
        this.main.appendChild(this.header);
        this.header.className = 'header';
        // Asignar los eventos de movimiento a la cabecera.       
        this.header.onmousedown = this.BeginMove;
        this.header.onmouseup = this.EndMove;
        this.header.container = this;
        this.header.lastMouseX = -1;
        this.header.lastMouseY = -1;                               
        this.contentDiv = document.createElement('div');
        this.contentDiv.container = this;
        this.contentDiv.id = this.nombre + '_content';
        this.main.appendChild(this.contentDiv);       
        this.contentDiv.className = 'content';       
        this.contentDiv.onmousedown = this.HandleClick;
        if (this.content)
        {
            this.contentDiv.innerHTML = this.content.GetHTML();
            this.header.innerHTML = this.content.title;
            this.content.container = this;           
        }
       
    }
    this.MakeActive();   
    return this.main;
}

En primer lugar comprobamos si existe un elemento con el nombre especificado. El nombre de cada contenedor deberá representar un único elemento HTML con un id único. Si no existe procederemos a crear el objeto.

Además de definir las características principales se asignan los eventos que van a controlar el movimiento de nuestros contenedores (al hacer click en la cabecera) o el redimensionado de estos (que será un poco más dificil).

Primero, el objeto principal deberá controlar el click (para iniciar un posible redimensionado) así como el mousemove, es decir, el evento que se lanza cuando el cursor se mueve por encima del contenedor (para ir cambiando el cursor para adaptarse a lo que le vamos a ofrecer al usuario).

Por otro lado el objeto de cabecera deberá controlar el mousedown (el momento en que se pulsa un boton) y el mouseup (el momento en el que se suelta el boton) de forma que podamos realizar el movimiento del contenedor cuando se haga click en la cabecera.

Vamos a echar un vistazo a los metodos que manejaran los eventos anteriores.

CContainer.prototype.BeginMove = function CContainer_BeginMove(e)
{   
       
    e = GetEvent(e);
    if (this.container)
    {
      this.container.MakeActive();           
      currentWindow = this.container.main;   
      currentWindow.lastMouseX = e.clientX - parseInt(currentWindow.style.left);
      currentWindow.lastMouseY = e.clientY - parseInt(currentWindow.style.top);
      document.container = this.container;
      document.onmousemove = this.container.HandleMouseMove;       
      document.onmouseup = this.container.EndMove;
      document.dragType = ac_move;       
    }   
}

La función GetEvent se encarga de devolvernos el objeto evento (que se obtiene de distinta forma en internet explorer (una de las 'gracias' de Microsoft y su mania de no acogerse a los estandares)).

La secuencia que seguimos es activar el contenedor que contiene la cabecera, marcar la variable global currentWindow con el objeto que actualmente está activo (esto probablemente podríamos delegarlo a la función MakeActive) y establecemos dos variables en el objeto q nos indicarán el punto en el que se hizo click de forma que, cuando movamos el contenedor, podamos hacer que el movimiento sea coherente (es decir no desplazar la parte izquierda automáticamente a la posición del cursor).

Por último asignamos los controladores de eventos, uno es el HandleMouseMove que se encargará de realizar el movimiento del contenedor siguiendo al ratón y el EndMove que se encargará principalmente de desasignar el HandleMouseMove.

Para acabar con esta parte vamos a poner tres funciones más, una es el manejador de movimiento del raton (HandleMouseMove), otra es la que finaliza el movimiento (EndMove) y otra es la función que hace activo un contenedor (MakeActive).

CContainer.prototype.HandleMouseMove = function CContainer_HandleMouseMove(e)
{   
    e = GetEvent(e);
           
    moveXBy = e.clientX - currentWindow.lastMouseX;
    moveYBy = e.clientY - currentWindow.lastMouseY;
    currentWindow.container.top = moveYBy;
    currentWindow.container.left = moveXBy;
    currentWindow.style.top = moveYBy + 'px';
    currentWindow.style.left = moveXBy + 'px';   
}

La función es bastante sencilla, se limita a desplazar el contenedor al punto en el que esta el cursor (clientX y clientY) restandole la distancia desde donde pulsó originalmente. Una vez hecho esto actualizamos las propiedades (son solo de consulta pero es interesante mantenerlas actualizadas) y por último actualizar el estilo del div para cambiar su posición.

CContainer.prototype.EndMove = function CContainer_EndMove(e)
{       
    document.onmousemove = null;
}

Esta función sencillamente se va a limitar a desasignar el manejador del evento omousemove.

CContainer.prototype.MakeActive = function CContainer_MakeActve()
{
    if (document.lastActiveContainer)
        document.lastActiveContainer.MakeUnActive();
       
    this.active = true;   
    this.main.style.zIndex = '10';
    this.header.className = 'header';
    document.lastActiveContainer = this;   
}

CContainer.prototype.MakeUnActive = function CContainer_MakeUnActive()
{
    this.active = false;
    this.main.style.zIndex = '0';
    this.header.className = 'header_inactive';
}

La función MakeActive (y MakeUnactive) va a encargarse de traer las ventanas al frente y cambiar su aspecto de forma que podamos identificar de alguna forma cual es la ventana activa. Para ello vamos a tocar dos propiedades fundamentalmente el zIndex y por supuesto el className (al cual tendremos asignados dos colores distintos por ejemplos y quizá distintas letras).

Bueno, más o menos eso es todo lo necesario para crear algunos contenedores básicos que pueden desplazarse por la pantalla.

Aqui podeis encontrar una pequeña demostración del funcionamiento de lo anterior (el código es mejorable (y de hecho estoy en ello) así que no le presteis mucha atención por que esta entre medias de esta versión y de la que permite hacer resize y cerrar los contenedores.