Skip to content

Instantly share code, notes, and snippets.

@cferdinandi
Created November 28, 2023 20:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cferdinandi/ce2b63ed6ca9acaed0d4706c170b06d6 to your computer and use it in GitHub Desktop.
Save cferdinandi/ce2b63ed6ca9acaed0d4706c170b06d6 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head>
<title>Modals</title>
<style type="text/css">
body {
margin: 1em auto;
max-width: 30em;
width: 88%;
}
dialog::backdrop {
background-color: rgba(0, 0, 0, 0.7);
}
</style>
</head>
<body>
<h1>Modals</h1>
<modal-toggle target="#wizards" hidden><button>Wizards</button></modal-toggle>
<modal-toggle target="#apes" hidden><button>Apes</button></modal-toggle>
<modal-toggle target="#confirm" hidden><button>Confirm</button></modal-toggle>
<modal-content id="wizards">
<dialog>
<h2>Wizards</h2>
<form method="dialog">
<label for="wizard">Who is your favorite wizard?</label>
<input type="text" id="wizard" name="wizard" modal-value>
<button close>Cancel</button>
<button>Submit</button>
</form>
</dialog>
</modal-content>
<modal-content id="apes">
<dialog>
<h2>Apes</h2>
<form method="dialog">
<label for="best-ape">What's the best ape?</label>
<select id="best-ape" modal-value>
<option value="">--</option>
<option value="Chimp">Chimp</option>
<option value="Orangatan">Orangatan</option>
<option value="Gorilla">Gorilla</option>
</select>
<button close>Cancel</button>
<button>Submit</button>
</form>
</dialog>
</modal-content>
<modal-content id="confirm">
<dialog>
<h2>Stuff is happening</h2>
<form method="dialog">
<button close>Cancel</button>
<button value="ok">Ok</button>
</form>
</dialog>
</modal-content>
<script>
customElements.define('modal-toggle', class extends HTMLElement {
/**
* Instantiate the component
*/
constructor () {
// Inherit parent class properties
super();
// Set properties
this.modal = document.querySelector(this.getAttribute('target'));
this.toggle = this.querySelector('button, a');
this.handler = this.createHandler();
// Show button
this.removeAttribute('hidden');
// If toggle is a link, add proper role
if (this.toggle.matches('a')) {
this.toggle.setAttribute('role', 'button');
}
}
/**
* Create the event handler function
* @return {Function} The handler function
*/
createHandler () {
return (event) => {
event.preventDefault();
if (!this.modal) return;
this.modal.open();
};
}
/**
* Add event listener
*/
connectedCallback () {
this.toggle.addEventListener('click', this.handler);
}
/**
* Remove event listener
*/
disconnectedCallback () {
this.toggle.removeEventListener('click', this.handler);
}
});
customElements.define('modal-content', class extends HTMLElement {
/**
* Instantiate the component
*/
constructor () {
// Inherit parent class properties
super();
// Set properties
this.modal = this.querySelector('dialog');
this.form = this.querySelector('[method="dialog"]');
this.cancelling = false;
this.clickHandler = this.createClickHandler();
this.closeHandler = this.createCloseHandler();
this.cancelHandler = this.createCancelHandler();
this.inputHandler = this.createInputHandler();
// Setup the DOM
this.setup();
}
/**
* Prevent form buttons from submitting
*/
setup () {
let btns = this.querySelectorAll('form [close]');
for (let btn of btns) {
btn.setAttribute('type', 'button');
}
}
/**
* Create the click handler callback function
* @return {Function} The callback function
*/
createClickHandler () {
return (event) => {
if (!event.target.closest('[close]')) return;
this.modal.returnValue = '';
this.close('cancel', true);
};
}
/**
* Create the close handler callback function
* @return {Function} The callback function
*/
createCloseHandler () {
return (event) => {
if (this.cancelling) {
this.cancelling = false;
return;
}
this.close(this.modal.returnValue ? 'submit' : 'cancel');
};
}
/**
* Create the cancel handler callback function
* @return {Function} The callback function
*/
createCancelHandler () {
return (event) => {
this.close('cancel', true);
};
}
/**
* Create the input handler callback function
* @return {Function} The callback function
*/
createInputHandler () {
return (event) => {
if (!event.target.matches('[modal-value]')) return;
this.modal.returnValue = event.target.value || '';
};
}
/**
* Attach event listeners
*/
connectedCallback () {
if (!this.modal) return;
this.modal.addEventListener('click', this.clickHandler);
this.modal.addEventListener('close', this.closeHandler);
this.modal.addEventListener('cancel', this.cancelHandler);
if (!this.form) return;
this.modal.addEventListener('input', this.inputHandler);
}
/**
* Remove event listeners
*/
disconnectedCallback () {
if (!this.modal) return;
this.modal.removeEventListener('click', this.clickHandler);
this.modal.removeEventListener('close', this.closeHandler);
this.modal.removeEventListener('cancel', this.cancelHandler);
this.modal.removeEventListener('input', this.inputHandler);
}
/**
* Emit a custom event
* @param {String} type The event type name
*/
emit (type) {
// Set the event detail
let detail = type === 'submit' ? this.modal.returnValue : null;
// Create a new event
let event = new CustomEvent(`modal:${type}`, {
bubbles: true,
detail
});
// Dispatch the event
return this.dispatchEvent(event);
}
/**
* Open the modal
*/
open () {
// If there's no modal, bail
if (!this.modal) return;
// Show the modal
this.modal.showModal();
// Add styling hook to body
document.body.setAttribute('modal-open', '');
// Emit an open event
this.emit('open');
}
/**
* Close the modal
* @param {string} type The close type
* @param {boolean} cancel If true, temporary set cancel state
*/
close (type, cancel = false) {
// If there's no modal, bail
if (!this.modal) return;
// Remove the style hook from the body
document.body.removeAttribute('modal-open');
// Temporarily set cancel state
this.cancelling = cancel;
// Close the modal
this.modal.close();
// Emit a close event
this.emit(type);
}
});
//
// LOG STUFF
//
function log (event) {
console.log(event.type, event.target, event.detail);
}
document.addEventListener('modal:open', log);
document.addEventListener('modal:submit', log);
document.addEventListener('modal:cancel', log);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment