Skip to content

Instantly share code, notes, and snippets.

@cferdinandi
Last active June 3, 2023 04:38
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save cferdinandi/c2e9e7aab0439d655a3d31145a6ee13d to your computer and use it in GitHub Desktop.
Save cferdinandi/c2e9e7aab0439d655a3d31145a6ee13d to your computer and use it in GitHub Desktop.
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
.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);
}
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