Writing access methods for web components

by Erik Gillespie

HTML

<input id="game" placeholder="Ticket to Ride">
<div id="game-shelf">
  <game-entry name="Settlers of Catan"></game-entry>
</div>
<div class="game-total">
  Total games: <span id="game-count"></span>
</div>

<template id="game-entry-template">
  <div>
    <button id="remove">remove</button>
    <button id="rename">rename</button>
    <span id="name"></span>
  </div>
</template>

CSS

body {
  background-color: #222;
  color: white;
}

input {
  margin-bottom: 1rem;
}

.game-total {
  margin-top: 1rem;
  padding-top: 0.5rem;
  border-top: 1px solid white;
}

JavaScript

const gameInput = document.getElementById('game')
gameInput.addEventListener('keydown', event => {
  if (event.key === 'Enter') {
    const gameName = gameInput.value
    const gameEntry = new GameEntry()
    gameEntry.name = gameName
    const gameShelf = document.getElementById('game-shelf')
    gameShelf.append(gameEntry)
    gameInput.value = ''
    gameInput.focus()
  }
})

const gameShelf = document.getElementById('game-shelf')
gameShelf.addEventListener('game:add', () => {
  const gameCount = document.getElementById('game-count')
  gameCount.textContent = (parseInt(gameCount.textContent) || 0) + 1
})
gameShelf.addEventListener('game:remove', () => {
  const gameCount = document.getElementById('game-count')
  gameCount.textContent = (parseInt(gameCount.textContent) || 0) - 1
})

class GameEntry extends HTMLElement {
  constructor () {
    super()
    this.attachShadow({ mode: 'open' })
    const template = document.getElementById('game-entry-template')
    this.shadowRoot.append(template.content.cloneNode(true))
    const removeButton = this.shadowRoot.getElementById('remove')
    removeButton.addEventListener('click', () => this.remove())
    const renameButton = this.shadowRoot.getElementById('rename')
    renameButton.addEventListener('click', () => this.rename())
  }
  
  connectedCallback () {
    this.shelf = this.parentElement
    this.shelf.dispatchEvent(new CustomEvent('game:add'))
  }
  
  disconnectedCallback () {
    this.shelf.dispatchEvent(new CustomEvent('game:remove'))
  }
  
  static get observedAttributes () {
    return ['name']
  }
  
  attributeChangedCallback (name, oldValue, newValue) {
    if (name === 'name') {
      this.shadowRoot.getElementById('name').textContent = newValue
    }
  }
  
  get name () {
    return this.getAttribute('name')
  }
  
  set name (value) {
    if (value != null) {
      this.setAttribute('name', value)
    } else {
      this.removeAttribute('name')
    }
  }
  
  rename () {
    const name =...