Select row in grid within WebComponent failing

Hi

I’m trying to use a grid within a web component but I’m failing to select a clicked row. What am I doing wrong…?

I’m able to put a listener on the component, I can also catch the relevant event but when I try to set “this.grid.selectedItems = item ? [item]
: ;” I get “Uncaught TypeError: Cannot set property ‘selectedItems’ of undefined.”

Where am I going wrong!?

import { LitElement, html, css } from 'lit-element';
import '@vaadin/vaadin-lumo-styles';
import '@vaadin/vaadin-grid/vaadin-grid.js';
import '@vaadin/vaadin-grid/vaadin-grid-column.js';
import '@vaadin/vaadin-grid/vaadin-grid-filter-column.js';
import '@vaadin/vaadin-grid/vaadin-grid-selection-column.js';
import '@vaadin/vaadin-button/vaadin-button.js';
import '@polymer/paper-card';

class CardUnitsList extends LitElement {
  static get properties() {
    return {
      units: { type: Array },
      cardHeader: { type: String }
    };
  }

  constructor() {
    super();
    this.units = [];
    this.cardHeader = '';
    this.getUnits();
    this.grid;
    this.item;
  }

  firstUpdated(changedProperties) {
    this.grid = this.shadowRoot.getElementById('grid');
    this.grid.addEventListener('active-item-changed', this.onActiveItemChangedEvent);
  }

  updated(changedProperties) {
    changedProperties.forEach((oldValue, propName) => {
        //console.log(`${propName} changed. oldValue: ${oldValue}`);
        if (propName === 'units'){
          console.log('Prop units was updated!');
          this.grid.items = this.units;
          this.grid.recalculateColumnWidths();
        }
    });
  }

  onActiveItemChangedEvent(event) {
    console.log(event.path[0]
._focusedItemIndex);
    const item = event.detail.value;
    this.grid.selectedItems = item ? [item]
 : [];
  }

  doMagicThings(event) {
    this.grid.recalculateColumnWidths();
  }

  getUnits() {
    fetch('../../assets/units.json')
    .then(response => {
      return response.json();
    })
    .then(response => {
      //console.log(response);
      this.units = response;
    });
  }

  static get styles() {
    return css`
      #card {
        width: 800px;
        max-height:450px;
        padding-top: 0px;
        padding-left: 20px;
        padding-right: 20px;
        padding-bottom:20px;
        border-radius: 3px;
        margin: 5px;
      }

      #grid {
        max-height: 400px;
        font-size: 14px;
      }

      #grid vaadin-text-field {
        width: 100px;
      }
    `;
  }

  render() {
    return html`
      <paper-card id="card">
        <div style="padding-left: 15px;">
          <h2 style="color: rgb(110, 120, 130);">${this.cardHeader}</h2>
        </div>
        <div >

          <vaadin-grid id="grid" aria-label="Selection using Active Item Example">
            <vaadin-grid-column path="friendlyName"  header="Name"></vaadin-grid-column>
            <vaadin-grid-column path="modelName" header="Model"></vaadin-grid-column>
            <vaadin-grid-column path="chassiNumber" header="Chassi Number"></vaadin-grid-column>
            <vaadin-grid-column path="description" header="Description"></vaadin-grid-column>
          </vaadin-grid>

        </div>
        
      </paper-card>
    `;
  }
}

customElements.define('card-units-list', CardUnitsList);

“Uncaught TypeError: Cannot set property ‘selectedItems’ of undefined.”

This means that this.grid is undefined when you’re trying to set this.grid.selectedItems

The issue is that this code is in the event listener method onActiveItemChangedEvent(event) which is triggered by the active-item-changed event from <vaadin-grid>. If you set a breakpoint in the event you can see that in this context this is actually the <vaadin-grid> element because that’s the element where you’re listening for the event. In this case this is the same as event.target.

So here you could just just do this.selectedItems = item ? [item] : []; (because this is the grid).

Or alternatively you can force this in the event listener to always be your <card-units-list> element by using [bind]
(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind). A common way to do this is to override the method with a bound version by adding a line like this in your constructor: this.onActiveItemChangedEvent = this.onActiveItemChangedEvent.bind(this); (then you can use this.grid in the event listener).

Thanks a million!

Now I understand what is what and how things are related, greatly appreciated!

/Anton

Btw another common way to handle event listeners in modern JS code is using an [arrow function]
(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) as the event listener, since this is not bound in arrow functions (this doesn’t depend on how/where the arrow function is called) but you can just use the this from the current scope of where you define the function.

So another way to do this would be to do something like this in the firstUpdated:

this.grid.addEventListener('active-item-changed', event => {
  console.log(event.path[0]
._focusedItemIndex);
  this.selectItem(event.detail.value);
});

Or shorter without the console.log:

this.grid.addEventListener('active-item-changed', event => this.selectItem(event.detail.value));

And then have this method:

selectItem(item) {
  this.grid.selectedItems = item ? [item]
 : [];
}

Though this is more useful if you have more logic here. You could also just have this code directly in the arrow function if you like (but probably good idea to call a separate method if the code is longer).

So having all of it in one place you could just have:

this.grid.addEventListener('active-item-changed', event => {
  console.log(event.path[0]
._focusedItemIndex);
  const item = event.detail.value;
  this.grid.selectedItems = item ? [item]
 : [];
});

Your assistance is fantastic. I’m kind of new to the 20th century JS as you might have notice. This is so helpful, thanks once again!