Usa mapas de Leaflet en tus componentes LWC

Este artículo también está disponible en: English (Inglés)

Si guardas habitualmente la geolocalización de tus usuarios al crear nuevos leads, o utilizas servicios como GeoIP para tratar de ubicarlos, puedes utilizar Leaflet en tus componentes LWC para mostrar a los usuarios donde se encuentra el cliente.

Esto es muy útil para evitar que tus usuarios tengan que buscar en Google las ubicaciones concretas, y pueden utilizar este mapa para conocer la zona.

En este artículo, utilizaremos la Metadata API para configurar todos los elementos necesarios, así que manos a la obra.

Añadir la librería LeafletJS

Para empezar, necesitaremos la librería en sí que podemos añadir como una dependencia estática en Salesforce, para ello iremos a la página de descargas de LeafletJS, y descargaremos la última versión.

Añade el ZIP que acabamos de descargar a la carpeta staticresourcecode> y crea su fichero de manifiesto (Si tu fichero se llama leaflet.jscode>, el nombre del manifiesto debería ser leaflet.resource-meta.xmlcode>).

El manifiesto debe ser similar a lo siguiente, especificaremos que la cabecera Cache-Control debe ser pública para que pueda ser cacheada por el CDN de Salesforce para acelerar la descarga, y el Content-Type a application/zip, para permitir que Salesforce pueda descomprimir nuestra librería y ser capaz de utilizar su contenido.

<?xml version="1.0" encoding="UTF-8"?>
<StaticResource xmlns="<http://soap.sforce.com/2006/04/metadata>">
    <cacheControl>Public</cacheControl>
    <contentType>application/zip</contentType>
    <description>Leaflet 1.7.1</description>
</StaticResource>

Preparando el nuevo componente

Salesforce nos proporciona dos métodos para cargar librerías externas, siendo loadStylecode> y loadScriptcode> dentro de Platform Resource Loader, el cual se explica en el artículo: Use Third-Party JavaScript Libraries. Estos métodos siempre devuelven una promesa, por lo que podemos esperar hasta que se resuelva para utilizar la libreria.

Crearemos un nuevo componente al que llamaremos OpportunityMapcode> para mostrar el mapa. Crea los ficheros opportunityMap.jscode>, opportunityMap.htmlcode>, opportunityMap.csscode>, y su manifiesto, que debería ser como lo siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="<http://soap.sforce.com/2006/04/metadata>" fqn="sonneilDevelopmentAvailability">
    <apiVersion>49.0</apiVersion>
    <isExposed>true</isExposed>
    <masterLabel>Opportunity Map</masterLabel>
    <targets>
        <target>lightning__RecordPage</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__RecordPage">
            <objects>
                <object>Opportunity</object>
            </objects>
            <property name="height" type="Integer" min="50" required="true" default="200" label="Height (in pixels)"/>
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

En este fichero de manifiesto, configuramos nuestro componente para estar disponible en la página de detalles del objeto de Oportunidad, ya que necesitamos el identificador del registro para obtener la localización de nuestro cliente.

También configuramos una propiedad para poder definir la altura al insertar el componente, así podemos personalizarlo dependiendo de donde queramos añadirlo en la página.

En el fichero opportunityMap.html solo necesitaremos un div, que será donde añadiremos el mapa, por lo que puedes añadir lo siguiente:

<template>
    <div class="slds-box slds-theme_default"></div>
</template>

Añadiremos las clases slds-box y slds-theme_default para tener el mismo diseño que el resto de componentes. La primera aplicará los espaciados, bordes y esquinas redondeadas, y la segunda añadirá un fondo blanco al componente mientras carga nuestro mapa.

El código de nuestro componente

Si ahora probamos nuestro componente, solo veremos un rectángulo de 50px de alto, vamos a añadir nuestro código.

Primero, importamos nuestras dependencias:

import { api, LightningElement } from 'lwc';
import { loadScript, loadStyle } from 'lightning/platformResourceLoader';
import LEAFLET from '@salesforce/resourceUrl/leaflet';

Recuerda que al cargar LeafletJS, debemos usar el mismo nombre de fichero con el que lo subimos como recurso estático.

Empezaremos con la clase OpportunityMapcode>, añadiendo las propiedades recordIdcode> y heightcode>. La primera será gestionada por Salesforce, quien añadirá el ID de la Oportunidad actual, y la segunda tendrá el valor de la altura que habremos definido al añadir el componente a la página.

export default class InquiryMap extends LightningElement {
    @api recordId;
    @api height;
}

Ahora utilizaremos los métodos connectedCallbackcode> y renderedCallbackcode> para cargar la librería LeafletJS. Estos métodos son parte de los Lifecycle hooks. El primero se lanzará cuando el componente se inserte en el documento, mientras que renderedCallbackcode> se lanzará con cada renderizado del componente.

import { api, LightningElement } from 'lwc';
import { loadScript, loadStyle } from 'lightning/platformResourceLoader';
import LEAFLET from '@salesforce/resourceUrl/leaflet';

export default class InquiryMap extends LightningElement {
    @api recordId;
    @api height;

    renderedCallback() {
        this.template.querySelector('div').style.height = `${this.height}px`;
    }
		
    connectedCallback() {
        Promise.all([
            loadStyle(this, LEAFLET + '/leaflet.css'),
            loadScript(this, LEAFLET + '/leaflet.js'),
        ]).then(() => {
            // Leaflet should be ready, create a new draw method
            // this.draw();
        });
    }
}

Ahora, después de completar las promesas, podemos instanciar Leaflet, por lo que crearemos un nuevo método draw, donde activaremos nuestro mapa y añadiremos el punto. Utilizaremos los mapas de Open Street Map, y crearemos un nuevo marcador de ejemplo cerca de nuestras oficinas en Alicante (España).

draw() {
    let container = this.template.querySelector('div');
    let position = [38.341891109801594, -0.48035610027642234];
    let map = L.map(container, { scrollWheelZoom: false }).setView(position, 13);

    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '&copy; <a href="<https://www.openstreetmap.org/copyright>">OpenStreetMap</a> contributors',
    }).addTo(map);

    let marker = L.marker(position).addTo(map);
    let featureGroup = L.featureGroup([marker]).addTo(map);
    map.fitBounds(featureGroup.getBounds());
}

Ahora, si insertamos el componente en la página de Oportunidad, podremos ver algo similar a lo siguiente:

Añadiendo datos reales de la Oportunidad

En la sección anterior, añadimos un punto de ejemplo, pero ahora mostraremos la ubicación real de nuestro cliente. Para este ejemplo, suponemos que ya estás guardando la ubicación a mostrar, en nuestro caso, tenemos una propiedad Geolocation en nuestro objeto Oportunidad.

Puedes utilizar el servicio wire de Salesforce y getRecord para obtener el valor de este campo, pero en nuestro caso utilizaremos un nuevo controlador para devolver ésta, y otra información a mostrar en nuestro componente. Puedes mostrar otra información útil como la zona horaria y la hora local del cliente. Además se tratará de otra promesa al igual que las utilizadas para cargar la librería LeafletJS:

connectedCallback() {
    Promise.all([
        // ...
        getOppportunityMapData({ recordId: this.recordId }),
    ]).then(([st, sc, opportunity]) => {
        this.opportunity = opportunity;
        this.draw();
    });
}

Ahora, actualizaremos nuestro método draw:

draw() {
    // ...
    let position = [
        this.opportunity.location__latitude__s,
        this.opportunity.location__longitude__s
    ];
    // ...
}

El mapa se superpone a la barra de navegación

Dado que el valor de z-index por defecto del mapa es de 400, es posible que tu mapa esté encima de la barra de navegación. Pero es fácilmente solucionable configurando el valor de z-index en opportunityMap.csscode>.

div {
    z-index: 0
}

Esperamos que este artículo te resulte de utilidad para ayudar a tus usuarios de Salesforce =) Si tienes cualquier pregunta ¡no dudes en ponerla en los comentarios!