Event handler on repeated element

299 Views Asked by At

I'm new to hyperHTML and am experimenting with it. Here's my question. How are event handlers added for repeated template elements. Here's my web component:

import css from './component.css';
import isJson from '../utils/isJson.js';
const hyper = hyperHTML;

class IdxAdminTab extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
  }

  attributeChangedCallback(name, oVal, nVal) {
    if (name === 'tabs' && isJson(nVal)) {
      this.tabs = JSON.parse(nVal);
      this.tabs.forEach((tab) => {
        tab.number = parseInt(tab.number, 10).toLocaleString();
      });
      this.updateView();
    }
  }

  clearSelections() {
    this.tabs.forEach((tab) => {
      tab.selected = false;
    });
  }

  connectedCallback() {
    this.updateView();
  }

  currentlySelected() {
    return this.tabs.find((tab) => tab.selected);
  }

  disconnectedCallback() {
    
  }

  tabSelected(evn) {
   
  }

  updateView() {
    hyper(this.shadowRoot)`
      <style>${css}</style>
      <div id="tab-container">
      ${this.tabs.map(tab => `
        <div class="${tab.selected ? 'selected' : ''}" id="${tab.id}" onclick="${this.selectedTab}">
          <div class="name">${tab.name}</div>
          <div class="number">${tab.number}</div>
        </div>
      `)}
      </div>
      `;
  }

  static get observedAttributes() {
    return ['tabs'];
  }
}

customElements.define('idx-admin-tab', IdxAdminTab);

export { IdxAdminTab };

I want to add a click handler in the repeated tab and register tabSelected as the handler. The click handler that I have added throws a Uncaught syntax error:

(function(event){[object HTMLElement]
})

2

There are 2 best solutions below

0
On BEST ANSWER

Your map returns plain strings, and so your click handler does not point to a function, but to a string representation of your function.

      ${this.tabs.map(tab => `
        <div class="${tab.selected ? 'selected' : ''}" id="${tab.id}" onclick="${this.selectedTab}">
          <div class="name">${tab.name}</div>
          <div class="number">${tab.number}</div>
        </div>
      `)}

Use

hyperHTML.wire(tab)`<div>...</div>`  

instead. CodePen

0
On

There are two little issues with your code:

  1. you are returning just a string as previously mentioned
  2. you are not binding your handler

When you use Template Literals it's easy to forget those are just strings. You need to return DOM nodes (or wires) that should point always at the same tab.

I've assumed tab.id are unique, and I've used them as identifiers bound to the component itself. This is a better version:

class IdxAdminTab extends HTMLElement {
  constructor() {
    super();
    this.selectedTab = this.selectedTab.bind(this);
    this.render = hyper(this.attachShadow({mode: 'open'}));
    this.updateView();
  }

  selectedTab(e) {
    console.log('tab selected');
  }

  updateView() {
    this.render`
      <style>${css}</style>
      <div id="tab-container">
      ${this.tabs.map(tab => hyper(this, `:${tab.id}`)`
        <div class="${tab.selected ? 'selected' : ''}" id="${tab.id}" onclick="${this.selectedTab}">
          <div class="name">${tab.name}</div>
          <div class="number">${tab.number}</div>
        </div>
      `)}
      </div>`;
  }

}

As you can see you could even use {mode: 'closed'} while assigning the shadowRoot once as render property.

Now you have a fully working component.

However, hyperHTML comes with a Custom Elements helper called HyperHTMLElement, have a look at its utilities, maybe it'll make your life easier.