<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Pick at Random</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style type="text/css"> body { margin: 1em auto; max-width: 40em; width: 88%; } pick-at-random [role="status"]:not(:empty) { background-color: #f7f7f7; border: 1px solid #e5e5e5; border-radius: 0.25em; padding: 0.5rem 1rem; margin-top: 0.5rem; } pick-at-random li button { background-color: transparent; border: 0; color: inherit; margin-inline-start: 0.5em; padding: 0; } pick-at-random li button svg { margin-bottom: -0.25em; } pick-at-random [clear-list] { background-color: transparent; border: 0; font: inherit; color: inherit; padding: 0; } </style> </head> <body> <h1>Pick at Random</h1> <pick-at-random add-label="Add a Person" add-button="Add Person" add-status='You added "${value}" to the list!' pick-button="Pick a Person" clear-button="or remove everyone" clear-all-confirm="You sure, bro?" remove-status="You have removed ${value}" selected-status='"${value}" has to drive!' remove-button="❌" remove-label="Get rid of ${value}" local-storage="designed-driver" ></pick-at-random> <br><br><br><br> <pick-at-random local-storage="rando"></pick-at-random> <script> customElements.define('pick-at-random', class extends HTMLElement { /** * Instantiate the component */ constructor () { // Inherits parent class properties super(); // Create a unique ID for the instance this.uuid = `pick-${crypto.randomUUID()}`; // Settings this.settings = { addLabel: this.getAttribute('add-label') || 'Add an Item', addButton: this.getAttribute('add-button') || 'Add Item', pickButton: this.getAttribute('pick-button') || 'Pick an Item', clearButton: this.getAttribute('clear-button') || 'or remove all items', addStatus: this.getAttribute('add-status') || '"${value}" has been added to the list.', clearAllConfirm: this.getAttribute('clear-all-confirm') || 'Are you sure you want to do this? It cannot be undone.', removeStatus: this.getAttribute('remove-status') || '"${value}" has been removed from the list.', selectedStatus: this.getAttribute('selected-status') || 'You picked ${value}', removeButton: this.getAttribute('remove-button') || `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true"><path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708"/></svg>`, removeLabel: this.getAttribute('remove-label') || 'Remove ${value}', localStorageID: this.getAttribute('local-storage') }; // Render our initial HTML this.innerHTML = `<form> <label for="${this.uuid}">${this.settings.addLabel}</label> <input type="text" id="${this.uuid}"> <button>${this.settings.addButton}</button> </form> <ul></ul> <p><button pick-item>${this.settings.pickButton}</button> <button clear-list>${this.settings.clearButton}</button></p> <div role="status" pick-result></div>`; // Get our elements this.form = this.querySelector('form'); this.list = this.querySelector('ul'); this.field = this.form.querySelector('input'); this.pickBtn = this.querySelector('[pick-item]'); this.result = this.querySelector('[pick-result]'); // Listen for event this.form.addEventListener('submit', this); this.addEventListener('click', this); // Load list from localStorage this.loadItems(); } /** * Handle events * @param {Event} event The event object */ handleEvent (event) { this[`on${event.type}`](event); } /** * Handle submit events * @param {Event} event The event object */ onsubmit (event) { // Stop the form from reloading the page event.preventDefault(); // If there's no item to add, bail if (!this.field.value.length) return; // Create a list item this.createListItem(this.field.value); // Show a status message this.showStatus(this.settings.addStatus.replace('${value}', this.field.value)); // Clear text from field this.field.value = ''; // Save our list to localStorage this.saveItems(); } /** * Handle click event * @param {Event} event The event object */ onclick (event) { this.onPickButton(event); this.onRemove(event); this.onClearList(event); } /** * Clear the list of items * @param {Event} event The event object */ onClearList (event) { // Only run on [clear-list] button if (!event.target.closest('[clear-list]')) return; // Double check the user actually wants to do this let doClear = confirm(this.settings.clearAllConfirm); if (!doClear) return; // Clear the list this.list.innerHTML = ''; // Remove items from localStorage this.removeItems(); } /** * Handle remove button click * @param {Event} event The event object */ onRemove (event) { // Only run on remove buttons let btn = event.target.closest('[data-remove]'); if (!btn) return; let txt = btn.getAttribute('data-remove'); // Get the list item let li = event.target.closest('li'); if (!li) return; li.remove(); // Show remove message this.showStatus(this.settings.removeStatus.replace('${value}', txt)); // Save updated list this.saveItems(); } /** * Handle pick button click * @param {Event} event The event object */ onPickButton (event) { // Only run on [pick-item] button if (!event.target.closest('[pick-item]')) return; // Get all of the list items let items = this.getItems(); if (!items.length) return; // Randomize the items this.shuffle(items); // Show the result this.result.textContent = this.settings.selectedStatus.replace('${value}', items[0]); } /** * Create list item * @param {String} The text to add to the item */ createListItem (txt) { // Create list item let li = document.createElement('li'); li.textContent = txt; // Create remove button let btn = document.createElement('button'); btn.innerHTML = this.settings.removeButton; btn.setAttribute('aria-label', this.settings.removeLabel.replace('${value}', txt)); btn.setAttribute('data-remove', txt); li.append(btn); this.list.append(li); } /** * Get an array of user-added items * @return {Array} The items */ getItems () { return Array.from(this.list.querySelectorAll('li')).map((item) => { return item.textContent.replace(this.settings.removeButton, ''); }); } /** * Save items to localStorage */ saveItems () { if (!this.settings.localStorageID) return; let items = JSON.stringify(this.getItems()); localStorage.setItem(`pickAtRandom_${this.settings.localStorageID}`, items); } /** * Remove items to localStorage */ removeItems () { if (!this.settings.localStorageID) return; localStorage.removeItem(`pickAtRandom_${this.settings.localStorageID}`); } /** * Load saved list from localStorage */ loadItems () { if (!this.settings.localStorageID) return; let items = JSON.parse(localStorage.getItem(`pickAtRandom_${this.settings.localStorageID}`)); if (!items) return; for (let item of items) { this.createListItem(item); } } /** * Show a status message in the form * @param {String} msg The message to display */ showStatus (msg) { // Create a notification let notification = document.createElement('div'); notification.setAttribute('role', 'status'); // Inject it into the DOM this.form.append(notification); // Add text after it's in the UI setTimeout(function () { notification.textContent = msg; }, 1); // Remove it after 4 seconds setTimeout(function () { notification.remove(); }, 4000); } /** * Randomly shuffle an array * https://stackoverflow.com/a/2450976/1293256 * @param {Array} array The array to shuffle * @return {Array} The shuffled array */ 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; } }); </script> </body> </html>