-
-
Save cferdinandi/ce2b63ed6ca9acaed0d4706c170b06d6 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> | |
<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