Skip to content

Contact Manager (In-Memory State)

Note: In Mobile Safari, the list of names is hidden and you'll need to click on the select box to see it. This is a known issue with Mobile Safari and is not a bug with Cami. I'll fix this minor issue in a future release.

HTML

<article>
  <contact-manager></contact-manager>
</article>
<script src="./build/cami.cdn.js"></script>
<!-- CDN version below -->
<!-- <script src="https://unpkg.com/cami@latest/build/cami.cdn.js"></script> -->
<style>
  .container {
    display: flex;
    flex-direction: column;
    width: 330px;
    margin: auto;
  }
  .input-group {
    margin-bottom: 10px;
  }
  .list-container {
    margin-bottom: 10px;
    height: 150px;
    overflow: auto;
    border: 1px solid gray;
  }
  select {
    overflow: -moz-scrollbars-none;
    -ms-overflow-style: none;
  }

  select::-webkit-scrollbar {
    width: 0;
    height: 0;
  }
  .button-group {
    display: flex;
    justify-content: space-between;
    gap: 10px;
  }
  .validation-error {
    border: 1px solid red;
    padding: 10px;
    position: relative;
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

  .close-btn {
    cursor: pointer;
  }
</style>
<script type="module">
  const { html, ReactiveElement } = cami;

  class NameManagerElement extends ReactiveElement {
    names = [{
      firstName: "Hans",
      lastName: "Zimmermann"
    }, {
      firstName: "Ruth",
      lastName: "Huber"
    }, {
      firstName: "Heidi",
      lastName: "Müller"
    }]
    prefix = '';
    selectedName = null;
    firstName = '';
    lastName = '';
    filteredNames = [];
    validationError = '';
    fadeOutClass = '';

    validateName() {
      if (!this.firstName || !this.lastName) {
        this.validationError = 'Both first name and last name must be provided.';
        return false;
      }
      this.validationError = '';
      return true;
    }

    addName() {
      if (!this.validateName()) {
        return;
      }
      this.names.push({ firstName: this.firstName, lastName: this.lastName });
      this.firstName = '';
      this.lastName = '';
    }

    updateName() {
      if (!this.validateName()) {
        return;
      }
      const index = this.names.findIndex(name => name.firstName === this.selectedName.firstName && name.lastName === this.selectedName.lastName);
      if (index !== -1) {
        this.names.splice(index, 1, { firstName: this.firstName, lastName: this.lastName });
        this.firstName = '';
        this.lastName = '';
      }
    }

    deleteName() {
      const index = this.names.findIndex(name => name === this.selectedName);
      if (index !== -1) {
        this.names.splice(index, 1);
        this.selectedName = null;
      }
    }

    get filterNames() {
      return this.names.filter(name => name.firstName.startsWith(this.prefix) || name.lastName.startsWith(this.prefix));
    }

    template() {
      return html`
        <div class="container">
          <div class="input-group">
            <label>Filter prefix:</label>
            <input type="text" @input=${(e) => { this.prefix = e.target.value; }} />
          </div>
          <label>Contacts:</label>
          <div class="list-container">
            <select size="8"
                    @change=${(e) => { this.selectedName = this.filterNames[e.target.selectedIndex]; }}
            >
              ${this.filterNames.map(name => html`<option>${name.firstName} ${name.lastName}</option>`)}
            </select>
          </div>
          <div class="input-group">
            <label>First Name:</label>
            <input type="text" .value=${this.firstName} @input=${(e) => { this.firstName = e.target.value; }} />
          </div>
          <div class="input-group">
            <label>Last Name:</label>
            <input type="text" .value=${this.lastName} @input=${(e) => { this.lastName = e.target.value; }} />
          </div>
          <div class="button-group">
            <button @click=${() => this.addName()}>Create</button>
            <button @click=${() => this.updateName()} ?disabled=${!this.selectedName}>Update</button>
            <button @click=${() => this.deleteName()} ?disabled=${!this.selectedName}>Delete</button>
          </div>
          ${this.validationError ? html`
            <div class="validation-error">
              <span>Notice: ${this.validationError}</span>
              <span class="close-btn" @click=${() => this.validationError = ''}>X</span>
            </div>
          ` : ''}
        </div>
      `;
    }
  }

  customElements.define('contact-manager', NameManagerElement);
</script>