0

I've created a custom element and defined it in html as follows: `

<header>
    <custom-navbar links="[{title:'home'},{title:'About'}]" class="bg-purple">
        <h1 slot="logo">
            Amazon
        </h1>
    
        <navbar  slot="link" class="navbar-wrapper">
            <ul  class="link-container">
               
            </ul>
        </navbar>
    </custom-navbar>
</header>

`

An I've defined my custom element as follows:

class CustomNavbar extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
    this.shadowRoot.innerHTML = `
    
        <style>
           :host(.bg-purple){
                display:flex;
                width:100%;
                background-color:purple !important;
           
            }

            .container{
                display:flex;
                flex-direction:row;
                margin:1rem;
                align-items:center;
            
            }
   
        </style>

        <div class='container'>
                <div>
                    <slot name="logo"></slot>
                </div>
                <slot name='link'></slot>
        </div>
    
    
    `;
    }

     connectedCallback() {
       this._links = this.getAttribute("links");
   
        }
    }

    customElements.define("custom-navbar", CustomNavbar);


I am looking for getting the links attribute defined in light DOM in shadow DOM and add the link to ul element? I've also use :document.querySelector("custom-navbar").querySelector("ul") inside of connectedCallback(), But it didn't work correctly.

amir tbi
  • 320
  • 2
  • 14
  • You can help us answering your question, by adding a [minimal-reproducible-example StackOverflow Snippet](https://stackoverflow.com/help/minimal-reproducible-example). It will help readers execute your code with one click. And help create answers with one click. Thank you. – Danny '365CSI' Engelman Aug 28 '23 at 10:07
  • @Danny'365CSI'Engelman, Sure, Here you can find the source of question.https://jsfiddle.net/amirtbi/bfu0o1dj/11/#&togetherjs=lo1Rcgn3Rf – amir tbi Aug 28 '23 at 10:37
  • To get all the nodes in your `slot`, you should select the slot and ask for it: `this.shadowRoot.querySelector( 'slot' ).assignedElements()` – somethinghere Aug 28 '23 at 11:07
  • @somethinghere, the found assignedElements is an empty array!. – amir tbi Aug 28 '23 at 11:33
  • `this.shadowRoot.querySelector( 'slot[name=link]' ).assignedElements()`, as the first `slot` in your dom is actually `name=logo`, which has no assigned elements here. – somethinghere Aug 28 '23 at 12:05
  • @somethinghere, It's an empty array!. I want to add some links like li elements to its parent (ul). when using this.shadowRoot.queryselector..., the result is just a slot with its name. it does not include anything. – amir tbi Aug 28 '23 at 12:36
  • 1
    If you have a child element with a `slot=link` attribute, you should be able to find it by finding the slot with that name and requesting it's `assignedElements()`, if there are none something else must be wrong. However, if you _run this in the `constructor`_, you will get `0` as at that point, the element isn't allowed to have children yet. Try checking for existing elements in `connectedCalback` _or_ by listening for `slotchange` events dispatched by your slot element (`this.shadowRoot.querySelector( 'slot[name=link]' ).addEventListener( 'slotchange', ... )` – somethinghere Aug 28 '23 at 12:40

2 Answers2

1

The connectedCallback fires on the opening tag; so there is no parsed lightDOM yet at this stage
(unless the CE is defined after DOM parsing)

So you either:

  • use setTimeout inside the connectedCallback to postpone quering for your <ul> tag in lightDOM

  • add a insertLI method on you component that checks/waits for the <ul> to exist

The setTimeout is totally valid unless you have a very LARGE lightDOM (like > 750 DOM nodes) then add HTML-parsed-element code

Note:

super();
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `...`

can be chained to:

super() // sets AND returns 'this' scope
    .attachShadow({ mode: "open" }) // sets AND returns this.shadowRoot
    .innerHTML = `...`
Danny '365CSI' Engelman
  • 16,526
  • 2
  • 32
  • 49
  • It's so tricky!. Apparently it is necessary to use something like setTimeout().!. But , what about very large light DOM? What is the best practice? – amir tbi Aug 28 '23 at 12:54
  • 1
    Use ``setTimeout`` if you have only a handfull of DOM nodes in lightDOM. it will give the browser enough time to parse all its lightDOM. This never failed me in tests with under 750 DOM nodes in lightDOM. All tools like Lit, Stencil etc. do this for you when they provide a **renderedCallback**. Also see: https://stackoverflow.com/questions/61971919/wait-for-element-upgrade-in-connectedcallback-firefox-and-chromium-differences – Danny '365CSI' Engelman Aug 28 '23 at 13:37
0

Try this code, it creates and populates a list of links dynamically within the shadow DOM by using the links attribute provided in the custom element's HTML.

class CustomNavbar extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
    this.shadowRoot.innerHTML = `
      <style>:host(.bg-purple){display:flex;width:100%;background-color:purple!important;}.container{display:flex;flex-direction:row;margin:1rem;align-items:center;}</style>
      <div class='container'><div><slot name="logo"></slot></div><slot name='link'></slot></div>
    `;
  }

  connectedCallback() {
    const links = JSON.parse(this.getAttribute("links") || '[]');
    const ulElement = this.shadowRoot.querySelector("ul.link-container");
    
    links.forEach(linkInfo => {
      const liElement = document.createElement("li");
      liElement.textContent = linkInfo.title;
      ulElement.appendChild(liElement);
    });
  }
}

customElements.define("custom-navbar", CustomNavbar);

Hope it works :)