Source: core/ArrayElement.js

import { LitElement, html } from 'lit';
import { EventType } from './EventType';

/**
 * @class ArrayElement
 *
 * This class is a store of arrays of objects of one type.
 *
 * @property {String} provider - The provider for data
 *
 * @example
 * <js-array provider="provider"></js-array>
 */
export class ArrayElement extends LitElement {
  #data = [];

  #newdata = [];

  #provider = null;

  static get localName() {
    return 'js-array';
  }

  static get properties() {
    return {
      provider: { type: String, reflect: true },
      select: { type: String, reflect: true },
    };
  }

  firstUpdated() {
    // Set up a listener for data changes
    this.addEventListener(EventType.CHANGE, () => {
      this.requestUpdate();
    });
  }

  render() {
    return html`<div>Array contains ${this.length} elements, provider=${this.provider}</div>`;
  }

  attributeChangedCallback(name, oldVal, newVal) {
    super.attributeChangedCallback(name, oldVal, newVal);
    if (name === 'provider') {
      this.#providerChanged(newVal, oldVal);
    }
  }

  get length() {
    return this.#data.length;
  }

  at(index) {
    return this.#data[index];
  }

  #providerChanged(newVal, oldVal) {
    if (oldVal != null && this.#provider && newVal !== oldVal) {
      this.#provider.removeEventListener(EventType.FETCH, this.#providerFetch.bind(this));
      this.#provider.removeEventListener(EventType.OBJECT, this.#providerObject.bind(this));
      this.#provider.removeEventListener(EventType.DONE, this.#providerDone.bind(this));
      this.#provider = null;
    }
    if (newVal != null && newVal !== oldVal) {
      this.#provider = document.querySelector(newVal);
      if (this.#provider) {
        this.#provider.addEventListener(EventType.FETCH, this.#providerFetch.bind(this));
        this.#provider.addEventListener(EventType.OBJECT, this.#providerObject.bind(this));
        this.#provider.addEventListener(EventType.DONE, this.#providerDone.bind(this));
      } else {
        throw new Error(`Provider "${newVal}" not found`);
      }
    }
  }

  #providerFetch() {
    // Reset the data container
    this.#newdata = [];
  }

  #providerObject(event) {
    let data = event.detail;
    if (this.select) {
      if (data instanceof Object) {
        data = data[this.select];
        if (data === undefined) {
          throw new Error(`Property "${this.select}" not found in object`);
        } else if (Array.isArray(data)) {
          this.#newdata = this.#newdata.concat(data);
        } else {
          this.#newdata.push(data);
        }
      }
    } else {
      // Add the object to the data container
      this.#newdata.push(data);
    }
  }

  #providerDone() {
    let modified = false;
    if (this.#newdata.length !== this.#data.length) {
      modified = true;
    } else {
      for (let i = 0; i < this.#newdata.length; i += 1) {
        if (this.#newdata[i] !== this.#data[i]) {
          modified = true;
          break;
        }
      }
    }

    // Copy over the data
    this.#data = this.#newdata;

    // Emit a change event if the data was modified
    if (modified) {
      this.dispatchEvent(new CustomEvent(EventType.CHANGE, {
        detail: this,
      }));
    }
  }
}