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 seanvirtual
). - 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 variableoldSetMap
y poder utilizarla sin contaminar el ámbito global. - Para invocar el antiguo
setMap
, hay que utilizar la funcióncall()
de javascript que nos permite invocar una función como si perteneciese a un objeto, en este caso aMarker
.
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.
buen consejo, gracias me sirvio de mucho.
Saludos.
Gracias por tu aporte, me fue de mucha utilidad
muy buena informacion!!!
buena información me salvaste :)
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
Hola Renzo,
Para ocultar cada marker sólo necesitas hacer marker.setMap(null).
Un saludo,
Juanma
O sea, ese código me oculta solo un marker de entre todos?
Sí, de hecho el código que oculta todos los markers lo que hace es ejecutar ese código para cada marker que has ido añadiendo.
Muchas gracias Juanma
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
Al recorrer los marcadores, pon el mapa a null en todos excepto en el que quieres dejar visible.
Como puedo mostrar una imagen de tipo blob guardada en mi BD y que se muestre dentro de mi globo que sale del marker?
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.