Styleable 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" part="button remove">remove</button>
<button id="rename" part="button rename">rename</button>
<span id="name" part="name"></span>
</div>
</template>
CSS
body {
background-color: #222;
color: white;
}
game-entry::part(button) {
font-size: 85%;
text-transform: uppercase;
border: none;
border-radius: 3px;
}
game-entry::part(remove) {
background-color: pink;
color: darkred;
}
game-entry::part(rename) {
background-color: lightblue;
color: darkblue;
}
game-entry::part(name) {
font-family: sans-serif;
font-style: italic;
}
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 =...