Search code examples
javascripthtmlcustom-element

Custom HTML element is not rendered on the screen while successfully being rendered in the HTML tree


I created a custom HTML element:

class PieChart extends HTMLElement {
    constructor() {
        super();
    } 

    init() {
        this.#render();
    }

    #render() {
        const circleElement = document.createElement("circle");
        circleElement.setAttribute("cx", 100);
        circleElement.setAttribute("cy", 100);
        circleElement.setAttribute("r", 80);
        circleElement.setAttribute("fill", "none");
        circleElement.setAttribute("stroke", "green");
        circleElement.setAttribute("stroke-width", 40);
        circleElement.setAttribute("stroke-dasharray", `502.85 502.85`);

        const svgElement = document.createElement("svg");
        svgElement.style.display = "block";
        svgElement.style.width = "inherit";
        svgElement.style.height = "inherit";
        svgElement.setAttribute("viewBox", `0 0 200 200`);
        svgElement.append(circleElement);

        this.appendChild(svgElement);

        this.style.display  = "block";
        this.style.width = "200px";
        this.style.height = "200px";
    }

    connectedCallback() {
        const event = new CustomEvent("init", {
            bubbles: true
        });
        this.dispatchEvent(event);
    }
}

customElements.define(
    "pie-chart",
    PieChart
);
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
    <link rel="stylesheet" href="./css/site.css" />
</head>
<body>
    <p>Hello!</p>
    <pie-chart></pie-chart>
</body>
</html>
<script>
    document.body.addEventListener("init", (pEvent) => {
        const element = pEvent.target;
        element.init(this);
    });
</script>

This code is executed without any problems and the custom element is rendered in the DOM. However, the circle is not rendered on the screen.

enter image description here

PS: if I open DOM in a browser and click "Edit As HTML" on the PieChart element and change anything inside and save it, the circle appears. I used this pattern several times before and there were not problems, but this time is the first one when I use SVG element.


Solution

  • This is not really how you create a custom element. Create a shadow DOM in the connectedCallback, and the browser does the rest, including "activating" any custom elements that already exist on the page by the time you register their tags. No custom event dispatching, the custom element system was designed to take care of this for you.

    const createSVGElement = tag => document.createElementNS(`http://www.w3.org/2000/svg`, tag);
    
    class PieChart extends HTMLElement {
      constructor() {
        super();
      } 
    
      connectedCallback() {
        console.log(`let's go`);
        const shadow = this.attachShadow({ mode: "open" });
    
        const circleElement = createSVGElement("circle");
        circleElement.setAttribute("cx", 100);
        circleElement.setAttribute("cy", 100);
        circleElement.setAttribute("r", 80);
        circleElement.setAttribute("fill", "none");
        circleElement.setAttribute("stroke", "green");
        circleElement.setAttribute("stroke-width", 40);
        circleElement.setAttribute("stroke-dasharray", "502.85 502.85");
    
        const svgElement = createSVGElement("svg");
        svgElement.setAttribute("viewBox", "0 0 200 200");
        svgElement.append(circleElement);
    
        shadow.appendChild(svgElement);
          
        const style = document.createElement("style");
        style.textContent = `
          svg {
            display: block;
            width: 200px;
            height: 200px;
          }
        `;
        
        shadow.appendChild(style);
      }
    }
    
    customElements.define("pie-chart", PieChart);
    <pie-chart></pie-chart>

    And since you're using SVG: remember that even though SVG is "part of HTML" ever since HTML5, SVG elements on the JS side still require the use of document.createElementNS with the SVG namespace. It's super weird, super inconvenient, but we're stuck with it.