UNPKG

@randstad-design/orbit-multitheme

Version:

multitheme Front-end code based on Randstad Human Forward components

349 lines (321 loc) 10.6 kB
/** * modal.js * Script to toggle modal */ import ElementHelpers from '../helpers/element-helpers'; import trapFocus from '../helpers/trapFocus'; /** * Declare constants */ const attributeBase = 'data-rs-modal'; export class Modal { constructor(element) { this.element = element; this.openTrigger = ElementHelpers.getElementByAttribute( this.attributes.trigger, this.element.getAttribute(attributeBase) ); this.closeTrigger = ElementHelpers.getElementByAttributeWithinElement( this.element, this.attributes.closeTrigger ); this.doNotCloseOnClickOverlay = ElementHelpers.getElementByAttributeWithinElement( this.element, this.attributes.doNotCloseOnClickOverlay ); this.dialog = ElementHelpers.getElementByAttributeWithinElement( this.element, this.attributes.dialog ); this.main = ElementHelpers.getElementByAttributeWithinElement( this.element, this.attributes.main ); this.header = ElementHelpers.getElementByAttributeWithinElement( this.element, this.attributes.header ); this.footer = ElementHelpers.getElementByAttributeWithinElement( this.element, this.attributes.footer ); this.disableHistory = this.element.getAttribute(this.attributes.disableHistory) !== null; this.popupDelay = this.element.getAttribute(this.attributes.popupDelay); this.defaultPopupDelay = 5; this.scrollbarWidth = window.innerWidth - document.documentElement.clientWidth; this.scrollPosition = 3; this.bodyMarginTop = Modal.initializeBodyMarginTop(); this.addEventHandlers(); this.handleDividers(); this.handleQueryStringParameters(); this.initializePopup(); this.keystroke = 0; this.modalOpen; } /** * Declare classes */ get classes() { return { modalActive: 'modal--active', modalOpen: 'modal-open', modalHeaderDivider: 'divider', modalHeaderDividerIn: 'modal__header--divider-in', modalHeaderDividerOut: 'modal__header--divider-out', modalMainOverflow: 'modal__main--overflow', divider: 'divider', dividerTop: 'divider--top' }; } /** * Declare attribute constants */ get attributes() { return { trigger: `${attributeBase}-trigger`, popupDelay: `${attributeBase}-popup-delay`, closeTrigger: `${attributeBase}-close-trigger`, dialog: `${attributeBase}-dialog`, main: `${attributeBase}-main`, doNotCloseOnClickOverlay: `${attributeBase}-do-not-close-on-click-overlay`, header: `${attributeBase}-header`, footer: `${attributeBase}-footer`, disableHistory: `${attributeBase}-disable-history`, modalOpen: 'modal_open', hidden: 'aria-hidden' }; } /** * Declare key codes */ get keyCodes() { return { Escape: 'Escape', Tab: 'Tab' }; } /** * Initalize body margin top */ static initializeBodyMarginTop() { const marginTop = document.body.style.marginTop; return !marginTop ? 0 : marginTop.slice(0, marginTop.length - 2); } /** * Add event handlers */ addEventHandlers() { // toggle modal on click trigger this.openTrigger.addEventListener('click', (event) => { this.modalOpen = true; event.preventDefault(); this.scrollPosition = window.pageYOffset; this.toggleModal(!this.disableHistory); }); // close/toggle modal on click close trigger this.closeTrigger.addEventListener('click', (event) => { event.preventDefault(); this.toggleModal(!this.disableHistory); document.body.style.marginTop = `${this.bodyMarginTop}px`; window.scrollTo(0, this.scrollPosition); this.modalOpen = false; }); // if clicked outside modal/on overlay close/toggle modal this.element.addEventListener('mousedown', (clickEvent) => { if ( !this.doNotCloseOnClickOverlay && clickEvent.target !== this.dialog && !this.dialog.contains(clickEvent.target) ) { this.toggleModal(!this.disableHistory); document.body.style.marginTop = `${this.bodyMarginTop}px`; window.scrollTo(0, this.scrollPosition); } }); // on resize, handle dividers of modal window.addEventListener('resize', () => { this.handleDividers(); }); // on scroll, check position to set/unset dividers this.main.addEventListener( 'scroll', () => { this.checkPosition(); }, { passive: true } ); // on popstate event (i.e. back button in browser), handle querystring parameters window.addEventListener('popstate', () => { this.handleQueryStringParameters(false); }); // handle key down event - close modal if escape is pressed document.addEventListener('keydown', (keydownEvent) => { this.handleButtonKeys(keydownEvent); }); } /** * Toggle modal */ toggleModal(changePushState = true) { this.element.classList.contains(this.classes.modalActive) ? this.closeModal(changePushState) : this.openModal(changePushState); } /** * Close modal - closes modal, set styling and edit pushstate * @param {boolean} changePushState - determines if history push state should be adjusted */ closeModal(changePushState) { // remove width of scrollbar to body and modal document.body.style.paddingRight = '0px'; this.element.style.paddingRight = ''; // if changePushState is true, remove querystring parameters from url if (changePushState) { window.history.pushState({}, '', window.location.pathname); } document.querySelector('html').classList.remove(this.classes.modalOpen); this.element.setAttribute(this.attributes.hidden, 'true'); this.element.classList.remove(this.classes.modalActive); this.keystroke = 0; // If the modal is active and there is a trigger,When closing the modal focus on the trigger again if (this.modalOpen) { this.openTrigger.focus(); } this.modalOpen = false; } /** * Open modal - opens modal, set styling and edit pushstate * @param {boolean} changePushState - determines if history push state should be adjusted */ openModal(changePushState) { this.modalOpen = true; document.body.style.marginTop = `${ this.bodyMarginTop - this.scrollPosition }px`; document.body.style.paddingRight = `${this.scrollbarWidth}px`; this.element.style.paddingRight = `${this.scrollbarWidth}px`; // if changePushState is true, add querystring parameters to url if (changePushState) { window.history.pushState( {}, '', `${window.location.pathname}?${ this.attributes.modalOpen }=${this.element.getAttribute(attributeBase)}` ); } document.querySelector('html').classList.add(this.classes.modalOpen); this.element.removeAttribute(this.attributes.hidden); this.element.classList.add(this.classes.modalActive); } /** * Handle dividers - add or remove dividers */ handleDividers() { if (this.main.scrollHeight > this.main.offsetHeight) { // article element has a scrollbar, add divider classes this.addDivider(); } else if ( this.header && this.header.classList.contains(this.classes.divider) ) { // article element has no scrollbar, remove divider classes this.removeDivider(); } } /** * Add divider - add divider classes if main content is scrollable */ addDivider() { this.main.classList.add(this.classes.modalMainOverflow); if (this.footer !== null) { this.footer.classList.add(this.classes.divider, this.classes.dividerTop); } } /** * Remove divider - remove divider classes if main content is not scrollable */ removeDivider() { this.header.classList.remove(this.classes.divider); this.main.classList.remove(this.classes.modalMainOverflow); if (this.footer !== null) { this.footer.classList.remove(this.classes.dividerTop); } } /** * Check position */ checkPosition() { const modalMainY = this.main.scrollTop; if (this.header && modalMainY > this.scrollPosition) { this.header.classList.add(this.classes.divider); this.header.classList.add(this.classes.modalHeaderDividerIn); this.header.classList.remove(this.classes.modalHeaderDividerOut); } if ( (this.header && modalMainY < this.scrollPosition) || (this.header && modalMainY === 0) ) { this.header.classList.add(this.classes.modalHeaderDividerOut); this.header.classList.remove(this.classes.divider); this.header.classList.remove(this.classes.modalHeaderDividerIn); } } /** * Handle querystring parameters - check querystring parameters to show or hide modal */ handleQueryStringParameters() { if (window.location.href.includes(`?${this.attributes.modalOpen}`)) { let urlParams = new URLSearchParams(window.location.search); // open modal if element is equal to value in querystring parameter this.openModalElement = urlParams.get(this.attributes.modalOpen); if (this.openModalElement === this.element.getAttribute(attributeBase)) { this.openModal(false); } } else { this.closeModal(false); } } /** * Initialize popup - if element contains popup-delay attribute => show popup with delay, if no delay found -> set to 5 sec */ initializePopup() { if (this.popupDelay !== null) { let popupDelay = this.element.getAttribute(this.attributes.popupDelay); setTimeout(() => { this.openModal(false); }, (popupDelay === '' ? this.defaultPopupDelay : popupDelay) * 1000); } } /** * Handle button keys - handle key presses escape * * @param {Event} event - event triggered */ handleButtonKeys(event) { if (event != null) { switch (ElementHelpers.getKeyCodeOnKeyDownEvent(event)) { case this.keyCodes.Escape: event.preventDefault(); this.closeModal(true); document.body.style.marginTop = `${this.bodyMarginTop}px`; window.scrollTo(0, this.scrollPosition); break; case this.keyCodes.Tab: trapFocus(event, this.element, this.modalOpen, this.keystroke); if (this.modalOpen) { this.keystroke++; } } } } /** * Get selector */ static getSelector() { return `[${attributeBase}]`; } }