Skip to content
Advertisement

Adding an event within a WebComponent

I want to create a Web Component. I use a ShadowDom and after generating a list I want to add a click Event to each element of the list. But I wonder how to access the list.

Neither the document nor the template shows me the items after a querySelect.

My question: How can I access the generated list within the webcomponent?

const news =
  {
    uk: [{
      id: 1,
      title: "News 1 UK",
      body: "lorem ipsum" 
    },
    {
      id: 2,
      title: "News 2 UK",
      body: "lorem ipsum" 
    },
    {
      id: 3,
      title: "News 3 UK",
      body: "lorem ipsum" 
    },
    {
      id: 4,
      title: "News 4 UK",
      body: "lorem ipsum" 
    },      
  ],
  de: [
    {
      id: 1,
      title: "News 1 DE",
      body: "lorem ipsum" 
    },
    {
      id: 2,
      title: "News 2 DE",
      body: "lorem ipsum" 
    },
    {
      id: 3,
      title: "News 3 DE",
      body: "lorem ipsum" 
    },
    {
      id: 4,
      title: "News 4 DE",
      body: "lorem ipsum" 
    }, 
  ]
  };

class MyNews {

  #limit = 10;
  #region = 'uk';
  news = [];
  
  constructor(conf = {}) {
    this.#limit = conf.limit ?? this.#limit;
    this.#region = conf.region ?? this.#region;
    this.news = news[this.#region]
  }

  
  showNews() {
    const items = this.news.slice(0,this.#limit);
    return items.map((n,i) => {
      return `<p>${i+1}. ${n.title}</p>`;
    }).join('');
  }
  
  getNewsData() {
    return this.news;
  }
}


class NewsTicker extends HTMLElement {
  
  constructor() {
    super();
    this.name = 'World News';
    this.limit = 10;
    this.region = "uk"
  }

  static get observedAttributes() {
    return ['name', 'url', 'limit', 'region'];
  }

  attributeChangedCallback(property, oldValue, newValue) {

    if (oldValue === newValue) return;
    this[ property ] = newValue;
    
  }

  async connectedCallback() {
    const options = {
      url: this.url, 
      limit: this.limit,
      region: this.region
    };
    const myNews = new MyNews(options);
    
    const 
      shadow = this.attachShadow({ mode: 'closed' }),
      template = document.getElementById('news-template').content.cloneNode(1),
      contextTitle = `Context ${ this.name } !`;

    template.querySelector('.news-context-title').textContent = contextTitle;
    template.querySelector('.news-list').innerHTML = myNews.showNews();
    shadow.append( template );
    
    const list = document.querySelector('.news-list');
    console.log("try to get list inner the template tag:", list)
  }
 
}

customElements.define( 'news-ticker', NewsTicker );
<news-ticker 
     name="News DE" 
     region="de" 
     limit="2">
</news-ticker>
<template id="news-template">
  <style>
    h2 {
      text-align: center;
      font-weight: normal;
      padding: 0.5em;
      margin: 1px 0;
      background-color: black;
      color: white;
      border: 1px solid #666;
      font-weight: bold;
    }
    
    .news-list > p {
      font-weight: normal;
      border: 1px solid #787878; 
      padding: 0.3em; 
      border-radius: 5px; 
      margin: 0.2em; 
      text-transform: capitalize; 
      text-align: left;
    }
    
    .news-list p:hover { 
      cursor: pointer; 
      background-color: #ffffd0; 
    }
  </style>

  <h2 class="news-context-title"></h2>
  <div class="news-list"></div>
</template>

<h1></h1>

Advertisement

Answer

You dont have access to the template over document. The template tag is a shadow DOM. You can make avaible for access the shadow DOM if you change the mode parameter to true: this.attachShadow({ mode: 'open' }). Then you can use this.shadowRoot.querySel.... Otherwise you can access directly over your shadow object (shadow = this.attachShadow({ mode: 'closed' })) with shadow.querySelector().

const news =
  {
    uk: [{
      id: 1,
      title: "News 1 UK",
      body: "lorem ipsum" 
    },
    {
      id: 2,
      title: "News 2 UK",
      body: "lorem ipsum" 
    },
    {
      id: 3,
      title: "News 3 UK",
      body: "lorem ipsum" 
    },
    {
      id: 4,
      title: "News 4 UK",
      body: "lorem ipsum" 
    },      
  ],
  de: [
    {
      id: 1,
      title: "News 1 DE",
      body: "lorem ipsum" 
    },
    {
      id: 2,
      title: "News 2 DE",
      body: "lorem ipsum" 
    },
    {
      id: 3,
      title: "News 3 DE",
      body: "lorem ipsum" 
    },
    {
      id: 4,
      title: "News 4 DE",
      body: "lorem ipsum" 
    }, 
  ]
  };

class MyNews {

  #limit = 10;
  #region = 'uk';
  news = [];
  
  constructor(conf = {}) {
    this.#limit = conf.limit ?? this.#limit;
    this.#region = conf.region ?? this.#region;
    this.news = news[this.#region]
  }

  
  showNews() {
    const items = this.news.slice(0,this.#limit);
    return items.map((n,i) => {
      return `<p>${i+1}. ${n.title}</p>`;
    }).join('');
  }
  
  getNewsData() {
    return this.news;
  }
}


class NewsTicker extends HTMLElement {
  
  constructor() {
    super();
    this.name = 'World News';
    this.limit = 10;
    this.region = "uk"
  }

  static get observedAttributes() {
    return ['name', 'url', 'limit', 'region'];
  }

  attributeChangedCallback(property, oldValue, newValue) {

    if (oldValue === newValue) return;
    this[ property ] = newValue;
    
  }

  async connectedCallback() {
    const options = {
      url: this.url, 
      limit: this.limit,
      region: this.region
    };
    const myNews = new MyNews(options);
    
    const 
      shadow = this.attachShadow({ mode: 'open' }), // change mode to open then you have access over the shadowRoot
      template = document.getElementById('news-template').content.cloneNode(1),
      contextTitle = `Context ${ this.name } !`;

    template.querySelector('.news-context-title').textContent = contextTitle;
    template.querySelector('.news-list').innerHTML = myNews.showNews();
    shadow.append( template );
    
    const list = document.querySelector('.news-list');
    const list_1 = shadow.querySelector('.news-list');
    const list_2 = this.shadowRoot.querySelector('.news-list');
    
    console.log("document.querySelector('.news-list') :", list_1)
    console.log("shadow.querySelector('.news-list') :", list_2);
    console.log("this.shadowRoot.querySelector('.news-list') :", list_3);
    
  }
 
}

customElements.define( 'news-ticker', NewsTicker );
<news-ticker 
     name="News DE" 
     region="de" 
     limit="2">
</news-ticker>
<template id="news-template">
  <style>
    h2 {
      text-align: center;
      font-weight: normal;
      padding: 0.5em;
      margin: 1px 0;
      background-color: black;
      color: white;
      border: 1px solid #666;
      font-weight: bold;
    }
    
    .news-list > p {
      font-weight: normal;
      border: 1px solid #787878; 
      padding: 0.3em; 
      border-radius: 5px; 
      margin: 0.2em; 
      text-transform: capitalize; 
      text-align: left;
    }
    
    .news-list p:hover { 
      cursor: pointer; 
      background-color: #ffffd0; 
    }
  </style>

  <h2 class="news-context-title"></h2>
  <div class="news-list"></div>
</template>
User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement