0

So, I'm creating my first web component and I'm struggling with the event handling side with elements within the shadow root.

The goal with this component is to display a list of results from an endpoint where the user types in an input field and then grab the text from the selected element and display it on the same input field.

The HTMl is this:

<div class="results">
<ul>
<li class="result-item"><i></i><span>Amsterdam</span></li>
<li class="result-item"><i></i><span>Lisbon</span></li>
<li class="result-item"><i></i><span>Paris</span></li>
<li class="result-item"><i></i><span>London</span></li>
<li class="result-item"><i></i><span>Oslo</span></li>
<li class="result-item"><i></i><span>Copenhagen</span></li>
<li class="result-item"><i></i><span>Milan</span></li>
</ul>
</div>

I then query the result-item:

let resultItem = document.querySelector('destination-search').shadowRoot.querySelectorAll('.result-item');

And the function to get the selected value into the input:

function selectTextIntoInput(resultItem, searchInput, shadowRoot) {
    for (let i = 0; i < resultItem.length; i++) {
        resultItem[i].addEventListener("click", function (event) {
            searchInput.value = resultItem[i].innerText;

            const destinationEvent = new CustomEvent('destination-search', {
                detail: resultItem[i].innerText,
                bubbles: true,
                composed: true
            });

            shadowRoot.dispatchEvent(destinationEvent);
        });
    }
}

I've checked the console to see if I was querying the result-item fields and it's working fine. The problem is clicking each one ,select the text into the input and dispatch it as a custom event to be used by another component.

How do we handle click events within shadow root elements?

Fab
  • 145
  • 3
  • 20

2 Answers2

1

You need to use composed option in the event, and also need to use arrow function

like this below updated code:-

function selectTextIntoInput(resultItem, searchInput, shadowRoot) {
        for (let i = 0; i < resultItem.length; i++) {
            resultItem[i].addEventListener("click", (event) => {
                searchInput.value = resultItem[i].innerText;

                const destinationEvent = new CustomEvent('destination-search', {
                    detail: resultItem[i].innerText,
                    bubbles: true,
                    composed: true
                });

                this.dispatchEvent(destinationEvent);
            }, { composed: true });
        }
    }
Abhishek Garg
  • 207
  • 1
  • 5
0

Instead of <li class="result-item"><i></i><span>Amalfiküste</span></li>

Make it a Web Component <result-item>

And keep all (interaction) logic inside the Web Component

customElements.define("result-item",class extends HTMLElement{
  connectedCallback(){
    let input = this.closest("destination-search").querySelector("input");
    input.addEventListener("keyup", (evt) => {
      let match = this.innerText.includes(evt.target.value);
      this.closest("li").style.display = match ? "list-item" : "none";
    });
    this.onclick = (evt) => input.value = evt.target.innerText;  
    this.style.cursor = "pointer";
    //wait till lightDOM is parsed
    setTimeout(()=>this.innerHTML = `<b>${this.innerHTML}</b>`);
  }
});
body{ font:12px arial }
<destination-search>
  <input>
  <ul>
    <li><result-item>Amsterdam</result-item></li>
    <li><result-item>Rotterdam</result-item></li>
    <li><result-item>The Hague</result-item></li>
    <li><result-item>Utrecht</result-item></li>
    <li><result-item>Eindhoven</result-item></li>
    <li><result-item>Tilburg</result-item></li>
    <li><result-item>Groningen</result-item></li>
    <li><result-item>Almere</result-item></li>
    <li><result-item>Breda</result-item></li>
    <li><result-item>Nijmegen</result-item></li>
    <li><result-item>Haarlem</result-item></li>
    <li><result-item>Arnhem</result-item></li>
    <li><result-item>Zaanstad</result-item></li>
    <li><result-item>'s-Hertogenbosch</result-item></li>
    <li><result-item>Amersfoort</result-item></li>
    <li><result-item>Zierikzee</result-item></li>
  </ul>
</destination-search>
Danny '365CSI' Engelman
  • 16,526
  • 2
  • 32
  • 49