javascriptshopifynative-web-component

How to bind a delete method to a rendered web component


I am having trouble figuring out how to bind my handleDelete method.

The way I have it structured is the user clicks the plus button then data objects get stored in an array on the window object. Then render is called on the SelectedProducts component that renders a card that has the button I am trying to bind the handleDelete method to.

If you run the code you can see what I have so far.

Maybe this is not the right approach just trying to do it with out adding a library.

Still trying to wrap my head around which lifecycle methods I need or a custom event?

class SelectedProducts extends HTMLElement {
  constructor() {
    super();
    this.mockData = [
      { id: 1, name: 'name-1', qty: 1 },
      { id: 2, name: 'name-2', qty: 1 },
      { id: 2, name: 'name-1', qty: 2 },
    ];
  }
  handleDelete(e) {
    e.preventDefault();
    console.log('called handleDelete');
  }

  connectedCallback() {
    this.querySelector('button').addEventListener('click', this.handleDelete.bind(this));
  }

  attributeChangedCallback(attrName, oldValue, newValue) {
    console.log('attributeChange called');
    this.handleDelete(e);
  }

  static get observedAttributes() {
    return ['data-id'];
  }

  disconnectedCallback() {
    console.log('disconnectedCallback ran');
  }

  render() {
    this.mockData.forEach((item, index) => {
      this.innerHTML += `
      <div style="display:flex; align-items: center; background-color:white; padding:15px; ">
        <button data-id="${item.id}" class="delete-btn">
          Delete me
        </button>
      </div>
      `;
    });
  }

  handleDelete(e) {
    e.preventDefault();
    alert('Called Handle delete');
  }
}
customElements.define('selected-products', SelectedProducts);

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

    this.shadowRoot.innerHTML =
      `<style>
      ::slotted(div){
        color: #4B5563; 
        font-weight: 900; 
        text-align: center; 
        font-size: 20px; 
      }
      </style>
      ` +
      ` <div style="background: white; margin-right: 15px;">
          <slot name="button"></slot>
          <slot name="img"></slot>
        </div>
      `;
  }
}

customElements.define('product-card', ProductCard);

class SelectBtn extends HTMLElement {
  constructor() {
    super();
    // This is called with render below
    this.itemsPicked = document.querySelector('selected-products');
    this.attachShadow({
      mode: 'open',
    });
    this.shadowRoot.innerHTML = `
    <button
    aria-label="Select"
    type="button"
    class="pressed"
    data-addbtn="add-btn"
  >
   +
  </button>
    
    `;

    this.id = this.getAttribute('id');
    this.name = this.getAttribute('name');
    this.shadowRoot
      .querySelectorAll('button')
      .forEach((button) => button.addEventListener('click', this.handleSelect.bind(this)));
  }
  // Get data from attributes & store object in
  // an array on window object
  handleSelect(e) {
    e.preventDefault();
    this.itemsPicked.render();
  }
}

customElements.define('select-button', SelectBtn);
<body>
    <div style="display: flex; justify-content: center; align-items: center; background: lightblue; padding: 10px">
      <product-card>
        <div slot="button">
          <select-button id="1" name="product name"></select-button>
        </div>
        <div slot="img">
          <div style="height: 100px; width: 100px">Select Button</div>
        </div>
      </product-card>
      <div>
        <selected-products></selected-products>
      </div>
    </div>
    <script src="app.js"></script>
    <script>
      window.selectedItems = {
        items: [],
      };
    </script>
  </body>


Solution

  • The way I solved this problem was not to attach an event listener to inner html itself. Instead I created another web component (delete-button) with and event listener and a method to handle the event. Any feedback would be super cool.

    class DeleteButton extends HTMLElement {
      constructor() {
        super();
        this.selectedProducts = document.querySelector('selected-products');
        this.querySelectorAll('button').forEach((button) => button.addEventListener('click', this.handleDelete.bind(this)));
      }
    
      handleDelete(e) {
        e.preventDefault();
        alert('Delete button Event');
      }
    }
    customElements.define('delete-button', DeleteButton);
    
    class SelectedProducts extends HTMLElement {
      constructor() {
        super();
        this.mockData = [
          { id: 1, name: 'name-1', qty: 1 },
          { id: 2, name: 'name-2', qty: 1 },
          { id: 2, name: 'name-1', qty: 2 },
        ];
      }
    
      render() {
        this.mockData.forEach((item, index) => {
          this.innerHTML += `
          <div style="display:flex; align-items: center; background-color:white; padding:15px; ">
            <delete-button>
              <button data-id="${item.id}" class="delete-btn">
                Delete me
              </button>
            </delete-button>
          </div>
          `;
        });
      }
    
      handleDelete(e) {
        e.preventDefault();
        alert('Called Handle delete');
      }
    }
    customElements.define('selected-products', SelectedProducts);
    
    class ProductCard extends HTMLElement {
      constructor() {
        super();
        this.attachShadow({
          mode: 'open',
        });
    
        this.shadowRoot.innerHTML =
          `<style>
          ::slotted(div){
            color: #4B5563; 
            font-weight: 900; 
            text-align: center; 
            font-size: 20px; 
          }
          </style>
          ` +
          ` <div style="background: white; margin-right: 15px;">
              <slot name="button"></slot>
              <slot name="img"></slot>
            </div>
          `;
      }
    }
    
    customElements.define('product-card', ProductCard);
    
    class SelectBtn extends HTMLElement {
      constructor() {
        super();
        // This is called with render below
        this.itemsPicked = document.querySelector('selected-products');
        this.attachShadow({
          mode: 'open',
        });
        this.shadowRoot.innerHTML = `
        <button
        aria-label="Select"
        type="button"
        class="pressed"
        data-addbtn="add-btn"
      >
       +
      </button>
        
        `;
    
        this.id = this.getAttribute('id');
        this.name = this.getAttribute('name');
        this.shadowRoot
          .querySelectorAll('button')
          .forEach((button) => button.addEventListener('click', this.handleSelect.bind(this)));
      }
      // Get data from attributes & store object in
      // an array on window object
      handleSelect(e) {
        e.preventDefault();
        this.itemsPicked.render();
      }
    }
    
    customElements.define('select-button', SelectBtn);
    <body>
        <div style="display: flex; justify-content: center; align-items: center; background: lightblue; padding: 10px">
          <product-card>
            <div slot="button">
              <select-button id="1" name="product name"></select-button>
            </div>
            <div slot="img">
              <div style="height: 100px; width: 100px">Select Button</div>
            </div>
          </product-card>
          <div>
            <selected-products></selected-products>
          </div>
        </div>
        <script src="app.js"></script>
        <script>
          window.selectedItems = {
            items: [],
          };
        </script>
      </body>