0

I have a SVG file as static resource. I'm using a LWC to display the image (floorplan) using an object tag in the template file. Need to load the floorplan based on user selection so I can't put the SVG inline in the template file. Is there a way to capture events started from within the SVG file outside of the SVG file at the component level?

I'm able to add events to the SVG file dynamically and handle the events within the context of the SVG file, but I can't seem to get it to bubble up outside of the SVG to the component level or to the parent. On click event fires and writes out the console messages but the event fired from the on click event doesn't seem to bubble up.

Component template file

<template>
    <div id="testID" class="diagram">
      <!-- map svg tried with and without lwc:dom="manual" -->
      <object type="image/svg+xml" data={svgURL} data-id="flrPlan" onload={handleLoad} ontestdiagram={handleTestDiagram} lwc:dom="manual"></object>
    </div>
</template>

Javascript file snippet

// Adds event handler to the SVG file - logs are written out upon clicking
 @api prepSpaces(spaceIds){
        console.log("diagram: prepping spaces...")

        const svgDoc = this.template.querySelector('[data-id="flrPlan"]').contentDocument;
        
        spaceIds.forEach( element => {
            let spaceId = element['Space_Public_Id__c'];
            const spaceElement = svgDoc.querySelector("[data-name=\"" + spaceId + "\"]");
            console.log("spaceElement: " + spaceElement);

            spaceElement.onclick = function highlightSpace()
            {
                // get id of this space
                let spaceId = this.dataset.name;
                console.log("spaceId         : " + spaceId);

                // get parent element
                let spaceParent = this.parentElement;
                let spaceParentId = spaceParent.dataset.name;
                console.log("spaceParentId   : " + spaceParentId);

                // unhighlight all children
                let spaceChildren = spaceParent.children;
                console.log("space children  : " + spaceChildren);
                console.log("size            : " + spaceChildren.length);
                for (let i = 0; i < spaceChildren.length; i++){
                    let child = spaceChildren[i];
                    let childId = child.dataset.name;
                    console.log("childId         : " + childId);
                    child.classList.remove("spaceSelected");
                    if (childId == spaceId){
                        console.log(childId + " == " + spaceId);
                        child.classList.add("spaceSelected");
                    }
                }

                // Issue seems to be here - doesn't send event back up to LWC
                this.dispatchEvent(new CustomEvent("testdiagram", {bubbles: true, detail: 'space clicked'}));
            };
            console.log("space element data" + JSON.stringify(spaceElement.data));
        });

        console.log("diagram: done prepping spaces")

    }

// Capture event fired from click event - never fires
handleTestDiagram(event){
        console.log("diagram: testdiagram()", event.detail);
    }

Any suggestions would be appreciated!

1 Answer 1

1

The issue is caused by the value of this inside a function: since you're in a class, the code runs in strict mode:

All parts of ECMAScript classes are strict mode code, including both class declarations and class expressions — and so also including all parts of class bodies.

You have used a function declaration. In strict mode the value of this is not set when entering an execution context, it remains as undefined. That's why this.dispatchEvent doesn't work (it throws an error).
Instead an arrow function

does not have its own this, arguments, super, or new.target

In order to fix it you could either change the function definition to an arrow function or use the bind() method.

Arrow function:
change spaceElement.onclick = function highlightSpace() to spaceElement.onclick = () =>

Bind:
change

    this.dispatchEvent(new CustomEvent("testdiagram", {bubbles: true, detail: 'space clicked'}));
});

to

    this.dispatchEvent(new CustomEvent("testdiagram", {bubbles: true, detail: 'space clicked'}));
}.bind(this));

Since you're using this for spaceId and spaceParent you should modify those lines, getting both properties via event.target, of course you have to define event as an input parameter of the arrow function. The whole forEach will look like:

spaceIds.forEach( element => {
    let spaceId = element['Space_Public_Id__c'];
    const spaceElement = svgDoc.querySelector("[data-name=\"" + spaceId + "\"]");
    console.log("spaceElement: " + spaceElement);

    spaceElement.onclick = (event) =>
    {
        // get id of this space
        let spaceId = event.target.dataset.name;
        console.log("spaceId         : " + spaceId);

        // get parent element
        let spaceParent = event.target.parentElement;
        let spaceParentId = spaceParent.dataset.name;
        console.log("spaceParentId   : " + spaceParentId);

        // unhighlight all children
        let spaceChildren = spaceParent.children;
        console.log("space children  : " + spaceChildren);
        console.log("size            : " + spaceChildren.length);
        for (let i = 0; i < spaceChildren.length; i++){
            let child = spaceChildren[i];
            let childId = child.dataset.name;
            console.log("childId         : " + childId);
            child.classList.remove("spaceSelected");
            if (childId == spaceId){
                console.log(childId + " == " + spaceId);
                child.classList.add("spaceSelected");
            }
        }

        this.dispatchEvent(new CustomEvent("testdiagram", {bubbles: true, detail: 'space clicked'}));
    };
    console.log("space element data" + JSON.stringify(spaceElement.data));
});
2
  • Thanks for the suggestion, It works. But now I get the following error: 'Accessing the global HTML property "dataset" is disabled.' I assume for the same reason, on the line that contains a reference to this ( let spaceId = this.dataset.name; ) I tried getElmentById and that doesn't seem to work either
    – Avalanchd
    Commented Aug 15, 2022 at 21:54
  • @Avalanchd I edited the answer adding a fix for spaceId and spaceParent
    – RubenDG
    Commented Aug 15, 2022 at 22:20

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .