debug_mode=ON

Buscar en

 
 

Javascript no intrusivo con Prototype

Escrito por gimenete hace 1 años bajo una licencia de Creative Commons Creative Commons License
2982 visitas. Etiquetas: javascript, html, prototype

Supongamos que tenemos una página web en la que los usuarios pueden publicar contenidos (que llamaremos items) y también pueden tener un conjunto de items favoritos.

Cuando un usuario visita un item aparece un enlace para añadir o eliminar dicho item de favoritos. Esta es parte de la plantilla HTML (Django):

{% if canadd %}
<span><a id="fav" href="/item.favourite?key={{ item.key }}">Añadir a favoritos</a></span>
{% else %}
<span><a id="fav" href="/item.favourite?key={{ item.key }}">Eliminar favorito</a></span>
{% endif %}

Y este es parte del código que se ejecuta en el servidor (python).

# user es el usuario e item es el artículo que se quiere añadir a favoritos
favourite = model.Favourite.gql('WHERE user=:1 AND item=:2', user, item).get()
if not favourite:
    favourite = model.Favourite(item=item,user=user)
    favourite.put()
    self.redirect('/item/%s' % item.url_path)
else:
    favourite.delete()
    self.redirect('/item/%s' % item.url_path)

Si no tiene el artículo en favoritos, se crea un objeto Favourite en la base de datos y se devuelve a la página del item. Si el item estaba en favoritos se borra y se redirecciona a la página del item.

Bien, hasta aquí la página es funcional y accesible. Pero la página sería más usable si no fuese necesario recargar la página simplemente para añadir el item a favoritos. Además, si el item fuese un vídeo, la recarga de la página provocaría que el vídeo se cortase y tuviese que comenzar de nuevo. Por ello voy a utilizar AJAX para que la página no tenga que recargarse. Pero no quiero que la página deje de poder utilizarse para un usuario que tenga javascript desactivado. Realizar primero una página accesible y funcional y posteriormente ir mejorando la usabilidad sin romper la funcionalidad y accesabilidad es una técnica conocida como: progressive enhancement.

Voy a capturar el evento onclick del enlace y en él haré una petición mediante AJAX. En el lado del servidor en vez de redireccionar mandaré un objeto JSON indicando la acción que se llevó a cabo: o bien se eliminó el favorito o se creó. Para que el servidor sepa si estamos haciendo la llamada desde javascript utilizaremos un parámetro más en la URL. El parámetro será "x". Le daremos un valor arbitrario ("1", por ejemplo) para que no sea simplemente una cadena vacía.

Este sería una posible solución javascript utilizando prototype:

function ajaxFav() {
    var fav = $('fav');
    if(fav == null) return;
    var parent = $(fav.parentNode);
    Element.observe(fav, 'click', function(e) {
        href = fav.href + '&x=1';
        parent.innerHTML = '<img class="icon" src="/static/images/spinner.gif" />';
        new Ajax.Request(href, {
            method: 'get',
            onSuccess: function(transport, json) {
                var action = transport.responseText.evalJSON().action;
                if(action == 'deleted') {
                    parent.innerHTML = '<a id="fav" href="/item.favourite?key={{ item.key }}"> Añadir a favoritos</a>';
                } else {
                    parent.innerHTML = '<a id="fav" href="/item.favourite?key={{ item.key }}"> Eliminar favorito</a>';
                }
                ajaxFav();
            },
            onFailure: function() {
                parent.innerHTML = 'Ocurrió un error :(';
            }
        });
        Event.stop(e);
    });
}
ajaxFav();

Es recomendable que el javascript esté al final de la página, antes de la etiqueta de cierre del elemento body. Tanto si se enlaza a un archivo externo (.js) como si se incluye tal cual.

He creado una función ajaxFav() que se llama justo después de definirla (última línea del ejemplo). Voy a explicar línea a línea lo que hace:

var fav = $('fav');

Obtiene el elemento DOM con id 'fav'. Esto es equivalente a:

var fav = document.getElementById('fav');

Con la salvedad de que como se puede ver, es más breve de escribir :). Como se puede ver en el HTML el elemento con id 'fav' será el enlace que el usuario clickará para añadir o quitar el item de favoritos.

var parent = $(fav.parentNode);

Obtiene el elemento padre del enlace. Como se puede ver en el HTML este es un elemento span. Este elemento se va a utilizar para reemplazar su contenido mientras la petición AJAX se procesa y para posteriormente cambiar el texto del enlace una vez procesada.

Element.observe(fav, 'click', function(e) { /* ... */ Event.stop(e); });

Esta es la forma de capturar un evento con prototype: con Element.observe(). El primer parámetro es el elemento DOM, el segundo es el nombre del evento y el tercer parámetro es la función que se ejecutará. Si no hiciéramos nada más, al clickar el enlace se ejecutaría la función y después seguiría la "ejecución normal" del enlace. Para evitar que el navegador vaya a la página indicada por el atributo "href" del enlace, se utiliza: Event.stop(e);

href = fav.href + '&x=1';

A la URL del enlace le añado un parámetro para que el servidor sepa si debe redireccionar (enlace clickado sin javascript) o si debe devolver un objeto JSON que ocurrirá cuando llamemos a la URL a través de AJAX.

parent.innerHTML = '<img src="/static/images/spinner.gif" />';

Sustituyo el HTML del objeto span para mostrar un GIF animado que indica que la petición está en proceso.

new Ajax.Request(href, {
    method: 'get',
    onSuccess: function(transport) {
        /* ... */
    },
    onFailure: function(transport) {
        /* ... */
    }
});

Ejecuta una llamada javascript a la dirección dada. La petición HTTP se hará con el método GET y se ejecutarán las funciones dadas en caso de éxito o fracaso respectivamente.

var action = transport.responseText.evalJSON().action;

En caso de éxito se obtiene el parámetro "action" del objeto JSON devuelto. Esperamos recibir alguno de los siguientes objetos:

{ 'action': 'added' }

{ 'action': 'deleted' }

Según el caso se modifica de una u otra forma el HTML del elemento span:

parent.innerHTML = '<a id="fav" ...';

En cualquier caso se vuelve a ejecutar la función ajaxFav(); para que se vuelva a añadir un listener al enlace que se acaba de crear al cambiar el HTML del elemento span.

En caso de error se muestra un mensaje de error.

Para enviar objetos JSON en caso de que la llamada sea a través de AJAX hay que cambiar el código del servidor. Asi quedaría en nuestro ejemplo:

# user es el usuario e item es el artículo que se quiere añadir a favoritos
favourite = model.Favourite.gql('WHERE user=:1 AND item=:2', user, item).get()
if not favourite:
    favourite = model.Favourite(item=item,user=user)
    favourite.put()
    if self.get_param('x'):
        self.render_json({ 'action': 'added' })
    else:
        self.redirect('/item/%s' % item.url_path)
else:
    favourite.delete()
    if self.get_param('x'):
        self.render_json({ 'action': 'deleted' })
    else:
        self.redirect('/item/%s' % item.url_path)
 

¡Votalo! 8 votos
¡Compártelo!

        

&nbps;

&nbps;

gimenete

Sobre gimenete

Gimenete es un tipo al que le encanta programar. Lleva media vida programando en Java, y ahora le da bastante también a Python. No le hace ascos a JavaScript. Su tema de investigación favorito ahora es el cloud computing.

 
Regístrate o haz login para participar.
¿Todavía no conoces debugmodeon?
debugmodeon es la red social para profesionales de la informática
descubre debugmodeon
 

2 comentarios en "Javascript no intrusivo con Prototype"

iagotb
iagotb escribió
hace 1 años

#1   

Un articulo muy directo y didactico, sigue así.

De todas formas revisa el articulo para que no aparezcan las etiquetas html (fuera de bloques de código) ya que la aplicación te lo modifica por: [HTML_REMOVED].

Un saludo

 

gimenete
gimenete escribió
hace 1 años

#2   

Ya he editado el artículo. Ya no aparecen los avisos de [HTML_REMOVED].

Muchas gracias! Y me alegro que haya sido de utilidad.

 
 
 
 

© Copyright 2008-2009 debug_mode=ON | Aviso legal | Contacto | FAQ | ¿Quiénes somos? |