Modificar una clase en Javascript: Eliminar los markers de Google Maps

Desde que empecé a jugar con javascript he ido aprendiendo muchas cosas nuevas, pero sin duda una de las más curiosas para alguien como yo, acostumbrado a lenguajes estáticos, ha sido la posibilidad de modificar las clases que ya existen.

En C#, si queremos cambiar el comportamiento de una clase tenemos disponibles varias técnicas, pero todas ellas implican crear una nueva clase:

  • Podemos crear una clase derivada de la clase existente y redefinir los métodos que queremos (siempre que la clase no sea sealed y los métodos sean virtual).
  • Podemos crear un decorador alrededor de la clase.

Sin embargo, en Javascript podemos cambiar no sólo el comportamiento de una clase ya definida, sino hasta el de un objeto ya creado.

El problema: eliminar todos los marcadores de un mapa

Para profundizar un poco en esto vamos a ver un ejemplo práctico: jugando con las APIs de Google Maps quería buscar una forma de eliminar todos los marcadores (markers) que había añadido sobre un mapa para indicar puntos de interés. Aparentemente esto era algo sencillo en la versión 2, pero no tan fácil en la versión 3.

Como siempre he pensado que es importante saber lo que uno está haciendo, en lugar de copiar el código a ciegas el código de StackOverflow, decidí tratar de entenderlo.

El API original de Google Maps es algo así:

var map = new google.maps.Map(...);

// Para añadir un marker:
var marker = new google.maps.Marker({
    map: map,
    position: new google.maps.LatLng(3, 17),
    icon: 'img/marker.png',
    title: '¡Hola!'
});

// Para eliminar el marker:
marker.setMap(null);

Básicamente, al crear un Marker le asignamos un Map y el Marker se pinta sobre él. Cuando queremos eliminarlo, establecemos el Map del Marker a null y ya está. Fácil.

El problema es que Map no almacena ninguna referencia a los Markers que se han pintado sobre él, por lo que no es posible eliminarlos a menos que mantengamos externamente una lista con todos los Markers que hemos ido añadiendo. Esa es una solución viable, pero queda feo. Podemos hacerlo mejor.

La solución: modificar las clases Marker y Map

Volvemos al principio del artículo, la modificación de clases en javascript, porque esa es justo la solución más elegante al problema. Vamos a modificar las clases Map y Marker para que entre ambas mantegan la lista de markers de forma interna y así podamos luego borrarlos limpiamente.

En javascript podemos modificar un objeto con sólo añadirle propiedades de cualquier tipo, y teniendo en cuenta que las funciones son un tipo más en javacript, eso quiere decir que podemos añadirle sin ningún problema nuevos métodos e incluso sobreescribir los que ya existen.

var lucas = {
    name: 'lucas',
    sing: function() {
        console.log('estoy cantando');
    },
};

lucas.dance= function() {
    console.log('estoy bailando');
}

// Ahora lucas sabe cantar y bailar
lucas.sing() 
lucas.dance()

Podemos añadir las propiedades a un objeto especial (el prototipo de la clase) para que la modificación afecte a todos los objetos de una clase (incluyendo los que ya se han creado y los que se crearán):

var Person = function(name) {
    this.name = name;
};

var lucas = new Person('lucas');
// Aquí lucas no sabe hacer nada todavía

Person.prototype.dance = function() {
    console.log('¡mira como bailo!);
};

// Ahora lucas ya sabe bailar
lucas.dance();

Con estos, ejem, profundos conocimientos de como funciona el prototipado en javascript, podemos lanzarnos a hacer nuestra modificación a Map y Marker:

(function() {
    // Añado a Map un array con los markers que contiene
    google.maps.Map.prototype.markers = new Array();

    // Añado a Map un método clearMakers que borrar los markers
    google.maps.Map.prototype.clearMarkers = function() {
        for (var i = 0; i < this.markers.length; i++) {
            this.markers[i].setMap(null);
        }
        this.markers = new Array();
	};

    // Reescribo el método setMap de Marker para que cuando se 
    // asigne el map se guarde en la propiedad markers del map
    // OJO: almaceno en oldSetMap el antiguo método setMap
    //      para poder seguir utilizándolo
    var oldSetMap = google.maps.Marker.prototype.setMap;
    google.maps.Marker.prototype.setMap = function(map) {
        if (map) {
            map.markers.push(this);
        }
        oldSetMap.call(this, map);
    }
})();

Creo que con los comentarios queda bastante claro lo que está pasando. Un par de cosas destacables:

  • Todo el código lo estamos ejecutando dentro de una función anónima que invocamos automáticamente: (function(){...})(). Esto lo hago porque quiero aprovechar el cierre (closure) sobre la variable oldSetMap y poder utilizarla sin contaminar el ámbito global.
  • Para invocar el antiguo setMap, hay que utilizar la función call() de javascript que nos permite invocar una función como si perteneciese a un objeto, en este caso a Marker.

Lo interesante

Para mi lo más interesante de este ejemplo no es cómo borrar los Markers (aunque en su momento era el problema que quería resolver), sino lo diferente que es la solución en javascript a la que hubiera sido en un lenguaje estático.

La idea de poder modificar "al vuelo" clases es una técnica muy poderosa, pero como decía el tío Ben: un gran poder conlleva una gran resposabilidad. Me da la impresión de que esta técnica hay que aplicarla con cuidado y cariño o puede causar caos y destrucción.

13 comentarios en “Modificar una clase en Javascript: Eliminar los markers de Google Maps

  1. Una consulta, yo tengo varios marker en un mapa, cuyas coordenadas son extraídas de una base de datos. Luego aplico un foreach para que todas la coordenadas se marquen automaticamente en el mapa. El punto es que me gustaría tener un botón para cada marker para ocultarlo o desocultarlo, no para ocultarlos a todos al mismo tiempo. Podrías ayudarme

  2. Una última pregunta, como podría ocultar todos los marcadores, menos uno?? Lo que pretendo es que en mi mapa aparezcan todos los marcadores al momento de cargar la página pero al hacer click en un botón que le corresponde a un marcador, ya que por todos los marcadores que tengo se genera un botón para cada uno, se oculten todos los demás menos el marcador que le corresponde al botón en el que hice click. Es lo último que te pregunto, me ayudarías muchísimo

  3. Como puedo mostrar una imagen de tipo blob guardada en mi BD y que se muestre dentro de mi globo que sale del marker?

  4. Gracias Me sirvio de Mucho esa explicación, soy nuevo en javascript pero mi mente estaba cerrada a los lenguajes habituales y por ende a programar en un esquema estatico. eres un capo.

Comentarios cerrados.