A web component that knows when it's on the DOM

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>

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.setAttribute('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 name = this.getAttribute('name')
    this.shadowRoot.innerHTML = `
      <div>
        <button>remove</button>
        <span>${name}</span>
      </div>
    `
    const button = this.shadowRoot.querySelector('button')
    button.addEventListener('click', () => this.remove())
  }
  
  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) {
    this.shadowRoot.querySelector('span').textContent = newValue
  }
}

customElements.define('game-entry', GameEntry)