0

I'm totally new to web-components and couldn't figure out which part has gone wrong.

I'm trying to create a web-component which includes a button, that supposed to show and hide a p tag. Below is the code and it's working fine.

class SpecialButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this._toShow = false;

    this.shadowRoot.innerHTML = `
    <style>
      p {
        display: none;
      }
    </style>
    <button>Show</button>
    <slot><p>default paragraph!</p></slot>
    `;
  }

  connectedCallback() {
    const button = this.shadowRoot.querySelector('button');
    const p = this.shadowRoot.querySelector('p');
    
    if (this.hasAttribute('toShow')) {
      this._toShow = this.getAttribute('toShow');

      if (this._toShow) {
        button.textContent = 'Hide';
        p.style.display = 'block';
        this._toShow = false;
      } else {
        button.textContent = 'Show';
        p.style.display = 'none';
        this._toShow = true;
      }
    }


    button.addEventListener('click', () => {
      if (this._toShow) {
        button.textContent = 'Hide';
        p.style.display = 'block';
        this._toShow = false;
      } else {
        button.textContent = 'Show';
        p.style.display = 'none';
        this._toShow = true;
      }
    });
  }
}

customElements.define('uc-button', SpecialButton);

If I dont pass anything to uc-button, it works great

<uc-button toShow="true"></uc-button>

The problem I'm facing now is I'm trying to pass a p as slot from my html

<uc-button toShow="true"><p>GGGGGGG</p></uc-button>

As a result, <p>GGGGGGG</p> seems like always showing and I'm not sure which part gone wrong. Am I understand slot wrongly, that it can only take in string but not HTML Node?

UPDATES I've created a reproducible demo @ https://codesandbox.io/s/inspiring-hill-evgol?file=/index.html

Isaac
  • 12,042
  • 16
  • 52
  • 116
  • Can you show me the line where you're passing `

    GGGGGG

    `?
    – HoldOffHunger Sep 21 '20 at 15:14
  • @HoldOffHunger: It's in `index.html`, basically the place where I try to render this `web-component` – Isaac Sep 21 '20 at 15:15
  • Hey, Isaac. I feel like I would like more code, if possible. `

    default paragraph!

    ` This works, right? Typically, from what your problem sounds like, you're doing `{var1}`, with `var1 = "

    GGGG

    "`, instead of the component syntax, of `var1 =

    GGGG

    ;`. But I'm not sure, because I don't see that part of the code. I can elaborate this into an answer if this is correct.
    – HoldOffHunger Sep 21 '20 at 15:18
  • @HoldOffHunger: I've created a demo @ https://codesandbox.io/s/inspiring-hill-evgol?file=/index.html – Isaac Sep 21 '20 at 15:26
  • @HoldOffHunger: I've placed two `uc-button`, where one is without wrapping any `p` tag while another is wrapping a `p` tag to overwrite default `slot` – Isaac Sep 21 '20 at 15:27

1 Answers1

1

On the connectedCallback method, you're getting the "button" and "p" elements from the shadowRoot. Those are the default ones. The one you pass when you use your component aren't inside the shadowRoot

<uc-button toShow="true"><p>GGGGGGG</p></uc-button>
connectedCallback() {
  const p = this.shadowRoot.querySelector("p");
  // this "p" isn't the one passed with the "GGGGGGG" textContent, but the one with "Default Paragraph"
}

The element you passed is inside the light DOM So you can get it querying the light dom:

const p = this.querySelector("p");

However, at the time the connectedCallback run, the elements from the light DOM hasn't been "moved" still (they aren't really moved, just rendered where the custom element <slot> is. Therefore, they are called distributed nodes). So you can't query them at this point.

So, when can you get those distributed nodes? The browser will tell you with the event fired at the shadowRoot onslotchange

You could listen to changes in slots and then apply the listeners.

connectedCallback() {
  // Notice that we bind the callback to "this" because otherwise it'll be shadowRoot,
  // and with the code below you would be still querying the shadowRoot.
  this.shadowRoot.addEventListener('slotchange', this.onSlotChange.bind(this));
}

onSlotChange() {
  const p = this.querySelector("p");
  // this is the passed "p"
}

nachoab
  • 1,908
  • 1
  • 23
  • 36
  • looks like you can actually query elements of the light DOM from either the `constructor` or the `connectedCallback` method if they are already present in the definition of a custom element when it is added in the page. The `onSlotChange` event will be **required** if that is not the case. – Darkosphere Feb 23 '23 at 21:49