Using slots to automatically sync content
by Erik Gillespie
HTML
<input id="game" placeholder="Ticket to Ride">
<div id="game-shelf">
<game-entry>
<span slot="name">Settlers of Catan</span>
</game-entry>
</div>
<div class="game-total">
Total games: <span id="game-count"></span>
</div>
<template id="game-entry-template">
<div>
<button>remove</button>
<span><slot name="name"></slot></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.innerHTML = `<span slot="name">${gameName}</span>`
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 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'))
}
}
customElements.define('game-entry', GameEntry)