skip to Main Content

Hopefully the snippet is self explanatory, most of the (albeit rather broken) magic happens in conntectedCallback(). Essentially, if you type into the first text field at the very top you will see a string being printed to the console. But typing into the second editable text field (which unlike the first one is nested inside another webcomponent) nothing gets printed.

Any thoughts or pointers would be most appreciated.

<!DOCTYPE html>
<html>
<head>
    <script>
class HtmlGenMachinery extends HTMLElement {
    disconnectedCallback() {
        console.log("disconnectedCallback for '" + String(this.tagName) + "' element");
    }
    attributeChangedCallback() {
        console.log("attributeChangedCallback for '" + String(this.tagName) + "' element");
    }
    adoptedCallback() {
        console.log("adoptedCallback for '" + String(this.tagName) + "' element");
    }
    constructor() {
        super();
        console.log("constructor for '" + String(this.tagName) + "' element");
        this.attachShadow({ mode: "open" });
    }
    connectedCallback() {
        console.log("connectedCallback for '" + String(this.tagName) + "' element");
        setTimeout(() => {
            console.log("connectedCallback setTimeout lambda for '" + String(this.tagName) + "' element");

            // Parse html
            this.sourceHtml = this.sourceHtml
            .replaceAll('{shadowRoot}', '(el{id}.shadowRoot)')
            .replaceAll('¤%&/📍$', '`')
            .replaceAll('¤%&/🧭$', '</sc' + 'ript>')
            .replaceAll(`{content}`, this.innerHTML);
            if (this.sourceHtml.includes('el{id}')) {
                this.sourceHtml = "<script>var el{id}=document.getElementById('{id}');</sc" + "ript>" + this.sourceHtml;
            }
            for (const argStr of this.arguments) {
                if (Array.isArray(argStr)) {
                    this.sourceHtml = this.sourceHtml.replaceAll(`{${argStr[0]}}`, this.getAttribute(argStr[1]));
                } else {
                    this.sourceHtml = this.sourceHtml.replaceAll(`{${argStr}}`, this.getAttribute(argStr));
                }
            }

            const newContent = document.createRange().createContextualFragment(this.sourceHtml);
            this.shadowRoot.appendChild(newContent);
        }, 0)
    }
} 
class HtmlGen extends HtmlGenMachinery { sourceHtml = `{content}`; arguments = []; }
window.customElements.define('f-html-gen', HtmlGen);
class HtmlGen_Box extends HtmlGenMachinery { 
    sourceHtml = `<div style="background-color: {color}">{content}</div>`; arguments = ["color"]; 
}
window.customElements.define('f-html-gen-box', HtmlGen_Box);
class HtmlGen_StringProperty extends HtmlGenMachinery {
    sourceHtml = `<blockquote><p contenteditable=true id=propertyParagraph>{text}</p></blockquote>
<script type="module">
let el={shadowRoot}.getElementById("propertyParagraph");
console.log("howdy from {id} shadowRoot: " + String(Object.getOwnPropertyNames({shadowRoot})));        
setTimeout(()=>{
    console.log("Latent message from {id}");
},0)
el.addEventListener("input", function() {
    console.log("Contents of {id} is ->\n" + el.innerHTML + "<-\n");
    {changedCallback}
}, false);
¤%&/🧭$`; 
arguments = ["text", ["changedCallback","changed-callback"], "id"];
}
window.customElements.define('f-html-gen-string-property', HtmlGen_StringProperty);
class HtmlGen_Text extends HtmlGenMachinery { sourceHtml = `<p>{text}</p>`; arguments = ["text"]; }
window.customElements.define('f-html-gen-text', HtmlGen_Text);
    </script>
</head>

<body>
    <f-html-gen-string-property
    text="Latent functions here work just fine! To confirm this, try clicking on me and typing, then look in the console"
    id=f8d3c6b4c21754686 changed-callback="console.log('changed-callback');"></f-html-gen-string-property>
    <f-html-gen>
    <f-html-gen-box color=gray>
        <f-html-gen-text text="Lorum Ipsum"></f-html-gen-text>
    </f-html-gen-box>
    <f-html-gen-box color=gray>
        <f-html-gen-string-property
        text="However, when nested inside other webcomponents, latent functions stop working. Hence, if you try clicking on me and start tying, you wont see anything printed to the console!"
        id=dzd3b64lc13t56ki4 changed-callback="console.log('changed-callback');">
        </f-html-gen-string-property>
    </f-html-gen-box>
    </f-html-gen>
</body>
</html>

2

Answers


  1. Someone else can probably explain this better than I can, but hopefully this will get you on the right track.

    The Shadow DOM is hiding the innerHTML of your custom elements, which is why the nested elements aren’t working and aren’t visible.

    If you set the shadow DOM’s innerHTML to a <slot> element, it will make those inner elements visible.

    When you run the snippet, you’ll see that when you type into ether f-html-gen-string-property elements (they are pink) the text shows up in the console.

    See the EDIT comment in the code snippet for the edit details.

    f-html-gen-string-property {
      display: block;
    
      border: 2px solid black;
      background-color: #cab;
    }
    <!DOCTYPE html>
    <html>
    <head>
        <script>
    class HtmlGenMachinery extends HTMLElement {
        disconnectedCallback() {
        }
        attributeChangedCallback() {
        }
        adoptedCallback() {
        }
        constructor() {
            super();
            this.attachShadow({ mode: "open" });
        }
        connectedCallback() {
          // EDIT add this
          this.shadowRoot.innerHTML = "<slot></slot>";
          
            setTimeout(() => {
    
                // Parse html
                this.sourceHtml = this.sourceHtml
                .replaceAll('{shadowRoot}', '(el{id}.shadowRoot)')
                .replaceAll('¤%&/📍$', '`')
                .replaceAll('¤%&/🧭$', '</sc' + 'ript>')
                .replaceAll(`{content}`, this.innerHTML);
                if (this.sourceHtml.includes('el{id}')) {
                    this.sourceHtml = "<script>var el{id}=document.getElementById('{id}');</sc" + "ript>" + this.sourceHtml;
                }
                for (const argStr of this.arguments) {
                    if (Array.isArray(argStr)) {
                        this.sourceHtml = this.sourceHtml.replaceAll(`{${argStr[0]}}`, this.getAttribute(argStr[1]));
                    } else {
                        this.sourceHtml = this.sourceHtml.replaceAll(`{${argStr}}`, this.getAttribute(argStr));
                    }
                }
    
                const newContent = document.createRange().createContextualFragment(this.sourceHtml);
                this.shadowRoot.appendChild(newContent);
            }, 0)
        }
    } 
    class HtmlGen extends HtmlGenMachinery { sourceHtml = `{content}`; arguments = []; }
    window.customElements.define('f-html-gen', HtmlGen);
    class HtmlGen_Box extends HtmlGenMachinery { 
        sourceHtml = `<div style="background-color: {color}">{content}</div>`; arguments = ["color"]; 
    }
    window.customElements.define('f-html-gen-box', HtmlGen_Box);
    class HtmlGen_StringProperty extends HtmlGenMachinery {
        sourceHtml = `<blockquote><p contenteditable=true id=propertyParagraph>{text}</p></blockquote>
    <script type="module">
    let el={shadowRoot}.getElementById("propertyParagraph");
    //console.log("howdy from {id} shadowRoot: " + String(Object.getOwnPropertyNames({shadowRoot})));        
    setTimeout(()=>{
        // console.log("Latent message from {id}");
    },0)
    el.addEventListener("input", function() {
        console.log("Contents of {id} is ->\n" + el.innerHTML + "<-\n");
        {changedCallback}
    }, false);
    ¤%&/🧭$`; 
    arguments = ["text", ["changedCallback","changed-callback"], "id"];
    }
    window.customElements.define('f-html-gen-string-property', HtmlGen_StringProperty);
    class HtmlGen_Text extends HtmlGenMachinery { sourceHtml = `<p>{text}</p>`; arguments = ["text"]; }
    window.customElements.define('f-html-gen-text', HtmlGen_Text);
        </script>
    </head>
    
    <body>
        <f-html-gen-string-property
        text="Latent: Click on me and type"
        id=f8d3c6b4c21754686 changed-callback="console.log('changed-callback');"></f-html-gen-string-property>
        <f-html-gen>
        <f-html-gen-box color=gray>
            <f-html-gen-text text="Lorum Ipsum"></f-html-gen-text>
        </f-html-gen-box>
        <f-html-gen-box color=gray>
            <f-html-gen-string-property
            text="Nested: Click on me and type!"
            id=dzd3b64lc13t56ki4 changed-callback="console.log('changed-callback');">
            </f-html-gen-string-property>
        </f-html-gen-box>
        </f-html-gen>
    </body>
    </html>
    Login or Signup to reply.
  2. My guess: you have multiple ids of propertyParagraph, but inside you have access to unique IDs as they come in. The getElementById() will return the first one it finds. I don’t know how the shadow-roots or custom elements work, but suspect that this has something to do with the listeners only being attached to the first, but not the others.image showing what happens when you inspect elements in chrome on the full page, it shows at least two elements have an id of propertyParagraph therefore a non-unique ID look towards the top and bottom of the image to see it

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search