This is source code from the Teach Jenn Tech livestream. Learn more at https://gomakethings.com/teach-jenn-tech/.
Last active
June 3, 2023 04:38
-
-
Save cferdinandi/c2e9e7aab0439d655a3d31145a6ee13d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Dice</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<link rel="stylesheet" type="text/css" href="style-traditional.css"> | |
<style type="text/css"> | |
body { | |
margin: 0 auto; | |
max-width: 40em; | |
width: 88%; | |
} | |
#d6 { | |
--radius: 0; | |
} | |
#d20 { | |
--border-color: rebeccapurple; | |
--color: rebeccapurple; | |
--radius: 1em; | |
} | |
#d20:hover { | |
--bg-color: rebeccapurple; | |
--color: #ffffff; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Dice</h1> | |
<p> | |
<button class="roll" id="d6">🎲 Roll a D6</button> | |
</p> | |
<div class="roll-result" id="d6-result" aria-live="polite"></div> | |
<p> | |
<button class="roll" id="d20">🧙♂️ Roll a D20</button> | |
</p> | |
<div class="roll-result" id="d20-result" aria-live="polite"></div> | |
<script> | |
class RollDice { | |
/** | |
* Randomly shuffle an array | |
* https://stackoverflow.com/a/2450976/1293256 | |
* @param {Array} array The array to shuffle | |
* @return {Array} The shuffled array | |
*/ | |
static #shuffle (array) { | |
let currentIndex = array.length; | |
let temporaryValue, randomIndex; | |
// While there remain elements to shuffle... | |
while (0 !== currentIndex) { | |
// Pick a remaining element... | |
randomIndex = Math.floor(Math.random() * currentIndex); | |
currentIndex -= 1; | |
// And swap it with the current element. | |
temporaryValue = array[currentIndex]; | |
array[currentIndex] = array[randomIndex]; | |
array[randomIndex] = temporaryValue; | |
} | |
return array; | |
} | |
/** | |
* Create a range of numbers. | |
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#sequence_generator_range | |
* @param {Number} stop The last number in the range. | |
* @param {Number} start The first number in the range. | |
* @param {Number} step How much to increase each number by | |
* @returns {Array} A range of numbers. | |
*/ | |
static #range (stop, start = 1, step = 1) { | |
return Array.from( | |
{ length: (stop - start) / step + 1 }, | |
(_, i) => start + i * step | |
); | |
} | |
#message; | |
#dice; | |
#result; | |
#disabled; | |
/** | |
* The library constructor | |
* @param {String} button Button element selector | |
* @param {String} message Result element selector | |
* @param {Object} options Options and settings | |
*/ | |
constructor (button, message, options = {}) { | |
// Merge user options into defaults | |
let {size, result} = Object.assign({ | |
size: 6, | |
result: 'You rolled a {{roll}}' | |
}, options); | |
// Define instance properties | |
this.#message = message; | |
this.#dice = RollDice.#range(size); | |
this.#result = result; | |
this.#disabled = null; | |
// Get the button | |
let btn = document.querySelector(button); | |
if (!btn) return; | |
// Listen for clicks on it | |
let instance = this; | |
btn.addEventListener('click', function (event) { | |
instance.roll(); | |
}); | |
} | |
/** | |
* Roll the dice | |
*/ | |
roll () { | |
// If disabled, don't run | |
if (this.#disabled !== null) return; | |
// Get the aria-live region | |
let target = document.querySelector(this.#message); | |
if (!target) return; | |
// Inject the message into the UI | |
RollDice.#shuffle(this.#dice); | |
target.textContent = this.#result.replace('{{roll}}', this.#dice[0]); | |
} | |
/** | |
* Clear the result | |
*/ | |
clear () { | |
// Get the aria-live region | |
let target = document.querySelector(this.#message); | |
if (!target) return; | |
// Clear the UI | |
target.textContent = ''; | |
} | |
/** | |
* Disable dice rolls | |
*/ | |
stop (msg) { | |
// Set #disabled property | |
this.#disabled = msg; | |
// Get the aria-live region | |
let target = document.querySelector(this.#message); | |
if (!target) return; | |
// Update the UI | |
target.innerHTML = newValue === null ? '' : newValue; | |
} | |
/** | |
* Reenable dice rolls | |
*/ | |
start () { | |
// Set #disabled property | |
this.#disabled = null; | |
// Get the aria-live region | |
let target = document.querySelector(this.#message); | |
if (!target) return; | |
// Update the UI | |
target.innerHTML = ''; | |
} | |
} | |
let dice = new RollDice('#d6', '#d6-result'); | |
let d20 = new RollDice('#d20', '#d20-result', { | |
size: 20, | |
result: 'Result: {{roll}}' | |
}); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Dice</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<style type="text/css"> | |
body { | |
margin: 0 auto; | |
max-width: 40em; | |
width: 88%; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Dice</h1> | |
<roll-dice></roll-dice> | |
<script> | |
// Extend the HTMLElement class to create the web component | |
class RollDice extends HTMLElement { | |
/** | |
* The class constructor object | |
*/ | |
constructor () { | |
// Always call super first in constructor | |
super(); | |
// Render HTML | |
this.innerHTML = | |
`<p> | |
<button>Roll a D6</button> | |
</p> | |
<div aria-live="polite"></div>`; | |
} | |
/** | |
* Runs each time the element is appended to or moved in the DOM | |
*/ | |
connectedCallback () { | |
console.log('connected'); | |
} | |
/** | |
* Runs when the element is removed from the DOM | |
*/ | |
disconnectedCallback () { | |
console.log('disconnected'); | |
} | |
} | |
// Define the new web component | |
if ('customElements' in window) { | |
customElements.define('roll-dice', RollDice); | |
} | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Dice</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<style type="text/css"> | |
body { | |
margin: 0 auto; | |
max-width: 40em; | |
width: 88%; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Dice</h1> | |
<roll-dice></roll-dice> | |
<script> | |
// Extend the HTMLElement class to create the web component | |
class RollDice extends HTMLElement { | |
/** | |
* Randomly shuffle an array | |
* https://stackoverflow.com/a/2450976/1293256 | |
* @param {Array} array The array to shuffle | |
* @return {Array} The shuffled array | |
*/ | |
static #shuffle (array) { | |
let currentIndex = array.length; | |
let temporaryValue, randomIndex; | |
// While there remain elements to shuffle... | |
while (0 !== currentIndex) { | |
// Pick a remaining element... | |
randomIndex = Math.floor(Math.random() * currentIndex); | |
currentIndex -= 1; | |
// And swap it with the current element. | |
temporaryValue = array[currentIndex]; | |
array[currentIndex] = array[randomIndex]; | |
array[randomIndex] = temporaryValue; | |
} | |
return array; | |
} | |
/** | |
* The class constructor object | |
*/ | |
constructor () { | |
// Always call super first in constructor | |
super(); | |
// Store the dice values | |
this.dice = [1, 2, 3, 4, 5, 6]; | |
// Render HTML | |
this.innerHTML = | |
`<p> | |
<button>Roll a D6</button> | |
</p> | |
<div aria-live="polite"></div>`; | |
} | |
/** | |
* Runs each time the element is appended to or moved in the DOM | |
*/ | |
connectedCallback () { | |
// Get the button | |
let host = this; | |
let btn = this.querySelector('button'); | |
if (!btn) return; | |
// Listen for clicks on it | |
btn.addEventListener('click', function (event) { | |
// Get the aria-live region | |
let target = host.querySelector('[aria-live]'); | |
if (!target) return; | |
// Inject the message into the UI | |
RollDice.#shuffle(host.dice); | |
target.textContent = `You rolled a ${host.dice[0]}`; | |
}); | |
} | |
/** | |
* Runs when the element is removed from the DOM | |
*/ | |
disconnectedCallback () { | |
console.log('disconnected'); | |
} | |
} | |
// Define the new web component | |
if ('customElements' in window) { | |
customElements.define('roll-dice', RollDice); | |
} | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Dice</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<style type="text/css"> | |
body { | |
margin: 0 auto; | |
max-width: 40em; | |
width: 88%; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Dice</h1> | |
<roll-dice></roll-dice> | |
<script> | |
// Extend the HTMLElement class to create the web component | |
class RollDice extends HTMLElement { | |
/** | |
* Randomly shuffle an array | |
* https://stackoverflow.com/a/2450976/1293256 | |
* @param {Array} array The array to shuffle | |
* @return {Array} The shuffled array | |
*/ | |
static #shuffle (array) { | |
let currentIndex = array.length; | |
let temporaryValue, randomIndex; | |
// While there remain elements to shuffle... | |
while (0 !== currentIndex) { | |
// Pick a remaining element... | |
randomIndex = Math.floor(Math.random() * currentIndex); | |
currentIndex -= 1; | |
// And swap it with the current element. | |
temporaryValue = array[currentIndex]; | |
array[currentIndex] = array[randomIndex]; | |
array[randomIndex] = temporaryValue; | |
} | |
return array; | |
} | |
/** | |
* Create the event listener | |
* @param {Element} host The host element | |
* @return {Function} The event callback function | |
*/ | |
static #createHandler (host) { | |
return function (event) { | |
// Get the aria-live region | |
let target = host.querySelector('[aria-live]'); | |
if (!target) return; | |
// Inject the message into the UI | |
RollDice.#shuffle(host.dice); | |
target.textContent = `You rolled a ${host.dice[0]}`; | |
}; | |
} | |
/** | |
* The class constructor object | |
*/ | |
constructor () { | |
// Always call super first in constructor | |
super(); | |
// Store the dice values | |
this.dice = [1, 2, 3, 4, 5, 6]; | |
this.handler = RollDice.#createHandler(this); | |
// Render HTML | |
this.innerHTML = | |
`<p> | |
<button>Roll a D6</button> | |
</p> | |
<div aria-live="polite"></div>`; | |
} | |
/** | |
* Runs each time the element is appended to or moved in the DOM | |
*/ | |
connectedCallback () { | |
// Get the button | |
let btn = this.querySelector('button'); | |
if (!btn) return; | |
// Listen for clicks on it | |
btn.addEventListener('click', this.handler); | |
} | |
/** | |
* Runs when the element is removed from the DOM | |
*/ | |
disconnectedCallback () { | |
// Get the button | |
let btn = this.querySelector('button'); | |
if (!btn) return; | |
// Remove the event listener on it | |
btn.removeEventListener('click', this.handler); | |
} | |
} | |
// Define the new web component | |
if ('customElements' in window) { | |
customElements.define('roll-dice', RollDice); | |
} | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Dice</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<style type="text/css"> | |
body { | |
margin: 0 auto; | |
max-width: 40em; | |
width: 88%; | |
} | |
/** | |
* These can break the component | |
*/ | |
button { | |
pointer-events: none; | |
} | |
[aria-live] { | |
display: none; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Dice</h1> | |
<roll-dice></roll-dice> | |
<script> | |
// Extend the HTMLElement class to create the web component | |
class RollDice extends HTMLElement { | |
/** | |
* Randomly shuffle an array | |
* https://stackoverflow.com/a/2450976/1293256 | |
* @param {Array} array The array to shuffle | |
* @return {Array} The shuffled array | |
*/ | |
static #shuffle (array) { | |
let currentIndex = array.length; | |
let temporaryValue, randomIndex; | |
// While there remain elements to shuffle... | |
while (0 !== currentIndex) { | |
// Pick a remaining element... | |
randomIndex = Math.floor(Math.random() * currentIndex); | |
currentIndex -= 1; | |
// And swap it with the current element. | |
temporaryValue = array[currentIndex]; | |
array[currentIndex] = array[randomIndex]; | |
array[randomIndex] = temporaryValue; | |
} | |
return array; | |
} | |
/** | |
* Create the event listener | |
* @param {Element} host The host element | |
* @return {Function} The event callback function | |
*/ | |
static #createHandler (host) { | |
return function (event) { | |
// Get the aria-live region | |
let target = host.querySelector('[aria-live]'); | |
if (!target) return; | |
// Inject the message into the UI | |
RollDice.#shuffle(host.dice); | |
target.textContent = `You rolled a ${host.dice[0]}`; | |
}; | |
} | |
/** | |
* The class constructor object | |
*/ | |
constructor () { | |
// Always call super first in constructor | |
super(); | |
// Store the dice values | |
this.dice = [1, 2, 3, 4, 5, 6]; | |
this.handler = RollDice.#createHandler(this); | |
// Render HTML | |
this.innerHTML = | |
`<p> | |
<button>Roll a D6</button> | |
</p> | |
<div aria-live="polite"></div>`; | |
} | |
/** | |
* Runs each time the element is appended to or moved in the DOM | |
*/ | |
connectedCallback () { | |
// Get the button | |
let btn = this.querySelector('button'); | |
if (!btn) return; | |
// Listen for clicks on it | |
btn.addEventListener('click', this.handler); | |
} | |
/** | |
* Runs when the element is removed from the DOM | |
*/ | |
disconnectedCallback () { | |
// Get the button | |
let btn = this.querySelector('button'); | |
if (!btn) return; | |
// Remove the event listener on it | |
btn.removeEventListener('click', this.handler); | |
} | |
} | |
// Define the new web component | |
if ('customElements' in window) { | |
customElements.define('roll-dice', RollDice); | |
} | |
// Disable ARIA live region | |
let message = document.querySelector('[aria-live]'); | |
message.removeAttribute('aria-live'); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Dice</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<style type="text/css"> | |
body { | |
margin: 0 auto; | |
max-width: 40em; | |
width: 88%; | |
} | |
/** | |
* These can break the component | |
*/ | |
button { | |
pointer-events: none; | |
} | |
[aria-live] { | |
display: none; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Dice</h1> | |
<roll-dice></roll-dice> | |
<script> | |
// Extend the HTMLElement class to create the web component | |
class RollDice extends HTMLElement { | |
/** | |
* Randomly shuffle an array | |
* https://stackoverflow.com/a/2450976/1293256 | |
* @param {Array} array The array to shuffle | |
* @return {Array} The shuffled array | |
*/ | |
static #shuffle (array) { | |
let currentIndex = array.length; | |
let temporaryValue, randomIndex; | |
// While there remain elements to shuffle... | |
while (0 !== currentIndex) { | |
// Pick a remaining element... | |
randomIndex = Math.floor(Math.random() * currentIndex); | |
currentIndex -= 1; | |
// And swap it with the current element. | |
temporaryValue = array[currentIndex]; | |
array[currentIndex] = array[randomIndex]; | |
array[randomIndex] = temporaryValue; | |
} | |
return array; | |
} | |
/** | |
* Create the event listener | |
* @param {Element} host The host element | |
* @return {Function} The event callback function | |
*/ | |
static #createHandler (host) { | |
return function (event) { | |
// Get the aria-live region | |
let target = host.root.querySelector('[aria-live]'); | |
if (!target) return; | |
// Inject the message into the UI | |
RollDice.#shuffle(host.dice); | |
target.textContent = `You rolled a ${host.dice[0]}`; | |
}; | |
} | |
/** | |
* The class constructor object | |
*/ | |
constructor () { | |
// Always call super first in constructor | |
super(); | |
// Creates a shadow root | |
this.root = this.attachShadow({mode: 'closed'}); | |
// Store the dice values | |
this.dice = [1, 2, 3, 4, 5, 6]; | |
this.handler = RollDice.#createHandler(this); | |
// Render HTML | |
this.root.innerHTML = | |
`<p> | |
<button>Roll a D6</button> | |
</p> | |
<div aria-live="polite"></div>`; | |
} | |
/** | |
* Runs each time the element is appended to or moved in the DOM | |
*/ | |
connectedCallback () { | |
// Get the button | |
let btn = this.root.querySelector('button'); | |
if (!btn) return; | |
// Listen for clicks on it | |
btn.addEventListener('click', this.handler); | |
} | |
/** | |
* Runs when the element is removed from the DOM | |
*/ | |
disconnectedCallback () { | |
// Get the button | |
let btn = this.root.querySelector('button'); | |
if (!btn) return; | |
// Remove the event listener on it | |
btn.removeEventListener('click', this.handler); | |
} | |
} | |
// Define the new web component | |
if ('customElements' in window) { | |
customElements.define('roll-dice', RollDice); | |
} | |
// Disable ARIA live region | |
// let message = document.querySelector('[aria-live]'); | |
// message.removeAttribute('aria-live'); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Dice</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<style type="text/css"> | |
body { | |
margin: 0 auto; | |
max-width: 40em; | |
width: 88%; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Dice</h1> | |
<roll-dice></roll-dice> | |
<roll-dice> | |
<span slot="icon">🧙♂️</span> | |
</roll-dice> | |
<roll-dice> | |
<span slot="text">Toss some cubes</span> | |
</roll-dice> | |
<roll-dice> | |
<span slot="icon">🧙♂️</span> | |
<span slot="text">Cast a spell</span> | |
</roll-dice> | |
<script> | |
// Extend the HTMLElement class to create the web component | |
class RollDice extends HTMLElement { | |
/** | |
* Randomly shuffle an array | |
* https://stackoverflow.com/a/2450976/1293256 | |
* @param {Array} array The array to shuffle | |
* @return {Array} The shuffled array | |
*/ | |
static #shuffle (array) { | |
let currentIndex = array.length; | |
let temporaryValue, randomIndex; | |
// While there remain elements to shuffle... | |
while (0 !== currentIndex) { | |
// Pick a remaining element... | |
randomIndex = Math.floor(Math.random() * currentIndex); | |
currentIndex -= 1; | |
// And swap it with the current element. | |
temporaryValue = array[currentIndex]; | |
array[currentIndex] = array[randomIndex]; | |
array[randomIndex] = temporaryValue; | |
} | |
return array; | |
} | |
/** | |
* Create the event listener | |
* @param {Element} host The host element | |
* @return {Function} The event callback function | |
*/ | |
static #createHandler (host) { | |
return function (event) { | |
// Get the aria-live region | |
let target = host.root.querySelector('[aria-live]'); | |
if (!target) return; | |
// Inject the message into the UI | |
RollDice.#shuffle(host.dice); | |
target.textContent = `You rolled a ${host.dice[0]}`; | |
}; | |
} | |
/** | |
* The class constructor object | |
*/ | |
constructor () { | |
// Always call super first in constructor | |
super(); | |
// Creates a shadow root | |
this.root = this.attachShadow({mode: 'closed'}); | |
// Store the dice values | |
this.dice = [1, 2, 3, 4, 5, 6]; | |
this.handler = RollDice.#createHandler(this); | |
// Render HTML | |
this.root.innerHTML = | |
`<p> | |
<button><slot name="icon">🎲</slot> <slot name="text">Roll a D6</slot></button> | |
</p> | |
<div aria-live="polite"></div>`; | |
} | |
/** | |
* Runs each time the element is appended to or moved in the DOM | |
*/ | |
connectedCallback () { | |
// Get the button | |
let btn = this.root.querySelector('button'); | |
if (!btn) return; | |
// Listen for clicks on it | |
btn.addEventListener('click', this.handler); | |
} | |
/** | |
* Runs when the element is removed from the DOM | |
*/ | |
disconnectedCallback () { | |
// Get the button | |
let btn = this.root.querySelector('button'); | |
if (!btn) return; | |
// Remove the event listener on it | |
btn.removeEventListener('click', this.handler); | |
} | |
} | |
// Define the new web component | |
if ('customElements' in window) { | |
customElements.define('roll-dice', RollDice); | |
} | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Dice</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<style type="text/css"> | |
body { | |
margin: 0 auto; | |
max-width: 40em; | |
width: 88%; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Dice</h1> | |
<roll-dice></roll-dice> | |
<script> | |
// Extend the HTMLElement class to create the web component | |
class RollDice extends HTMLElement { | |
/** | |
* Randomly shuffle an array | |
* https://stackoverflow.com/a/2450976/1293256 | |
* @param {Array} array The array to shuffle | |
* @return {Array} The shuffled array | |
*/ | |
static #shuffle (array) { | |
let currentIndex = array.length; | |
let temporaryValue, randomIndex; | |
// While there remain elements to shuffle... | |
while (0 !== currentIndex) { | |
// Pick a remaining element... | |
randomIndex = Math.floor(Math.random() * currentIndex); | |
currentIndex -= 1; | |
// And swap it with the current element. | |
temporaryValue = array[currentIndex]; | |
array[currentIndex] = array[randomIndex]; | |
array[randomIndex] = temporaryValue; | |
} | |
return array; | |
} | |
/** | |
* Create the event listener | |
* @param {Element} host The host element | |
* @return {Function} The event callback function | |
*/ | |
static #createHandler (host) { | |
return function (event) { | |
// If disabled, don't run | |
if (host.hasAttribute('disabled')) return; | |
// Get the aria-live region | |
let target = host.root.querySelector('[aria-live]'); | |
if (!target) return; | |
// Inject the message into the UI | |
RollDice.#shuffle(host.dice); | |
target.textContent = `You rolled a ${host.dice[0]}`; | |
}; | |
} | |
/** | |
* The class constructor object | |
*/ | |
constructor () { | |
// Always call super first in constructor | |
super(); | |
// Creates a shadow root | |
this.root = this.attachShadow({mode: 'closed'}); | |
// Store the dice values | |
this.dice = [1, 2, 3, 4, 5, 6]; | |
this.handler = RollDice.#createHandler(this); | |
// Render HTML | |
this.root.innerHTML = | |
`<p> | |
<button><slot name="icon">🎲</slot> <slot name="text">Roll a D6</slot></button> | |
</p> | |
<div aria-live="polite"></div>`; | |
} | |
/** | |
* Runs each time the element is appended to or moved in the DOM | |
*/ | |
connectedCallback () { | |
// Get the button | |
let btn = this.root.querySelector('button'); | |
if (!btn) return; | |
// Listen for clicks on it | |
btn.addEventListener('click', this.handler); | |
} | |
/** | |
* Runs when the element is removed from the DOM | |
*/ | |
disconnectedCallback () { | |
// Get the button | |
let btn = this.root.querySelector('button'); | |
if (!btn) return; | |
// Remove the event listener on it | |
btn.removeEventListener('click', this.handler); | |
} | |
/** | |
* Create a list of attributes to observe | |
*/ | |
static get observedAttributes () { | |
return ['disabled']; | |
} | |
/** | |
* Runs when the value of an attribute is changed on the component | |
* @param {String} name The attribute name | |
* @param {String} oldValue The old attribute value | |
* @param {String} newValue The new attribute value | |
*/ | |
attributeChangedCallback (name, oldValue, newValue) { | |
// Log stuff | |
console.log('changed', name, oldValue, newValue, this); | |
// Get the aria-live region | |
let target = this.root.querySelector('[aria-live]'); | |
if (!target) return; | |
// Update the UI | |
target.innerHTML = newValue === null ? '' : newValue; | |
} | |
} | |
// Define the new web component | |
if ('customElements' in window) { | |
customElements.define('roll-dice', RollDice); | |
} | |
let dice = document.querySelector('roll-dice'); | |
// Nothing will happen here, because we're not watching this attribute | |
dice.setAttribute('hello', 'you'); | |
// logs "changed", "disabled", null, "You can't roll right now" | |
dice.setAttribute('disabled', `You can't roll right now`); | |
// logs "changed", "disabled", "You can't roll right now", null | |
// dice.removeAttribute('disabled'); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Dice</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<style type="text/css"> | |
body { | |
margin: 0 auto; | |
max-width: 40em; | |
width: 88%; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Dice</h1> | |
<roll-dice></roll-dice> | |
<roll-dice size="20" result="Result: {{roll}}"> | |
<span slot="text">Roll a D20</span> | |
</roll-dice> | |
<script> | |
// Extend the HTMLElement class to create the web component | |
class RollDice extends HTMLElement { | |
/** | |
* Randomly shuffle an array | |
* https://stackoverflow.com/a/2450976/1293256 | |
* @param {Array} array The array to shuffle | |
* @return {Array} The shuffled array | |
*/ | |
static #shuffle (array) { | |
let currentIndex = array.length; | |
let temporaryValue, randomIndex; | |
// While there remain elements to shuffle... | |
while (0 !== currentIndex) { | |
// Pick a remaining element... | |
randomIndex = Math.floor(Math.random() * currentIndex); | |
currentIndex -= 1; | |
// And swap it with the current element. | |
temporaryValue = array[currentIndex]; | |
array[currentIndex] = array[randomIndex]; | |
array[randomIndex] = temporaryValue; | |
} | |
return array; | |
} | |
/** | |
* Create the event listener | |
* @param {Element} host The host element | |
* @return {Function} The event callback function | |
*/ | |
static #createHandler (host) { | |
return function (event) { | |
// If disabled, don't run | |
if (host.hasAttribute('disabled')) return; | |
// Get the aria-live region | |
let target = host.root.querySelector('[aria-live]'); | |
if (!target) return; | |
// Inject the message into the UI | |
RollDice.#shuffle(host.dice); | |
target.textContent = host.result.replace('{{roll}}', host.dice[0]); | |
}; | |
} | |
/** | |
* Create a range of numbers. | |
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#sequence_generator_range | |
* @param {Number} stop The last number in the range. | |
* @param {Number} start The first number in the range. | |
* @param {Number} step How much to increase each number by | |
* @returns {Array} A range of numbers. | |
*/ | |
static #range (stop, start = 1, step = 1) { | |
return Array.from( | |
{ length: (stop - start) / step + 1 }, | |
(_, i) => start + i * step | |
); | |
} | |
/** | |
* The class constructor object | |
*/ | |
constructor () { | |
// Always call super first in constructor | |
super(); | |
// Creates a shadow root | |
this.root = this.attachShadow({mode: 'closed'}); | |
// Store the dice values | |
let size = this.getAttribute('size'); | |
this.dice = RollDice.#range(size ? parseFloat(size) : 6); | |
this.result = this.getAttribute('result') ?? 'You rolled a {{roll}}'; | |
this.handler = RollDice.#createHandler(this); | |
// Render HTML | |
this.root.innerHTML = | |
`<p> | |
<button><slot name="icon">🎲</slot> <slot name="text">Roll a D6</slot></button> | |
</p> | |
<div aria-live="polite"></div>`; | |
} | |
/** | |
* Runs each time the element is appended to or moved in the DOM | |
*/ | |
connectedCallback () { | |
// Get the button | |
let btn = this.root.querySelector('button'); | |
if (!btn) return; | |
// Listen for clicks on it | |
btn.addEventListener('click', this.handler); | |
} | |
/** | |
* Runs when the element is removed from the DOM | |
*/ | |
disconnectedCallback () { | |
// Get the button | |
let btn = this.root.querySelector('button'); | |
if (!btn) return; | |
// Remove the event listener on it | |
btn.removeEventListener('click', this.handler); | |
} | |
/** | |
* Create a list of attributes to observe | |
*/ | |
static get observedAttributes () { | |
return ['disabled']; | |
} | |
/** | |
* Runs when the value of an attribute is changed on the component | |
* @param {String} name The attribute name | |
* @param {String} oldValue The old attribute value | |
* @param {String} newValue The new attribute value | |
*/ | |
attributeChangedCallback (name, oldValue, newValue) { | |
// Get the aria-live region | |
let target = this.root.querySelector('[aria-live]'); | |
if (!target) return; | |
// Update the UI | |
target.innerHTML = newValue === null ? '' : newValue; | |
} | |
} | |
// Define the new web component | |
if ('customElements' in window) { | |
customElements.define('roll-dice', RollDice); | |
} | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Dice</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<style type="text/css"> | |
body { | |
margin: 0 auto; | |
max-width: 40em; | |
width: 88%; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Dice</h1> | |
<roll-dice></roll-dice> | |
<roll-dice size="20" result="Result: {{roll}}"> | |
<span slot="text">Roll a D20</span> | |
</roll-dice> | |
<script> | |
// Extend the HTMLElement class to create the web component | |
class RollDice extends HTMLElement { | |
/** | |
* Randomly shuffle an array | |
* https://stackoverflow.com/a/2450976/1293256 | |
* @param {Array} array The array to shuffle | |
* @return {Array} The shuffled array | |
*/ | |
static #shuffle (array) { | |
let currentIndex = array.length; | |
let temporaryValue, randomIndex; | |
// While there remain elements to shuffle... | |
while (0 !== currentIndex) { | |
// Pick a remaining element... | |
randomIndex = Math.floor(Math.random() * currentIndex); | |
currentIndex -= 1; | |
// And swap it with the current element. | |
temporaryValue = array[currentIndex]; | |
array[currentIndex] = array[randomIndex]; | |
array[randomIndex] = temporaryValue; | |
} | |
return array; | |
} | |
/** | |
* Create the event listener | |
* @param {Element} host The host element | |
* @return {Function} The event callback function | |
*/ | |
static #createHandler (host) { | |
return function (event) { | |
host.roll(); | |
}; | |
} | |
/** | |
* Create a range of numbers. | |
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#sequence_generator_range | |
* @param {Number} stop The last number in the range. | |
* @param {Number} start The first number in the range. | |
* @param {Number} step How much to increase each number by | |
* @returns {Array} A range of numbers. | |
*/ | |
static #range (stop, start = 1, step = 1) { | |
return Array.from( | |
{ length: (stop - start) / step + 1 }, | |
(_, i) => start + i * step | |
); | |
} | |
#dice; | |
#result; | |
#handler; | |
/** | |
* The class constructor object | |
*/ | |
constructor () { | |
// Always call super first in constructor | |
super(); | |
// Creates a shadow root | |
this.root = this.attachShadow({mode: 'closed'}); | |
// Store the dice values | |
let size = this.getAttribute('size'); | |
this.#dice = RollDice.#range(size ? parseFloat(size) : 6); | |
this.#result = this.getAttribute('result') ?? 'You rolled a {{roll}}'; | |
this.#handler = RollDice.#createHandler(this); | |
// Render HTML | |
this.root.innerHTML = | |
`<p> | |
<button><slot name="icon">🎲</slot> <slot name="text">Roll a D6</slot></button> | |
</p> | |
<div aria-live="polite"></div>`; | |
} | |
/** | |
* Roll the dice | |
*/ | |
roll () { | |
// If disabled, don't run | |
if (this.hasAttribute('disabled')) return; | |
// Get the aria-live region | |
let target = this.root.querySelector('[aria-live]'); | |
if (!target) return; | |
// Inject the message into the UI | |
RollDice.#shuffle(this.#dice); | |
target.textContent = this.#result.replace('{{roll}}', this.#dice[0]); | |
} | |
/** | |
* Clear the result | |
*/ | |
clear () { | |
// Get the aria-live region | |
let target = this.root.querySelector('[aria-live]'); | |
if (!target) return; | |
// Clear the UI | |
target.textContent = ''; | |
} | |
/** | |
* Runs each time the element is appended to or moved in the DOM | |
*/ | |
connectedCallback () { | |
// Get the button | |
let btn = this.root.querySelector('button'); | |
if (!btn) return; | |
// Listen for clicks on it | |
btn.addEventListener('click', this.#handler); | |
} | |
/** | |
* Runs when the element is removed from the DOM | |
*/ | |
disconnectedCallback () { | |
// Get the button | |
let btn = this.root.querySelector('button'); | |
if (!btn) return; | |
// Remove the event listener on it | |
btn.removeEventListener('click', this.#handler); | |
} | |
/** | |
* Create a list of attributes to observe | |
*/ | |
static get observedAttributes () { | |
return ['disabled']; | |
} | |
/** | |
* Runs when the value of an attribute is changed on the component | |
* @param {String} name The attribute name | |
* @param {String} oldValue The old attribute value | |
* @param {String} newValue The new attribute value | |
*/ | |
attributeChangedCallback (name, oldValue, newValue) { | |
// Get the aria-live region | |
let target = this.root.querySelector('[aria-live]'); | |
if (!target) return; | |
// Update the UI | |
target.innerHTML = newValue === null ? '' : newValue; | |
} | |
} | |
// Define the new web component | |
if ('customElements' in window) { | |
customElements.define('roll-dice', RollDice); | |
} | |
// Get the element | |
let dice = document.querySelector('roll-dice'); | |
dice.roll(); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Dice</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<style type="text/css"> | |
body { | |
margin: 0 auto; | |
max-width: 40em; | |
width: 88%; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Dice</h1> | |
<roll-dice></roll-dice> | |
<roll-dice size="20" result="Result: {{roll}}"> | |
<span slot="text">Roll a D20</span> | |
</roll-dice> | |
<script> | |
// Extend the HTMLElement class to create the web component | |
class RollDice extends HTMLElement { | |
/** | |
* Randomly shuffle an array | |
* https://stackoverflow.com/a/2450976/1293256 | |
* @param {Array} array The array to shuffle | |
* @return {Array} The shuffled array | |
*/ | |
static #shuffle (array) { | |
let currentIndex = array.length; | |
let temporaryValue, randomIndex; | |
// While there remain elements to shuffle... | |
while (0 !== currentIndex) { | |
// Pick a remaining element... | |
randomIndex = Math.floor(Math.random() * currentIndex); | |
currentIndex -= 1; | |
// And swap it with the current element. | |
temporaryValue = array[currentIndex]; | |
array[currentIndex] = array[randomIndex]; | |
array[randomIndex] = temporaryValue; | |
} | |
return array; | |
} | |
/** | |
* Create the event listener | |
* @param {Element} host The host element | |
* @return {Function} The event callback function | |
*/ | |
static #createHandler (host) { | |
return function (event) { | |
host.roll(); | |
}; | |
} | |
/** | |
* Create a range of numbers. | |
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#sequence_generator_range | |
* @param {Number} stop The last number in the range. | |
* @param {Number} start The first number in the range. | |
* @param {Number} step How much to increase each number by | |
* @returns {Array} A range of numbers. | |
*/ | |
static #range (stop, start = 1, step = 1) { | |
return Array.from( | |
{ length: (stop - start) / step + 1 }, | |
(_, i) => start + i * step | |
); | |
} | |
#dice; | |
#result; | |
#handler; | |
/** | |
* The class constructor object | |
*/ | |
constructor () { | |
// Always call super first in constructor | |
super(); | |
// Creates a shadow root | |
this.root = this.attachShadow({mode: 'closed'}); | |
// Store the dice values | |
let size = this.getAttribute('size'); | |
this.#dice = RollDice.#range(size ? parseFloat(size) : 6); | |
this.#result = this.getAttribute('result') ?? 'You rolled a {{roll}}'; | |
this.#handler = RollDice.#createHandler(this); | |
// Render HTML | |
this.root.innerHTML = | |
`<style> | |
button { | |
background-color: #f5f5f5; | |
border: 1px solid #0088cc; | |
border-radius: 0.25em; | |
color: #0088cc; | |
font-size: 1.5em; | |
padding: 0.5em 1em; | |
} | |
button:hover { | |
background-color: #0088cc; | |
color: #ffffff; | |
} | |
</style> | |
<p> | |
<button><slot name="icon">🎲</slot> <slot name="text">Roll a D6</slot></button> | |
</p> | |
<div aria-live="polite"></div>`; | |
} | |
/** | |
* Roll the dice | |
*/ | |
roll () { | |
// If disabled, don't run | |
if (this.hasAttribute('disabled')) return; | |
// Get the aria-live region | |
let target = this.root.querySelector('[aria-live]'); | |
if (!target) return; | |
// Inject the message into the UI | |
RollDice.#shuffle(this.#dice); | |
target.textContent = this.#result.replace('{{roll}}', this.#dice[0]); | |
} | |
/** | |
* Clear the result | |
*/ | |
clear () { | |
// Get the aria-live region | |
let target = this.root.querySelector('[aria-live]'); | |
if (!target) return; | |
// Clear the UI | |
target.textContent = ''; | |
} | |
/** | |
* Runs each time the element is appended to or moved in the DOM | |
*/ | |
connectedCallback () { | |
// Get the button | |
let btn = this.root.querySelector('button'); | |
if (!btn) return; | |
// Listen for clicks on it | |
btn.addEventListener('click', this.#handler); | |
} | |
/** | |
* Runs when the element is removed from the DOM | |
*/ | |
disconnectedCallback () { | |
// Get the button | |
let btn = this.root.querySelector('button'); | |
if (!btn) return; | |
// Remove the event listener on it | |
btn.removeEventListener('click', this.#handler); | |
} | |
/** | |
* Create a list of attributes to observe | |
*/ | |
static get observedAttributes () { | |
return ['disabled']; | |
} | |
/** | |
* Runs when the value of an attribute is changed on the component | |
* @param {String} name The attribute name | |
* @param {String} oldValue The old attribute value | |
* @param {String} newValue The new attribute value | |
*/ | |
attributeChangedCallback (name, oldValue, newValue) { | |
// Get the aria-live region | |
let target = this.root.querySelector('[aria-live]'); | |
if (!target) return; | |
// Update the UI | |
target.innerHTML = newValue === null ? '' : newValue; | |
} | |
} | |
// Define the new web component | |
if ('customElements' in window) { | |
customElements.define('roll-dice', RollDice); | |
} | |
// Get the element | |
let dice = document.querySelector('roll-dice'); | |
dice.roll(); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Dice</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<style type="text/css"> | |
body { | |
margin: 0 auto; | |
max-width: 40em; | |
width: 88%; | |
} | |
roll-dice { | |
--radius: 0; | |
} | |
roll-dice[size="20"] { | |
--border-color: rebeccapurple; | |
--color: rebeccapurple; | |
--radius: 1em; | |
} | |
roll-dice[size="20"]:hover { | |
--bg-color: rebeccapurple; | |
--color: #ffffff; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Dice</h1> | |
<roll-dice></roll-dice> | |
<roll-dice size="20" result="Result: {{roll}}"> | |
<span slot="text">Roll a D20</span> | |
</roll-dice> | |
<script> | |
// Extend the HTMLElement class to create the web component | |
class RollDice extends HTMLElement { | |
/** | |
* Randomly shuffle an array | |
* https://stackoverflow.com/a/2450976/1293256 | |
* @param {Array} array The array to shuffle | |
* @return {Array} The shuffled array | |
*/ | |
static #shuffle (array) { | |
let currentIndex = array.length; | |
let temporaryValue, randomIndex; | |
// While there remain elements to shuffle... | |
while (0 !== currentIndex) { | |
// Pick a remaining element... | |
randomIndex = Math.floor(Math.random() * currentIndex); | |
currentIndex -= 1; | |
// And swap it with the current element. | |
temporaryValue = array[currentIndex]; | |
array[currentIndex] = array[randomIndex]; | |
array[randomIndex] = temporaryValue; | |
} | |
return array; | |
} | |
/** | |
* Create the event listener | |
* @param {Element} host The host element | |
* @return {Function} The event callback function | |
*/ | |
static #createHandler (host) { | |
return function (event) { | |
host.roll(); | |
}; | |
} | |
/** | |
* Create a range of numbers. | |
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#sequence_generator_range | |
* @param {Number} stop The last number in the range. | |
* @param {Number} start The first number in the range. | |
* @param {Number} step How much to increase each number by | |
* @returns {Array} A range of numbers. | |
*/ | |
static #range (stop, start = 1, step = 1) { | |
return Array.from( | |
{ length: (stop - start) / step + 1 }, | |
(_, i) => start + i * step | |
); | |
} | |
#dice; | |
#result; | |
#handler; | |
/** | |
* The class constructor object | |
*/ | |
constructor () { | |
// Always call super first in constructor | |
super(); | |
// Creates a shadow root | |
this.root = this.attachShadow({mode: 'closed'}); | |
// Store the dice values | |
let size = this.getAttribute('size'); | |
this.#dice = RollDice.#range(size ? parseFloat(size) : 6); | |
this.#result = this.getAttribute('result') ?? 'You rolled a {{roll}}'; | |
this.#handler = RollDice.#createHandler(this); | |
// Render HTML | |
this.root.innerHTML = | |
`<style> | |
button { | |
background-color: var(--bg-color, #f5f5f5); | |
border: 1px solid var(--border-color, #0088cc); | |
border-radius: var(--radius, 0.25em); | |
color: var(--color, #0088cc); | |
font-size: var(--size, 1.5em); | |
padding: 0.5em 1em; | |
} | |
button:hover { | |
background-color: var(--bg-color, #0088cc); | |
color: var(--color, #ffffff); | |
} | |
</style> | |
<p> | |
<button><slot name="icon">🎲</slot> <slot name="text">Roll a D6</slot></button> | |
</p> | |
<div aria-live="polite"></div>`; | |
} | |
/** | |
* Roll the dice | |
*/ | |
roll () { | |
// If disabled, don't run | |
if (this.hasAttribute('disabled')) return; | |
// Get the aria-live region | |
let target = this.root.querySelector('[aria-live]'); | |
if (!target) return; | |
// Inject the message into the UI | |
RollDice.#shuffle(this.#dice); | |
target.textContent = this.#result.replace('{{roll}}', this.#dice[0]); | |
} | |
/** | |
* Clear the result | |
*/ | |
clear () { | |
// Get the aria-live region | |
let target = this.root.querySelector('[aria-live]'); | |
if (!target) return; | |
// Clear the UI | |
target.textContent = ''; | |
} | |
/** | |
* Runs each time the element is appended to or moved in the DOM | |
*/ | |
connectedCallback () { | |
// Get the button | |
let btn = this.root.querySelector('button'); | |
if (!btn) return; | |
// Listen for clicks on it | |
btn.addEventListener('click', this.#handler); | |
} | |
/** | |
* Runs when the element is removed from the DOM | |
*/ | |
disconnectedCallback () { | |
// Get the button | |
let btn = this.root.querySelector('button'); | |
if (!btn) return; | |
// Remove the event listener on it | |
btn.removeEventListener('click', this.#handler); | |
} | |
/** | |
* Create a list of attributes to observe | |
*/ | |
static get observedAttributes () { | |
return ['disabled']; | |
} | |
/** | |
* Runs when the value of an attribute is changed on the component | |
* @param {String} name The attribute name | |
* @param {String} oldValue The old attribute value | |
* @param {String} newValue The new attribute value | |
*/ | |
attributeChangedCallback (name, oldValue, newValue) { | |
// Get the aria-live region | |
let target = this.root.querySelector('[aria-live]'); | |
if (!target) return; | |
// Update the UI | |
target.innerHTML = newValue === null ? '' : newValue; | |
} | |
} | |
// Define the new web component | |
if ('customElements' in window) { | |
customElements.define('roll-dice', RollDice); | |
} | |
// Get the element | |
let dice = document.querySelector('roll-dice'); | |
dice.roll(); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Dice</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<style type="text/css"> | |
body { | |
margin: 0 auto; | |
max-width: 40em; | |
width: 88%; | |
} | |
roll-dice { | |
--radius: 0; | |
} | |
roll-dice[size="20"] { | |
--border-color: rebeccapurple; | |
--color: rebeccapurple; | |
--radius: 1em; | |
} | |
roll-dice[size="20"]:hover { | |
--bg-color: rebeccapurple; | |
--color: #ffffff; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Dice</h1> | |
<roll-dice></roll-dice> | |
<roll-dice size="20" result="Result: {{roll}}"> | |
<span slot="text">Roll a D20</span> | |
</roll-dice> | |
<script type="module"> | |
import stylesheet from './styles.css' assert { type: 'css' }; | |
// Extend the HTMLElement class to create the web component | |
class RollDice extends HTMLElement { | |
/** | |
* Randomly shuffle an array | |
* https://stackoverflow.com/a/2450976/1293256 | |
* @param {Array} array The array to shuffle | |
* @return {Array} The shuffled array | |
*/ | |
static #shuffle (array) { | |
let currentIndex = array.length; | |
let temporaryValue, randomIndex; | |
// While there remain elements to shuffle... | |
while (0 !== currentIndex) { | |
// Pick a remaining element... | |
randomIndex = Math.floor(Math.random() * currentIndex); | |
currentIndex -= 1; | |
// And swap it with the current element. | |
temporaryValue = array[currentIndex]; | |
array[currentIndex] = array[randomIndex]; | |
array[randomIndex] = temporaryValue; | |
} | |
return array; | |
} | |
/** | |
* Create the event listener | |
* @param {Element} host The host element | |
* @return {Function} The event callback function | |
*/ | |
static #createHandler (host) { | |
return function (event) { | |
host.roll(); | |
}; | |
} | |
/** | |
* Create a range of numbers. | |
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#sequence_generator_range | |
* @param {Number} stop The last number in the range. | |
* @param {Number} start The first number in the range. | |
* @param {Number} step How much to increase each number by | |
* @returns {Array} A range of numbers. | |
*/ | |
static #range (stop, start = 1, step = 1) { | |
return Array.from( | |
{ length: (stop - start) / step + 1 }, | |
(_, i) => start + i * step | |
); | |
} | |
#dice; | |
#result; | |
#handler; | |
/** | |
* The class constructor object | |
*/ | |
constructor () { | |
// Always call super first in constructor | |
super(); | |
// Creates a shadow root | |
this.root = this.attachShadow({mode: 'closed'}); | |
// Attach the style sheet to the Shadow DOM of this component | |
this.root.adoptedStyleSheets = [stylesheet]; | |
// Store the dice values | |
let size = this.getAttribute('size'); | |
this.#dice = RollDice.#range(size ? parseFloat(size) : 6); | |
this.#result = this.getAttribute('result') ?? 'You rolled a {{roll}}'; | |
this.#handler = RollDice.#createHandler(this); | |
// Render HTML | |
this.root.innerHTML = | |
`<p> | |
<button><slot name="icon">🎲</slot> <slot name="text">Roll a D6</slot></button> | |
</p> | |
<div aria-live="polite"></div>`; | |
} | |
/** | |
* Roll the dice | |
*/ | |
roll () { | |
// If disabled, don't run | |
if (this.hasAttribute('disabled')) return; | |
// Get the aria-live region | |
let target = this.root.querySelector('[aria-live]'); | |
if (!target) return; | |
// Inject the message into the UI | |
RollDice.#shuffle(this.#dice); | |
target.textContent = this.#result.replace('{{roll}}', this.#dice[0]); | |
} | |
/** | |
* Clear the result | |
*/ | |
clear () { | |
// Get the aria-live region | |
let target = this.root.querySelector('[aria-live]'); | |
if (!target) return; | |
// Clear the UI | |
target.textContent = ''; | |
} | |
/** | |
* Runs each time the element is appended to or moved in the DOM | |
*/ | |
connectedCallback () { | |
// Get the button | |
let btn = this.root.querySelector('button'); | |
if (!btn) return; | |
// Listen for clicks on it | |
btn.addEventListener('click', this.#handler); | |
} | |
/** | |
* Runs when the element is removed from the DOM | |
*/ | |
disconnectedCallback () { | |
// Get the button | |
let btn = this.root.querySelector('button'); | |
if (!btn) return; | |
// Remove the event listener on it | |
btn.removeEventListener('click', this.#handler); | |
} | |
/** | |
* Create a list of attributes to observe | |
*/ | |
static get observedAttributes () { | |
return ['disabled']; | |
} | |
/** | |
* Runs when the value of an attribute is changed on the component | |
* @param {String} name The attribute name | |
* @param {String} oldValue The old attribute value | |
* @param {String} newValue The new attribute value | |
*/ | |
attributeChangedCallback (name, oldValue, newValue) { | |
// Get the aria-live region | |
let target = this.root.querySelector('[aria-live]'); | |
if (!target) return; | |
// Update the UI | |
target.innerHTML = newValue === null ? '' : newValue; | |
} | |
} | |
// Define the new web component | |
if ('customElements' in window) { | |
customElements.define('roll-dice', RollDice); | |
} | |
// Get the element | |
let dice = document.querySelector('roll-dice'); | |
dice.roll(); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.roll { | |
background-color: var(--bg-color, #f5f5f5); | |
border: 1px solid var(--border-color, #0088cc); | |
border-radius: var(--radius, 0.25em); | |
color: var(--color, #0088cc); | |
font-size: var(--size, 1.5em); | |
padding: 0.5em 1em; | |
} | |
.roll:hover { | |
background-color: var(--bg-color, #0088cc); | |
color: var(--color, #ffffff); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
button { | |
background-color: var(--bg-color, #f5f5f5); | |
border: 1px solid var(--border-color, #0088cc); | |
border-radius: var(--radius, 0.25em); | |
color: var(--color, #0088cc); | |
font-size: var(--size, 1.5em); | |
padding: 0.5em 1em; | |
} | |
button:hover { | |
background-color: var(--bg-color, #0088cc); | |
color: var(--color, #ffffff); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment