cancel
Showing results for 
Search instead for 
Did you mean: 

Custom Widget for mapbox

10-12-2022 3:42 PM
andrewbarlow1 Participant
846 views 3 comments Go to solution
0 Likes
SAP Managed Tags
Subscribe

Hi,

I was just throwing this out there to see if anybody had been able to successfully embed the mapbox js library inside a custom widget inside SAC?

I can load the library ok but when I instantiate the map through the constructor it fails to find the div which is defined at the beginning?

I was wondering if anybody had been able to successfully do this?

This is the Javascript I am using...

(function () {
    let template = document.createElement("template");
    template.innerHTML = `
        <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
        <link rel="stylesheet" href="https://api.mapbox.com/mapbox-gl-js/v2.10.0/mapbox-gl.css">
        <style>
            #map { 
                    width: 100%;
                    height:100%;
                }
        </style>
        <div id='map'></div>
`;
    class Map extends HTMLElement {
        constructor() {
            super();
            this.appendChild(template.content.cloneNode(true));
            this._props = {};
            let that = this;
            mapboxgl.accessToken = "OUR MAPBOX TOKEN";
            that._map = new mapboxgl.Map({
                container: "map",
                style: 'mapbox://styles/mapbox/streets-v11',
                center: [-74.5, 40],
                zoom: 9
            });
        }
        onCustomWidgetBeforeUpdate(changedProperties) {
            this._props = { ...this._props, ...changedProperties };
        }
        onCustomWidgetAfterUpdate(changedProperties) {
        }
    }

    let scriptSrc = "https://api.mapbox.com/mapbox-gl-js/v2.10.0/mapbox-gl.js";
    let onScriptLoaded = function () {
        customElements.define("com-test-map", Map);
    }

    let customElementScripts = window.sessionStorage.getItem("customElementScripts") || [];
    let scriptStatus = customElementScripts.find(function (element) {
       return element.src == scriptSrc;
    });

    if (scriptStatus) {
        if (scriptStatus.status == "ready") {
            onScriptLoaded();
        } else {
            scriptStatus.callbacks.push(onScriptLoaded);
        }
    } else {
        let scriptObject = {
            "src": scriptSrc,
            "status": "loading",
            "callbacks": [onScriptLoaded]
        }

        customElementScripts.push(scriptObject);
        var script = document.createElement("script");
        script.type = "text/javascript";
        script.src = scriptSrc;
        script.onload = function () {
            scriptObject.status = "ready";
            scriptObject.callbacks.forEach((callbackFn) => callbackFn.call());
        };
        document.head.appendChild(script);
       }
})();

0 Likes

Accepted Solutions (1)

Accepted Solutions (1)

Bob0001
Product and Topic Expert
Product and Topic Expert
0 Likes

I assume mapbox tries to find the target div by something like document.getElementById("map"). However at this point in time your custom element was not added to the DOM (see https://help.sap.com/docs/SAP_ANALYTICS_CLOUD/0ac8c6754ff84605a4372468d002f2bf/8b4c4c845b6643879e1bf.... You should defer the creation of the map to "connectedCallback" which is the point in time where the custom element was added to the DOM. You can check by calling getElementById from your constructor.

Also, do you want to allow your users to place more than one instance of this widget in an app 😉 ? If yes you should either give your map div a unique ID or use a shadow root (depends on whether mapbox supports this).

andrewbarlow1
Participant
0 Likes

You definitely put me on the right path Bob - thankyou very much...

(function () {
    let template = document.createElement("template");
    template.innerHTML = `
        <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
        <link rel="stylesheet" href="https://api.mapbox.com/mapbox-gl-js/v2.10.0/mapbox-gl.css">
        <style>
            #map{ 
                    width: 100%;
                    height:100%;
                }
        </style>
        <div id='map'></div>
`;

    class Map extends HTMLElement {
        constructor() {
            debugger
            super();
            this._shadowRoot = this.attachShadow({ mode: "open" });
            this.appendChild(template.content.cloneNode(true));
            this._props = {};
            this._firstConnection = false;
            //let that = this;
        }

        connectedCallback() {
            this._firstConnection = true;
            mapboxgl.accessToken = "THE TOKEN";
            this._map = new mapboxgl.Map({
                container: "map",
                style: 'mapbox://styles/mapbox/streets-v11',
                center: [-74.5, 40],
                zoom: 9
            });
        }

        onCustomWidgetBeforeUpdate(changedProperties) {
            this._props = { ...this._props, ...changedProperties };
        }
        onCustomWidgetAfterUpdate(changedProperties) {
        }
    }

    let scriptSrc = "https://api.mapbox.com/mapbox-gl-js/v2.10.0/mapbox-gl.js";
    let onScriptLoaded = function () {
        customElements.define("com-test-map", Map);
    }
    
    //SHARED FUNCTION: reuse between widgets
    //function(src, callback) {
    let customElementScripts = window.sessionStorage.getItem("customElementScripts") || [];

    let scriptStatus = customElementScripts.find(function (element) {
        return element.src == scriptSrc;
    });

    if (scriptStatus) {
        if (scriptStatus.status == "ready") {
            onScriptLoaded();
            // console.log("SCRIPT IS LOADED");
        } else {
            // console.log("SCRIPT NOT LOADED");
            scriptStatus.callbacks.push(onScriptLoaded);
        }
    } else {
        // debugger
        let scriptObject = {
            "src": scriptSrc,
            "status": "loading",
            "callbacks": [onScriptLoaded]
        }

        customElementScripts.push(scriptObject);
        var script = document.createElement("script");
        script.type = "text/javascript";
        script.src = scriptSrc;
        script.onload = function () {
            scriptObject.status = "ready";
            scriptObject.callbacks.forEach((callbackFn) => callbackFn.call());
        };
        document.head.appendChild(script);
    }

    // }, false);
})();

Answers (1)

Answers (1)

andrewbarlow1
Participant
0 Likes

Thanks for this Bob - I did initially try a document.getElementById("map") which returned a null inside the constructor so that rings true.

I had a look at the doc but I could do with an example of where and how to implement connectedCallback if possible?