Skip to content

Instantly share code, notes, and snippets.

@cferdinandi
Created June 25, 2024 16:14
Show Gist options
  • Save cferdinandi/308b4721cf8a8d762098e09398498866 to your computer and use it in GitHub Desktop.
Save cferdinandi/308b4721cf8a8d762098e09398498866 to your computer and use it in GitHub Desktop.
Can you build a modern web app using only vanilla Web Components in 2024? Watch part 2 tutorial on YouTube: https://youtu.be/6pttzPPtiFA
<!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></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()}`;
// Render our initial HTML
this.innerHTML =
`<form>
<label for="${this.uuid}">Add an Item</label>
<input type="text" id="${this.uuid}">
<button>Add Item</button>
</form>
<ul></ul>
<p><button pick-item>Pick an Item</button> <button clear-list>or remove all items</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.field.value}" has been added to the list.`);
// 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('Are you sure you want to do this? It cannot be undone.');
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(`"${txt}" has been removed from the list.`);
// 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 = `You picked ${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 = `<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>`;
btn.setAttribute('aria-label', `Remove ${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(function (item) {
return item.textContent;
});
}
/**
* Save items to localStorage
*/
saveItems () {
let items = JSON.stringify(this.getItems());
localStorage.setItem(`pickAtRandom`, items);
}
/**
* Remove items to localStorage
*/
removeItems () {
localStorage.removeItem(`pickAtRandom`);
}
/**
* Load saved list from localStorage
*/
loadItems () {
let items = JSON.parse(localStorage.getItem('pickAtRandom'));
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment