From a60e3c4a33c37bdd7bee5f9433a613d00002c73d Mon Sep 17 00:00:00 2001 From: Michael Gauthier Date: Thu, 19 Mar 2026 22:08:03 -0300 Subject: [PATCH 1/7] Tabs to spaces for SiteDialog JS --- www/javascript/site-dialog.js | 1286 ++++++++++++++++----------------- 1 file changed, 615 insertions(+), 671 deletions(-) diff --git a/www/javascript/site-dialog.js b/www/javascript/site-dialog.js index ca0a5b55..bbab15a7 100644 --- a/www/javascript/site-dialog.js +++ b/www/javascript/site-dialog.js @@ -1,17 +1,16 @@ -var SiteDialog = function(el, user_config) -{ - SiteDialog.addSentinel(); +var SiteDialog = function(el, user_config) { + SiteDialog.addSentinel(); - this.initConfig(user_config); - this.initElements(el); + this.initConfig(user_config); + this.initElements(el); - SiteDialog.dialogs.push(this); + SiteDialog.dialogs.push(this); }; SiteDialog.STATE_OPENED = 1; SiteDialog.STATE_CLOSED = 2; SiteDialog.dialogs = []; -SiteDialog.has_push_state = (window.pushState); +SiteDialog.has_push_state = window.pushState; SiteDialog.opened_dialog_stack = []; SiteDialog.desktop_sentinel = null; SiteDialog.is_desktop = false; @@ -22,679 +21,624 @@ SiteDialog.RESIZE_NONE = 0; SiteDialog.RESIZE_FILL = 1; SiteDialog.RESIZE_CENTER = 2; -SiteDialog.updateLayout = function() -{ - if (SiteDialog.opened_dialog_stack.length === 0) { - for (i = 0; i < document.body.childNodes.length; i++) { - YAHOO.util.Dom.removeClass( - document.body.childNodes[i], - 'site-dialog-hidden' - ); - } - - // Also re-show all dialogs. Needed because relatively positioned - // dialogs may not be children of the body element when updateLayout() - // is called. - for (j = 0; j < SiteDialog.dialogs.length; j++) { - YAHOO.util.Dom.removeClass( - SiteDialog.dialogs[j].dialog, - 'site-dialog-hidden' - ); - if (SiteDialog.dialogs[j].overlay) { - YAHOO.util.Dom.removeClass( - SiteDialog.dialogs[j].overlay, - 'site-dialog-hidden' - ); - } - } - - if (SiteDialog.scroll_top !== null) { - window.scrollTo(0, SiteDialog.scroll_top); - SiteDialog.scroll_top = null; - } - } else { - // save scroll position - SiteDialog.scroll_top = YAHOO.util.Dom.getDocumentScrollTop(); - - window.scrollTo(0, 0); - - var top_index = SiteDialog.opened_dialog_stack.length - 1; - var top_dialog = SiteDialog.opened_dialog_stack[top_index]; - for (i = 0; i < document.body.childNodes.length; i++) { - var node = document.body.childNodes[i]; - - // don't hide the top-level opened dialog - if (node === top_dialog.dialog) { - YAHOO.util.Dom.removeClass(node, 'site-dialog-hidden'); - - // don't hide sentinel - } else if (node !== SiteDialog.desktop_sentinel) { - YAHOO.util.Dom.addClass(node, 'site-dialog-hidden'); - } - } - } +SiteDialog.updateLayout = function() { + if (SiteDialog.opened_dialog_stack.length === 0) { + for (i = 0; i < document.body.childNodes.length; i++) { + YAHOO.util.Dom.removeClass( + document.body.childNodes[i], + 'site-dialog-hidden' + ); + } + + // Also re-show all dialogs. Needed because relatively positioned + // dialogs may not be children of the body element when updateLayout() + // is called. + for (j = 0; j < SiteDialog.dialogs.length; j++) { + YAHOO.util.Dom.removeClass( + SiteDialog.dialogs[j].dialog, + 'site-dialog-hidden' + ); + if (SiteDialog.dialogs[j].overlay) { + YAHOO.util.Dom.removeClass( + SiteDialog.dialogs[j].overlay, + 'site-dialog-hidden' + ); + } + } + + if (SiteDialog.scroll_top !== null) { + window.scrollTo(0, SiteDialog.scroll_top); + SiteDialog.scroll_top = null; + } + } else { + // save scroll position + SiteDialog.scroll_top = YAHOO.util.Dom.getDocumentScrollTop(); + + window.scrollTo(0, 0); + + var top_index = SiteDialog.opened_dialog_stack.length - 1; + var top_dialog = SiteDialog.opened_dialog_stack[top_index]; + for (i = 0; i < document.body.childNodes.length; i++) { + var node = document.body.childNodes[i]; + + // don't hide the top-level opened dialog + if (node === top_dialog.dialog) { + YAHOO.util.Dom.removeClass(node, 'site-dialog-hidden'); + + // don't hide sentinel + } else if (node !== SiteDialog.desktop_sentinel) { + YAHOO.util.Dom.addClass(node, 'site-dialog-hidden'); + } + } + } }; -SiteDialog.raiseDialog = function(dialog) -{ - var index = null; - for (var i = 0; i < SiteDialog.opened_dialog_stack.length; i++) { - if (SiteDialog.opened_dialog_stack[i] === dialog) { - index = i; - break; - } - } - - if (index !== null) { - SiteDialog.opened_dialog_stack.splice(index, 1); - } - - SiteDialog.opened_dialog_stack.push(dialog); - SiteDialog.updateLayout(); +SiteDialog.raiseDialog = function(dialog) { + var index = null; + for (var i = 0; i < SiteDialog.opened_dialog_stack.length; i++) { + if (SiteDialog.opened_dialog_stack[i] === dialog) { + index = i; + break; + } + } + + if (index !== null) { + SiteDialog.opened_dialog_stack.splice(index, 1); + } + + SiteDialog.opened_dialog_stack.push(dialog); + SiteDialog.updateLayout(); }; -SiteDialog.lowerDialog = function(dialog) -{ - var index = null; - for (var i = 0; i < SiteDialog.opened_dialog_stack.length; i++) { - if (SiteDialog.opened_dialog_stack[i] === dialog) { - index = i; - break; - } - } - - if (index !== null) { - SiteDialog.opened_dialog_stack.splice(index, 1); - } - - SiteDialog.updateLayout(); +SiteDialog.lowerDialog = function(dialog) { + var index = null; + for (var i = 0; i < SiteDialog.opened_dialog_stack.length; i++) { + if (SiteDialog.opened_dialog_stack[i] === dialog) { + index = i; + break; + } + } + + if (index !== null) { + SiteDialog.opened_dialog_stack.splice(index, 1); + } + + SiteDialog.updateLayout(); }; -SiteDialog.addSentinel = function() -{ - if (SiteDialog.desktop_sentinel === null) { - SiteDialog.desktop_sentinel = document.createElement('div'); - SiteDialog.desktop_sentinel.className = 'site-dialog-sentinel'; - document.body.appendChild(SiteDialog.desktop_sentinel); - - var timeout = null; - - var checkSentinel = function() - { - var display = YAHOO.util.Dom.getComputedStyle( - SiteDialog.desktop_sentinel, - 'display' - ); - - if (display === 'none' && !SiteDialog.is_desktop) { - // changing from mobile to desktop - SiteDialog.is_desktop = true; - SiteDialog.handleLayoutChange(); - } else if (display === 'block' && SiteDialog.is_desktop) { - // changing from desktop to mobile - SiteDialog.is_desktop = false; - SiteDialog.handleLayoutChange(); - } - }; - - var handleResize = function() - { - for (var i = 0; i < SiteDialog.dialogs.length; i++) { - SiteDialog.dialogs[i].handleResize(); - } - }; - - // Initialize layout state - if (YAHOO.util.Dom.hasClass(document.documentElement, 'ie8')) { - // Give IE8 time to load responsive styles before initializing - // mode. It needs to re-download and parse all the CSS. Respond.js - // does not provide an event for this. To do so, we add an element - // to the DOM that has a media query style that sets its display - // to 'none'. We check if the media query has been loaded on a - // short interval. - var mq_detect_el = document.createElement('div'); - mq_detect_el.className = 'site-dialog-mq-detect'; - document.body.appendChild(mq_detect_el); - var mq_detect_interval = setInterval(function() { - var display = YAHOO.util.Dom.getComputedStyle( - mq_detect_el, - 'display' - ); - if (display === 'none') { - mq_detect_el.parentNode.removeChild(mq_detect_el); - checkSentinel(); - clearInterval(mq_detect_interval); - mq_detect_interval = null; - } - }, 10); - setTimeout(checkSentinel, 1000); - } else { - checkSentinel(); - } - - YAHOO.util.Event.on( - window, - 'resize', - function (e) { - // Debounce resize updates so they only fire every at most - // every SiteDialog.resize_debounce_delay ms. - if (timeout) { - clearTimeout(timeout); - } - timeout = setTimeout(function() { - checkSentinel(); - handleResize(); - timeout = null; - }, SiteDialog.resize_debounce_delay); - } - ); - } +SiteDialog.addSentinel = function() { + if (SiteDialog.desktop_sentinel === null) { + SiteDialog.desktop_sentinel = document.createElement('div'); + SiteDialog.desktop_sentinel.className = 'site-dialog-sentinel'; + document.body.appendChild(SiteDialog.desktop_sentinel); + + var timeout = null; + + var checkSentinel = function() { + var display = YAHOO.util.Dom.getComputedStyle( + SiteDialog.desktop_sentinel, + 'display' + ); + + if (display === 'none' && !SiteDialog.is_desktop) { + // changing from mobile to desktop + SiteDialog.is_desktop = true; + SiteDialog.handleLayoutChange(); + } else if (display === 'block' && SiteDialog.is_desktop) { + // changing from desktop to mobile + SiteDialog.is_desktop = false; + SiteDialog.handleLayoutChange(); + } + }; + + var handleResize = function() { + for (var i = 0; i < SiteDialog.dialogs.length; i++) { + SiteDialog.dialogs[i].handleResize(); + } + }; + + // Initialize layout state + if (YAHOO.util.Dom.hasClass(document.documentElement, 'ie8')) { + // Give IE8 time to load responsive styles before initializing + // mode. It needs to re-download and parse all the CSS. Respond.js + // does not provide an event for this. To do so, we add an element + // to the DOM that has a media query style that sets its display + // to 'none'. We check if the media query has been loaded on a + // short interval. + var mq_detect_el = document.createElement('div'); + mq_detect_el.className = 'site-dialog-mq-detect'; + document.body.appendChild(mq_detect_el); + var mq_detect_interval = setInterval(function() { + var display = YAHOO.util.Dom.getComputedStyle(mq_detect_el, 'display'); + if (display === 'none') { + mq_detect_el.parentNode.removeChild(mq_detect_el); + checkSentinel(); + clearInterval(mq_detect_interval); + mq_detect_interval = null; + } + }, 10); + setTimeout(checkSentinel, 1000); + } else { + checkSentinel(); + } + + YAHOO.util.Event.on(window, 'resize', function(e) { + // Debounce resize updates so they only fire every at most + // every SiteDialog.resize_debounce_delay ms. + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(function() { + checkSentinel(); + handleResize(); + timeout = null; + }, SiteDialog.resize_debounce_delay); + }); + } }; -SiteDialog.handleLayoutChange = function() -{ - for (var i = 0; i < SiteDialog.dialogs.length; i++) { - SiteDialog.dialogs[i].handleLayoutChange(); - } +SiteDialog.handleLayoutChange = function() { + for (var i = 0; i < SiteDialog.dialogs.length; i++) { + SiteDialog.dialogs[i].handleLayoutChange(); + } }; (function() { - var Dom = YAHOO.util.Dom; - var Event = YAHOO.util.Event; - var Anim = YAHOO.util.Anim; - - var proto = SiteDialog.prototype; - - // {{{ Configuration - - var DEFAULT_CONFIG = { - 'USE_OVERLAY': { - key: 'use_overlay', - value: true, - validator: YAHOO.lang.isBoolean - }, - /** - * Allow clicking outside the dialog to close the dialog. - */ - 'DISMISSABLE': { - key: 'dismissable', - value: true, - validator: YAHOO.lang.isBoolean - }, - /** - * Only show dialog in mobile layout. If switching back to desktop, the - * dialog is automatically closed. - */ - 'MOBILE_ONLY': { - key: 'mobile_only', - value: false, - validator: YAHOO.lang.isBoolean - }, - /** - * Use pushState API if available to control opening and closing the - * dialog. - */ - 'USE_PUSH_STATE': { - key: 'use_push_state', - value: true, - validator: YAHOO.lang.isBoolean - }, - 'CLASS_NAME': { - key: 'class_name', - value: '' - }, - 'TOGGLE_ELEMENT': { - key: 'toggle_element', - value: null - }, - 'RELATIVE_CONTAINER': { - key: 'relative_container', - value: null - }, - 'RESIZE_MODE': { - key: 'resize_mode', - value: SiteDialog.RESIZE_FILL, - validator: YAHOO.lang.isNumber - } - }; - - proto.initDefaultConfig = function() - { - this.config.addProperty(DEFAULT_CONFIG.USE_OVERLAY.key, { - value: DEFAULT_CONFIG.USE_OVERLAY.value, - validator: DEFAULT_CONFIG.USE_OVERLAY.validator - }); - - this.config.addProperty(DEFAULT_CONFIG.DISMISSABLE.key, { - value: DEFAULT_CONFIG.DISMISSABLE.value, - validator: DEFAULT_CONFIG.DISMISSABLE.validator - }); - - this.config.addProperty(DEFAULT_CONFIG.MOBILE_ONLY.key, { - value: DEFAULT_CONFIG.MOBILE_ONLY.value, - validator: DEFAULT_CONFIG.MOBILE_ONLY.validator - }); - - this.config.addProperty(DEFAULT_CONFIG.USE_PUSH_STATE.key, { - value: DEFAULT_CONFIG.USE_PUSH_STATE.value, - validator: DEFAULT_CONFIG.USE_PUSH_STATE.validator - }); - - this.config.addProperty(DEFAULT_CONFIG.CLASS_NAME.key, { - value: DEFAULT_CONFIG.CLASS_NAME.value - }); - - this.config.addProperty(DEFAULT_CONFIG.TOGGLE_ELEMENT.key, { - value: DEFAULT_CONFIG.TOGGLE_ELEMENT.value - }); - - this.config.addProperty(DEFAULT_CONFIG.RESIZE_MODE.key, { - value: DEFAULT_CONFIG.RESIZE_MODE.value, - validator: DEFAULT_CONFIG.RESIZE_MODE.validator - }); - - this.config.addProperty(DEFAULT_CONFIG.RELATIVE_CONTAINER.key, { - value: DEFAULT_CONFIG.RELATIVE_CONTAINER.value - }); - }; - - proto.initConfig = function(user_config) - { - this.config = new YAHOO.util.Config(this); - this.initDefaultConfig(); - - // Merge user and default config values. - if (user_config) { - this.config.applyConfig(user_config, true); - } - - // Flatten config object. We're not using events. - this.config = this.config.getConfig(); - }; - - // }}} - - proto.initElements = function(el) - { - this.header = this.drawHeader(); - this.body = this.drawBody(); - this.scroll = this.drawScroll(this.header, this.body); - this.footer = this.drawFooter(); - this.container = this.drawContainer(this.scroll, this.footer); - - this.dialog = this.drawDialog(this.container, el); - - this.id = this.dialog.id; - - if (this.config.use_overlay) { - this.overlay = this.drawOverlay(); - } - - this.state = SiteDialog.STATE_OPENED; - this.close(); - - if (this.config.use_overlay) { - document.body.appendChild(this.overlay); - } - - if (this.config.dismissable) { - Event.on( - document.body, - 'click', - this.handleDocumentClick, - this, - true - ); - - Event.on( - document.body, - 'keydown', - this.handleDocumentKeyDown, - this, - true - ); - } - - if (SiteDialog.is_desktop && this.config.relative_container) { - this.config.relative_container.appendChild(this.dialog); - } else { - document.body.appendChild(this.dialog); - } - - if (SiteDialog.has_push_state && this.config.use_push_state) { - Event.on(window, 'popstate', this.handlePopState, this, true); - } - }; - - proto.getPushStateId = function() - { - return this.id; - }; - - // {{{ Element draw methods - - proto.drawOverlay = function() - { - var overlay = document.createElement('div'); - overlay.className = 'site-dialog-overlay'; - return overlay; - }; - - proto.drawDialog = function(container, el) - { - var dialog; - - if (el) { - dialog = Dom.get(el); - if (!dialog) { - dialog = document.createElement('div'); - dialog.id = el; - } - } else { - dialog = document.createElement('div'); - } - - Dom.generateId(dialog, 'site-dialog'); - - Dom.addClass(dialog, 'site-dialog-dialog'); - if (this.config.relative_container) { - Dom.addClass(dialog, 'site-dialog-relative'); - } - if (this.config.class_name + '' !== '') { - Dom.addClass(dialog, this.config.class_name); - } - - dialog.appendChild(container); - - return dialog; - }; - - proto.drawScroll = function(header, body) - { - var scroll = document.createElement('div'); - scroll.className = 'site-dialog-scroll'; - - scroll.appendChild(header); - scroll.appendChild(body); - - return scroll; - }; - - proto.drawContainer = function(scroll, footer) - { - var container = document.createElement('div'); - container.className = 'site-dialog-container'; - - container.appendChild(scroll); - container.appendChild(footer); - - return container; - }; - - proto.drawHeader = function() - { - var header = document.createElement('div'); - header.className = 'site-dialog-header'; - return header; - }; - - proto.drawBody = function() - { - var body = document.createElement('div'); - body.className = 'site-dialog-body'; - return body; - }; - - proto.drawFooter = function() - { - var footer = document.createElement('div'); - footer.className = 'site-dialog-footer'; - return footer; - }; - - // }}} - // {{{ Opening and closing - - proto.open = function() - { - if (this.isOpened()) { - return; - } - - this.raise(); - - Dom.removeClass(this.overlay, 'site-dialog-closed'); - Dom.removeClass(this.dialog, 'site-dialog-closed'); - - - // need to set state before doing initial positioning - this.state = SiteDialog.STATE_OPENED; - - this.handleResize(); - - // bubble up opened stack if not desktop - if (!SiteDialog.is_desktop) { - SiteDialog.raiseDialog(this); - } - }; - - proto.openWithAnimation = function() - { - // TODO: implement animations - this.open(); - }; - - proto.close = function() - { - if (this.isClosed()) { - return; - } - - // remove from opened stack - SiteDialog.lowerDialog(this); - - Dom.addClass(this.overlay, 'site-dialog-closed'); - Dom.addClass(this.dialog, 'site-dialog-closed'); - - this.state = SiteDialog.STATE_CLOSED; - }; - - proto.closeWithAnimation = function() - { - // TODO: implement animations - this.close(); - }; - - proto.isOpened = function() - { - return (this.state === SiteDialog.STATE_OPENED); - }; - - proto.isClosed = function() - { - return (!this.isOpened()); - }; - - proto.raise = function() - { - if (this.overlay) { - SwatZIndexManager.raiseElement(this.overlay); - } - SwatZIndexManager.raiseElement(this.dialog); - }; - - proto.toggle = function() - { - if (this.isOpened()) { - this.close(); - } else { - this.open(); - } - }; - - proto.toggleWithAnimation = function() - { - if (this.isOpened()) { - this.closeWithAnimation(); - } else { - this.openWithAnimation(); - } - }; - - // }}} - // {{{ Content setup methods - - proto.appendToHeader = function(node) - { - this.header.appendChild(node); - }; - - proto.appendToBody = function(node) - { - this.body.appendChild(node); - }; - - proto.appendToFooter = function(node) - { - this.footer.appendChild(node); - }; - - proto.clearHeader = function() - { - while (this.header.firstChild) { - this.header.removeChild(this.header.firstChild); - } - }; - - proto.clearBody = function() - { - while (this.body.firstChild) { - this.body.removeChild(this.body.firstChild); - } - }; - - proto.clearFooter = function() - { - while (this.footer.firstChild) { - this.footer.removeChild(this.footer.firstChild); - } - }; - - // }}} - - proto.handleLayoutChange = function() - { - // switching from mobile to desktop - if (SiteDialog.is_desktop) { - // remove from stack if opened - if (this.isOpened()) { - SiteDialog.lowerDialog(this); - } - - // close if mobile only - if (this.config.mobile_only) { - this.close(); - } - - // put dialog back in relative container - if (this.config.relative_container) { - this.config.relative_container.appendChild(this.dialog); - } - - // switching from desktop to mobile - } else { - - // put dialog in body - if (this.config.relative_container) { - document.body.appendChild(this.dialog); - } - - // add to stack if opened - if (this.isOpened()) { - SiteDialog.raiseDialog(this); - } - } - }; - - proto.handleResize = function() - { - if (this.isClosed()) { - return; - } - - if (this.config.resize_mode === SiteDialog.RESIZE_FILL || - !SiteDialog.is_desktop) { - var footer_region = Dom.getRegion(this.footer); - - var margin = parseInt(Dom.getStyle(this.container, 'marginTop')) + - parseInt(Dom.getStyle(this.container, 'marginBottom')); - - margin = (isNaN(margin)) ? 0 : margin; - - this.scroll.style.height = ( - Dom.getViewportHeight() - - footer_region.height - - margin - ) + 'px'; - - this.dialog.style.height = Dom.getViewportHeight() + 'px'; - this.dialog.style.top = null; - } else if (this.config.resize_mode === SiteDialog.RESIZE_CENTER) { - this.dialog.style.height = 'auto'; - this.scroll.style.height = 'auto'; - - var margin = parseInt(Dom.getStyle(this.container, 'marginTop')) + - parseInt(Dom.getStyle(this.container, 'marginBottom')); - - margin = (isNaN(margin)) ? 0 : margin; - - var region = Dom.getRegion(this.container); - var viewport = Dom.getViewportHeight(); - - // center vertically in viewport - this.dialog.style.top = - (viewport - region.height - margin) / 2 + 'px'; - } else { - this.dialog.style.height = 'auto'; - this.scroll.style.height = 'auto'; - } - }; - - proto.handleDocumentClick = function(e) - { - if (this.isOpened()) { - var prevent_close = false; - var target = Event.getTarget(e); - while (target.parentNode && !prevent_close) { - if (target === this.dialog || - target === this.config.toggle_element) { - prevent_close = true; - } - target = target.parentNode; - } - - if (!prevent_close) { - this.closeWithAnimation(); - } - } - }; - - proto.handleDocumentKeyDown = function(e) - { - // allow escape key to close dialog - if (this.isOpened() && e.keyCode === 27) { - this.closeWithAnimation(); - } - }; - - proto.handlePopState = function(e) - { - // TODO: push/pop-state is not supported yet. - if (e.state && e.state.id && e.state.id === this.getPushStateId()) { - this.open(); - } else { - this.close(); - } - }; - + var Dom = YAHOO.util.Dom; + var Event = YAHOO.util.Event; + var Anim = YAHOO.util.Anim; + + var proto = SiteDialog.prototype; + + // {{{ Configuration + + var DEFAULT_CONFIG = { + USE_OVERLAY: { + key: 'use_overlay', + value: true, + validator: YAHOO.lang.isBoolean + }, + /** + * Allow clicking outside the dialog to close the dialog. + */ + DISMISSABLE: { + key: 'dismissable', + value: true, + validator: YAHOO.lang.isBoolean + }, + /** + * Only show dialog in mobile layout. If switching back to desktop, the + * dialog is automatically closed. + */ + MOBILE_ONLY: { + key: 'mobile_only', + value: false, + validator: YAHOO.lang.isBoolean + }, + /** + * Use pushState API if available to control opening and closing the + * dialog. + */ + USE_PUSH_STATE: { + key: 'use_push_state', + value: true, + validator: YAHOO.lang.isBoolean + }, + CLASS_NAME: { + key: 'class_name', + value: '' + }, + TOGGLE_ELEMENT: { + key: 'toggle_element', + value: null + }, + RELATIVE_CONTAINER: { + key: 'relative_container', + value: null + }, + RESIZE_MODE: { + key: 'resize_mode', + value: SiteDialog.RESIZE_FILL, + validator: YAHOO.lang.isNumber + } + }; + + proto.initDefaultConfig = function() { + this.config.addProperty(DEFAULT_CONFIG.USE_OVERLAY.key, { + value: DEFAULT_CONFIG.USE_OVERLAY.value, + validator: DEFAULT_CONFIG.USE_OVERLAY.validator + }); + + this.config.addProperty(DEFAULT_CONFIG.DISMISSABLE.key, { + value: DEFAULT_CONFIG.DISMISSABLE.value, + validator: DEFAULT_CONFIG.DISMISSABLE.validator + }); + + this.config.addProperty(DEFAULT_CONFIG.MOBILE_ONLY.key, { + value: DEFAULT_CONFIG.MOBILE_ONLY.value, + validator: DEFAULT_CONFIG.MOBILE_ONLY.validator + }); + + this.config.addProperty(DEFAULT_CONFIG.USE_PUSH_STATE.key, { + value: DEFAULT_CONFIG.USE_PUSH_STATE.value, + validator: DEFAULT_CONFIG.USE_PUSH_STATE.validator + }); + + this.config.addProperty(DEFAULT_CONFIG.CLASS_NAME.key, { + value: DEFAULT_CONFIG.CLASS_NAME.value + }); + + this.config.addProperty(DEFAULT_CONFIG.TOGGLE_ELEMENT.key, { + value: DEFAULT_CONFIG.TOGGLE_ELEMENT.value + }); + + this.config.addProperty(DEFAULT_CONFIG.RESIZE_MODE.key, { + value: DEFAULT_CONFIG.RESIZE_MODE.value, + validator: DEFAULT_CONFIG.RESIZE_MODE.validator + }); + + this.config.addProperty(DEFAULT_CONFIG.RELATIVE_CONTAINER.key, { + value: DEFAULT_CONFIG.RELATIVE_CONTAINER.value + }); + }; + + proto.initConfig = function(user_config) { + this.config = new YAHOO.util.Config(this); + this.initDefaultConfig(); + + // Merge user and default config values. + if (user_config) { + this.config.applyConfig(user_config, true); + } + + // Flatten config object. We're not using events. + this.config = this.config.getConfig(); + }; + + // }}} + + proto.initElements = function(el) { + this.header = this.drawHeader(); + this.body = this.drawBody(); + this.scroll = this.drawScroll(this.header, this.body); + this.footer = this.drawFooter(); + this.container = this.drawContainer(this.scroll, this.footer); + + this.dialog = this.drawDialog(this.container, el); + + this.id = this.dialog.id; + + if (this.config.use_overlay) { + this.overlay = this.drawOverlay(); + } + + this.state = SiteDialog.STATE_OPENED; + this.close(); + + if (this.config.use_overlay) { + document.body.appendChild(this.overlay); + } + + if (this.config.dismissable) { + Event.on(document.body, 'click', this.handleDocumentClick, this, true); + + Event.on( + document.body, + 'keydown', + this.handleDocumentKeyDown, + this, + true + ); + } + + if (SiteDialog.is_desktop && this.config.relative_container) { + this.config.relative_container.appendChild(this.dialog); + } else { + document.body.appendChild(this.dialog); + } + + if (SiteDialog.has_push_state && this.config.use_push_state) { + Event.on(window, 'popstate', this.handlePopState, this, true); + } + }; + + proto.getPushStateId = function() { + return this.id; + }; + + // {{{ Element draw methods + + proto.drawOverlay = function() { + var overlay = document.createElement('div'); + overlay.className = 'site-dialog-overlay'; + return overlay; + }; + + proto.drawDialog = function(container, el) { + var dialog; + + if (el) { + dialog = Dom.get(el); + if (!dialog) { + dialog = document.createElement('div'); + dialog.id = el; + } + } else { + dialog = document.createElement('div'); + } + + Dom.generateId(dialog, 'site-dialog'); + + Dom.addClass(dialog, 'site-dialog-dialog'); + if (this.config.relative_container) { + Dom.addClass(dialog, 'site-dialog-relative'); + } + if (this.config.class_name + '' !== '') { + Dom.addClass(dialog, this.config.class_name); + } + + dialog.appendChild(container); + + return dialog; + }; + + proto.drawScroll = function(header, body) { + var scroll = document.createElement('div'); + scroll.className = 'site-dialog-scroll'; + + scroll.appendChild(header); + scroll.appendChild(body); + + return scroll; + }; + + proto.drawContainer = function(scroll, footer) { + var container = document.createElement('div'); + container.className = 'site-dialog-container'; + + container.appendChild(scroll); + container.appendChild(footer); + + return container; + }; + + proto.drawHeader = function() { + var header = document.createElement('div'); + header.className = 'site-dialog-header'; + return header; + }; + + proto.drawBody = function() { + var body = document.createElement('div'); + body.className = 'site-dialog-body'; + return body; + }; + + proto.drawFooter = function() { + var footer = document.createElement('div'); + footer.className = 'site-dialog-footer'; + return footer; + }; + + // }}} + // {{{ Opening and closing + + proto.open = function() { + if (this.isOpened()) { + return; + } + + this.raise(); + + Dom.removeClass(this.overlay, 'site-dialog-closed'); + Dom.removeClass(this.dialog, 'site-dialog-closed'); + + // need to set state before doing initial positioning + this.state = SiteDialog.STATE_OPENED; + + this.handleResize(); + + // bubble up opened stack if not desktop + if (!SiteDialog.is_desktop) { + SiteDialog.raiseDialog(this); + } + }; + + proto.openWithAnimation = function() { + // TODO: implement animations + this.open(); + }; + + proto.close = function() { + if (this.isClosed()) { + return; + } + + // remove from opened stack + SiteDialog.lowerDialog(this); + + Dom.addClass(this.overlay, 'site-dialog-closed'); + Dom.addClass(this.dialog, 'site-dialog-closed'); + + this.state = SiteDialog.STATE_CLOSED; + }; + + proto.closeWithAnimation = function() { + // TODO: implement animations + this.close(); + }; + + proto.isOpened = function() { + return this.state === SiteDialog.STATE_OPENED; + }; + + proto.isClosed = function() { + return !this.isOpened(); + }; + + proto.raise = function() { + if (this.overlay) { + SwatZIndexManager.raiseElement(this.overlay); + } + SwatZIndexManager.raiseElement(this.dialog); + }; + + proto.toggle = function() { + if (this.isOpened()) { + this.close(); + } else { + this.open(); + } + }; + + proto.toggleWithAnimation = function() { + if (this.isOpened()) { + this.closeWithAnimation(); + } else { + this.openWithAnimation(); + } + }; + + // }}} + // {{{ Content setup methods + + proto.appendToHeader = function(node) { + this.header.appendChild(node); + }; + + proto.appendToBody = function(node) { + this.body.appendChild(node); + }; + + proto.appendToFooter = function(node) { + this.footer.appendChild(node); + }; + + proto.clearHeader = function() { + while (this.header.firstChild) { + this.header.removeChild(this.header.firstChild); + } + }; + + proto.clearBody = function() { + while (this.body.firstChild) { + this.body.removeChild(this.body.firstChild); + } + }; + + proto.clearFooter = function() { + while (this.footer.firstChild) { + this.footer.removeChild(this.footer.firstChild); + } + }; + + // }}} + + proto.handleLayoutChange = function() { + // switching from mobile to desktop + if (SiteDialog.is_desktop) { + // remove from stack if opened + if (this.isOpened()) { + SiteDialog.lowerDialog(this); + } + + // close if mobile only + if (this.config.mobile_only) { + this.close(); + } + + // put dialog back in relative container + if (this.config.relative_container) { + this.config.relative_container.appendChild(this.dialog); + } + + // switching from desktop to mobile + } else { + // put dialog in body + if (this.config.relative_container) { + document.body.appendChild(this.dialog); + } + + // add to stack if opened + if (this.isOpened()) { + SiteDialog.raiseDialog(this); + } + } + }; + + proto.handleResize = function() { + if (this.isClosed()) { + return; + } + + if ( + this.config.resize_mode === SiteDialog.RESIZE_FILL || + !SiteDialog.is_desktop + ) { + var footer_region = Dom.getRegion(this.footer); + + var margin = + parseInt(Dom.getStyle(this.container, 'marginTop')) + + parseInt(Dom.getStyle(this.container, 'marginBottom')); + + margin = isNaN(margin) ? 0 : margin; + + this.scroll.style.height = + Dom.getViewportHeight() - footer_region.height - margin + 'px'; + + this.dialog.style.height = Dom.getViewportHeight() + 'px'; + this.dialog.style.top = null; + } else if (this.config.resize_mode === SiteDialog.RESIZE_CENTER) { + this.dialog.style.height = 'auto'; + this.scroll.style.height = 'auto'; + + var margin = + parseInt(Dom.getStyle(this.container, 'marginTop')) + + parseInt(Dom.getStyle(this.container, 'marginBottom')); + + margin = isNaN(margin) ? 0 : margin; + + var region = Dom.getRegion(this.container); + var viewport = Dom.getViewportHeight(); + + // center vertically in viewport + this.dialog.style.top = (viewport - region.height - margin) / 2 + 'px'; + } else { + this.dialog.style.height = 'auto'; + this.scroll.style.height = 'auto'; + } + }; + + proto.handleDocumentClick = function(e) { + if (this.isOpened()) { + var prevent_close = false; + var target = Event.getTarget(e); + while (target.parentNode && !prevent_close) { + if (target === this.dialog || target === this.config.toggle_element) { + prevent_close = true; + } + target = target.parentNode; + } + + if (!prevent_close) { + this.closeWithAnimation(); + } + } + }; + + proto.handleDocumentKeyDown = function(e) { + // allow escape key to close dialog + if (this.isOpened() && e.keyCode === 27) { + this.closeWithAnimation(); + } + }; + + proto.handlePopState = function(e) { + // TODO: push/pop-state is not supported yet. + if (e.state && e.state.id && e.state.id === this.getPushStateId()) { + this.open(); + } else { + this.close(); + } + }; })(); From 8f8e454f372103806892484c8caec3f3bff97282 Mon Sep 17 00:00:00 2001 From: Michael Gauthier Date: Thu, 19 Mar 2026 22:23:45 -0300 Subject: [PATCH 2/7] Convert to ES6 class --- www/javascript/site-dialog.js | 961 +++++++++++++++++----------------- 1 file changed, 475 insertions(+), 486 deletions(-) diff --git a/www/javascript/site-dialog.js b/www/javascript/site-dialog.js index bbab15a7..b8a44aa1 100644 --- a/www/javascript/site-dialog.js +++ b/www/javascript/site-dialog.js @@ -1,198 +1,9 @@ -var SiteDialog = function(el, user_config) { - SiteDialog.addSentinel(); - - this.initConfig(user_config); - this.initElements(el); - - SiteDialog.dialogs.push(this); -}; - -SiteDialog.STATE_OPENED = 1; -SiteDialog.STATE_CLOSED = 2; -SiteDialog.dialogs = []; -SiteDialog.has_push_state = window.pushState; -SiteDialog.opened_dialog_stack = []; -SiteDialog.desktop_sentinel = null; -SiteDialog.is_desktop = false; -SiteDialog.scroll_top = null; -SiteDialog.resize_debounce_delay = 30; - -SiteDialog.RESIZE_NONE = 0; -SiteDialog.RESIZE_FILL = 1; -SiteDialog.RESIZE_CENTER = 2; - -SiteDialog.updateLayout = function() { - if (SiteDialog.opened_dialog_stack.length === 0) { - for (i = 0; i < document.body.childNodes.length; i++) { - YAHOO.util.Dom.removeClass( - document.body.childNodes[i], - 'site-dialog-hidden' - ); - } - - // Also re-show all dialogs. Needed because relatively positioned - // dialogs may not be children of the body element when updateLayout() - // is called. - for (j = 0; j < SiteDialog.dialogs.length; j++) { - YAHOO.util.Dom.removeClass( - SiteDialog.dialogs[j].dialog, - 'site-dialog-hidden' - ); - if (SiteDialog.dialogs[j].overlay) { - YAHOO.util.Dom.removeClass( - SiteDialog.dialogs[j].overlay, - 'site-dialog-hidden' - ); - } - } - - if (SiteDialog.scroll_top !== null) { - window.scrollTo(0, SiteDialog.scroll_top); - SiteDialog.scroll_top = null; - } - } else { - // save scroll position - SiteDialog.scroll_top = YAHOO.util.Dom.getDocumentScrollTop(); - - window.scrollTo(0, 0); - - var top_index = SiteDialog.opened_dialog_stack.length - 1; - var top_dialog = SiteDialog.opened_dialog_stack[top_index]; - for (i = 0; i < document.body.childNodes.length; i++) { - var node = document.body.childNodes[i]; - - // don't hide the top-level opened dialog - if (node === top_dialog.dialog) { - YAHOO.util.Dom.removeClass(node, 'site-dialog-hidden'); - - // don't hide sentinel - } else if (node !== SiteDialog.desktop_sentinel) { - YAHOO.util.Dom.addClass(node, 'site-dialog-hidden'); - } - } - } -}; - -SiteDialog.raiseDialog = function(dialog) { - var index = null; - for (var i = 0; i < SiteDialog.opened_dialog_stack.length; i++) { - if (SiteDialog.opened_dialog_stack[i] === dialog) { - index = i; - break; - } - } - - if (index !== null) { - SiteDialog.opened_dialog_stack.splice(index, 1); - } - - SiteDialog.opened_dialog_stack.push(dialog); - SiteDialog.updateLayout(); -}; - -SiteDialog.lowerDialog = function(dialog) { - var index = null; - for (var i = 0; i < SiteDialog.opened_dialog_stack.length; i++) { - if (SiteDialog.opened_dialog_stack[i] === dialog) { - index = i; - break; - } - } - - if (index !== null) { - SiteDialog.opened_dialog_stack.splice(index, 1); - } - - SiteDialog.updateLayout(); -}; - -SiteDialog.addSentinel = function() { - if (SiteDialog.desktop_sentinel === null) { - SiteDialog.desktop_sentinel = document.createElement('div'); - SiteDialog.desktop_sentinel.className = 'site-dialog-sentinel'; - document.body.appendChild(SiteDialog.desktop_sentinel); - - var timeout = null; - - var checkSentinel = function() { - var display = YAHOO.util.Dom.getComputedStyle( - SiteDialog.desktop_sentinel, - 'display' - ); - - if (display === 'none' && !SiteDialog.is_desktop) { - // changing from mobile to desktop - SiteDialog.is_desktop = true; - SiteDialog.handleLayoutChange(); - } else if (display === 'block' && SiteDialog.is_desktop) { - // changing from desktop to mobile - SiteDialog.is_desktop = false; - SiteDialog.handleLayoutChange(); - } - }; - - var handleResize = function() { - for (var i = 0; i < SiteDialog.dialogs.length; i++) { - SiteDialog.dialogs[i].handleResize(); - } - }; - - // Initialize layout state - if (YAHOO.util.Dom.hasClass(document.documentElement, 'ie8')) { - // Give IE8 time to load responsive styles before initializing - // mode. It needs to re-download and parse all the CSS. Respond.js - // does not provide an event for this. To do so, we add an element - // to the DOM that has a media query style that sets its display - // to 'none'. We check if the media query has been loaded on a - // short interval. - var mq_detect_el = document.createElement('div'); - mq_detect_el.className = 'site-dialog-mq-detect'; - document.body.appendChild(mq_detect_el); - var mq_detect_interval = setInterval(function() { - var display = YAHOO.util.Dom.getComputedStyle(mq_detect_el, 'display'); - if (display === 'none') { - mq_detect_el.parentNode.removeChild(mq_detect_el); - checkSentinel(); - clearInterval(mq_detect_interval); - mq_detect_interval = null; - } - }, 10); - setTimeout(checkSentinel, 1000); - } else { - checkSentinel(); - } +const SiteDialog = (() => { + const Dom = YAHOO.util.Dom; + const Event = YAHOO.util.Event; + const Anim = YAHOO.util.Anim; - YAHOO.util.Event.on(window, 'resize', function(e) { - // Debounce resize updates so they only fire every at most - // every SiteDialog.resize_debounce_delay ms. - if (timeout) { - clearTimeout(timeout); - } - timeout = setTimeout(function() { - checkSentinel(); - handleResize(); - timeout = null; - }, SiteDialog.resize_debounce_delay); - }); - } -}; - -SiteDialog.handleLayoutChange = function() { - for (var i = 0; i < SiteDialog.dialogs.length; i++) { - SiteDialog.dialogs[i].handleLayoutChange(); - } -}; - -(function() { - var Dom = YAHOO.util.Dom; - var Event = YAHOO.util.Event; - var Anim = YAHOO.util.Anim; - - var proto = SiteDialog.prototype; - - // {{{ Configuration - - var DEFAULT_CONFIG = { + const DEFAULT_CONFIG = { USE_OVERLAY: { key: 'use_overlay', value: true, @@ -238,407 +49,585 @@ SiteDialog.handleLayoutChange = function() { }, RESIZE_MODE: { key: 'resize_mode', - value: SiteDialog.RESIZE_FILL, + value: SiteDialog.RESIZE_FILL, // TODO validator: YAHOO.lang.isNumber } }; - proto.initDefaultConfig = function() { - this.config.addProperty(DEFAULT_CONFIG.USE_OVERLAY.key, { - value: DEFAULT_CONFIG.USE_OVERLAY.value, - validator: DEFAULT_CONFIG.USE_OVERLAY.validator - }); - - this.config.addProperty(DEFAULT_CONFIG.DISMISSABLE.key, { - value: DEFAULT_CONFIG.DISMISSABLE.value, - validator: DEFAULT_CONFIG.DISMISSABLE.validator - }); - - this.config.addProperty(DEFAULT_CONFIG.MOBILE_ONLY.key, { - value: DEFAULT_CONFIG.MOBILE_ONLY.value, - validator: DEFAULT_CONFIG.MOBILE_ONLY.validator - }); - - this.config.addProperty(DEFAULT_CONFIG.USE_PUSH_STATE.key, { - value: DEFAULT_CONFIG.USE_PUSH_STATE.value, - validator: DEFAULT_CONFIG.USE_PUSH_STATE.validator - }); - - this.config.addProperty(DEFAULT_CONFIG.CLASS_NAME.key, { - value: DEFAULT_CONFIG.CLASS_NAME.value - }); - - this.config.addProperty(DEFAULT_CONFIG.TOGGLE_ELEMENT.key, { - value: DEFAULT_CONFIG.TOGGLE_ELEMENT.value - }); - - this.config.addProperty(DEFAULT_CONFIG.RESIZE_MODE.key, { - value: DEFAULT_CONFIG.RESIZE_MODE.value, - validator: DEFAULT_CONFIG.RESIZE_MODE.validator - }); - - this.config.addProperty(DEFAULT_CONFIG.RELATIVE_CONTAINER.key, { - value: DEFAULT_CONFIG.RELATIVE_CONTAINER.value - }); - }; + return class { + static STATE_OPENED = 1; + static STATE_CLOSED = 2; + static dialogs = []; + static has_push_state = window.pushState; + static opened_dialog_stack = []; + static desktop_sentinel = null; + static is_desktop = false; + static scroll_top = null; + static resize_debounce_delay = 30; + + static RESIZE_NONE = 0; + static RESIZE_FILL = 1; + static RESIZE_CENTER = 2; - proto.initConfig = function(user_config) { - this.config = new YAHOO.util.Config(this); - this.initDefaultConfig(); + constructor(el, user_config) { + this.constructor.addSentinel(); - // Merge user and default config values. - if (user_config) { - this.config.applyConfig(user_config, true); + this.initConfig(user_config); + this.initElements(el); + + this.constructor.dialogs.push(this); } - // Flatten config object. We're not using events. - this.config = this.config.getConfig(); - }; + static updateLayout() { + if (this.constructor.opened_dialog_stack.length === 0) { + for (i = 0; i < document.body.childNodes.length; i++) { + YAHOO.util.Dom.removeClass( + document.body.childNodes[i], + 'site-dialog-hidden' + ); + } + + // Also re-show all dialogs. Needed because relatively positioned + // dialogs may not be children of the body element when updateLayout() + // is called. + for (j = 0; j < this.constructor.dialogs.length; j++) { + YAHOO.util.Dom.removeClass( + this.constructor.dialogs[j].dialog, + 'site-dialog-hidden' + ); + if (this.constructor.dialogs[j].overlay) { + YAHOO.util.Dom.removeClass( + this.constructor.dialogs[j].overlay, + 'site-dialog-hidden' + ); + } + } - // }}} + if (this.constructor.scroll_top !== null) { + window.scrollTo(0, this.constructor.scroll_top); + this.constructor.scroll_top = null; + } + } else { + // save scroll position + this.constructor.scroll_top = YAHOO.util.Dom.getDocumentScrollTop(); - proto.initElements = function(el) { - this.header = this.drawHeader(); - this.body = this.drawBody(); - this.scroll = this.drawScroll(this.header, this.body); - this.footer = this.drawFooter(); - this.container = this.drawContainer(this.scroll, this.footer); + window.scrollTo(0, 0); - this.dialog = this.drawDialog(this.container, el); + var top_index = this.constructor.opened_dialog_stack.length - 1; + var top_dialog = this.constructor.opened_dialog_stack[top_index]; + for (i = 0; i < document.body.childNodes.length; i++) { + var node = document.body.childNodes[i]; - this.id = this.dialog.id; + // don't hide the top-level opened dialog + if (node === top_dialog.dialog) { + YAHOO.util.Dom.removeClass(node, 'site-dialog-hidden'); - if (this.config.use_overlay) { - this.overlay = this.drawOverlay(); + // don't hide sentinel + } else if (node !== this.constructor.desktop_sentinel) { + YAHOO.util.Dom.addClass(node, 'site-dialog-hidden'); + } + } + } } - this.state = SiteDialog.STATE_OPENED; - this.close(); + static raiseDialog(dialog) { + var index = null; + for (var i = 0; i < this.constructor.opened_dialog_stack.length; i++) { + if (this.constructor.opened_dialog_stack[i] === dialog) { + index = i; + break; + } + } + + if (index !== null) { + this.constructor.opened_dialog_stack.splice(index, 1); + } - if (this.config.use_overlay) { - document.body.appendChild(this.overlay); + this.constructor.opened_dialog_stack.push(dialog); + this.constructor.updateLayout(); } - if (this.config.dismissable) { - Event.on(document.body, 'click', this.handleDocumentClick, this, true); + static lowerDialog(dialog) { + var index = null; + for (var i = 0; i < this.constructor.opened_dialog_stack.length; i++) { + if (this.constructor.opened_dialog_stack[i] === dialog) { + index = i; + break; + } + } - Event.on( - document.body, - 'keydown', - this.handleDocumentKeyDown, - this, - true - ); - } + if (index !== null) { + this.constructor.opened_dialog_stack.splice(index, 1); + } - if (SiteDialog.is_desktop && this.config.relative_container) { - this.config.relative_container.appendChild(this.dialog); - } else { - document.body.appendChild(this.dialog); + this.constructor.updateLayout(); + } + + static addSentinel() { + if (this.constructor.desktop_sentinel === null) { + this.constructor.desktop_sentinel = document.createElement('div'); + this.constructor.desktop_sentinel.className = 'site-dialog-sentinel'; + document.body.appendChild(this.constructor.desktop_sentinel); + + var timeout = null; + + var checkSentinel = function() { + var display = YAHOO.util.Dom.getComputedStyle( + this.constructor.desktop_sentinel, + 'display' + ); + + if (display === 'none' && !this.constructor.is_desktop) { + // changing from mobile to desktop + this.constructor.is_desktop = true; + this.constructor.handleLayoutChange(); + } else if (display === 'block' && this.constructor.is_desktop) { + // changing from desktop to mobile + this.constructor.is_desktop = false; + this.constructor.handleLayoutChange(); + } + }; + + var handleResize = () => { + for (var i = 0; i < this.constructor.dialogs.length; i++) { + this.constructor.dialogs[i].handleResize(); + } + }; + + // Initialize layout state + if (YAHOO.util.Dom.hasClass(document.documentElement, 'ie8')) { + // Give IE8 time to load responsive styles before initializing + // mode. It needs to re-download and parse all the CSS. Respond.js + // does not provide an event for this. To do so, we add an element + // to the DOM that has a media query style that sets its display + // to 'none'. We check if the media query has been loaded on a + // short interval. + var mq_detect_el = document.createElement('div'); + mq_detect_el.className = 'site-dialog-mq-detect'; + document.body.appendChild(mq_detect_el); + var mq_detect_interval = setInterval(function() { + var display = YAHOO.util.Dom.getComputedStyle( + mq_detect_el, + 'display' + ); + if (display === 'none') { + mq_detect_el.parentNode.removeChild(mq_detect_el); + checkSentinel(); + clearInterval(mq_detect_interval); + mq_detect_interval = null; + } + }, 10); + setTimeout(checkSentinel, 1000); + } else { + checkSentinel(); + } + + YAHOO.util.Event.on(window, 'resize', function(e) { + // Debounce resize updates so they only fire every at most + // every this.constructor.resize_debounce_delay ms. + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(() => { + checkSentinel(); + handleResize(); + timeout = null; + }, this.constructor.resize_debounce_delay); + }); + } } - if (SiteDialog.has_push_state && this.config.use_push_state) { - Event.on(window, 'popstate', this.handlePopState, this, true); + static handleLayoutChange() { + for (var i = 0; i < this.constructor.dialogs.length; i++) { + this.constructor.dialogs[i].handleLayoutChange(); + } } - }; - proto.getPushStateId = function() { - return this.id; - }; + initDefaultConfig() { + this.config.addProperty(DEFAULT_CONFIG.USE_OVERLAY.key, { + value: DEFAULT_CONFIG.USE_OVERLAY.value, + validator: DEFAULT_CONFIG.USE_OVERLAY.validator + }); - // {{{ Element draw methods + this.config.addProperty(DEFAULT_CONFIG.DISMISSABLE.key, { + value: DEFAULT_CONFIG.DISMISSABLE.value, + validator: DEFAULT_CONFIG.DISMISSABLE.validator + }); - proto.drawOverlay = function() { - var overlay = document.createElement('div'); - overlay.className = 'site-dialog-overlay'; - return overlay; - }; + this.config.addProperty(DEFAULT_CONFIG.MOBILE_ONLY.key, { + value: DEFAULT_CONFIG.MOBILE_ONLY.value, + validator: DEFAULT_CONFIG.MOBILE_ONLY.validator + }); - proto.drawDialog = function(container, el) { - var dialog; + this.config.addProperty(DEFAULT_CONFIG.USE_PUSH_STATE.key, { + value: DEFAULT_CONFIG.USE_PUSH_STATE.value, + validator: DEFAULT_CONFIG.USE_PUSH_STATE.validator + }); - if (el) { - dialog = Dom.get(el); - if (!dialog) { - dialog = document.createElement('div'); - dialog.id = el; - } - } else { - dialog = document.createElement('div'); - } + this.config.addProperty(DEFAULT_CONFIG.CLASS_NAME.key, { + value: DEFAULT_CONFIG.CLASS_NAME.value + }); + + this.config.addProperty(DEFAULT_CONFIG.TOGGLE_ELEMENT.key, { + value: DEFAULT_CONFIG.TOGGLE_ELEMENT.value + }); - Dom.generateId(dialog, 'site-dialog'); + this.config.addProperty(DEFAULT_CONFIG.RESIZE_MODE.key, { + value: DEFAULT_CONFIG.RESIZE_MODE.value, + validator: DEFAULT_CONFIG.RESIZE_MODE.validator + }); - Dom.addClass(dialog, 'site-dialog-dialog'); - if (this.config.relative_container) { - Dom.addClass(dialog, 'site-dialog-relative'); + this.config.addProperty(DEFAULT_CONFIG.RELATIVE_CONTAINER.key, { + value: DEFAULT_CONFIG.RELATIVE_CONTAINER.value + }); } - if (this.config.class_name + '' !== '') { - Dom.addClass(dialog, this.config.class_name); + + initConfig(user_config) { + this.config = new YAHOO.util.Config(this); + this.initDefaultConfig(); + + // Merge user and default config values. + if (user_config) { + this.config.applyConfig(user_config, true); + } + + // Flatten config object. We're not using events. + this.config = this.config.getConfig(); } - dialog.appendChild(container); + initElements(el) { + this.header = this.drawHeader(); + this.body = this.drawBody(); + this.scroll = this.drawScroll(this.header, this.body); + this.footer = this.drawFooter(); + this.container = this.drawContainer(this.scroll, this.footer); - return dialog; - }; + this.dialog = this.drawDialog(this.container, el); - proto.drawScroll = function(header, body) { - var scroll = document.createElement('div'); - scroll.className = 'site-dialog-scroll'; + this.id = this.dialog.id; - scroll.appendChild(header); - scroll.appendChild(body); + if (this.config.use_overlay) { + this.overlay = this.drawOverlay(); + } - return scroll; - }; + this.state = this.constructor.STATE_OPENED; + this.close(); - proto.drawContainer = function(scroll, footer) { - var container = document.createElement('div'); - container.className = 'site-dialog-container'; + if (this.config.use_overlay) { + document.body.appendChild(this.overlay); + } - container.appendChild(scroll); - container.appendChild(footer); + if (this.config.dismissable) { + Event.on(document.body, 'click', this.handleDocumentClick, this, true); - return container; - }; + Event.on( + document.body, + 'keydown', + this.handleDocumentKeyDown, + this, + true + ); + } - proto.drawHeader = function() { - var header = document.createElement('div'); - header.className = 'site-dialog-header'; - return header; - }; + if (this.constructor.is_desktop && this.config.relative_container) { + this.config.relative_container.appendChild(this.dialog); + } else { + document.body.appendChild(this.dialog); + } - proto.drawBody = function() { - var body = document.createElement('div'); - body.className = 'site-dialog-body'; - return body; - }; + if (this.constructor.has_push_state && this.config.use_push_state) { + Event.on(window, 'popstate', this.handlePopState, this, true); + } + } - proto.drawFooter = function() { - var footer = document.createElement('div'); - footer.className = 'site-dialog-footer'; - return footer; - }; + getPushStateId() { + return this.id; + } - // }}} - // {{{ Opening and closing + drawOverlay() { + var overlay = document.createElement('div'); + overlay.className = 'site-dialog-overlay'; + return overlay; + } - proto.open = function() { - if (this.isOpened()) { - return; + drawDialog(container, el) { + var dialog; + + if (el) { + dialog = Dom.get(el); + if (!dialog) { + dialog = document.createElement('div'); + dialog.id = el; + } + } else { + dialog = document.createElement('div'); + } + + Dom.generateId(dialog, 'site-dialog'); + + Dom.addClass(dialog, 'site-dialog-dialog'); + if (this.config.relative_container) { + Dom.addClass(dialog, 'site-dialog-relative'); + } + if (this.config.class_name + '' !== '') { + Dom.addClass(dialog, this.config.class_name); + } + + dialog.appendChild(container); + + return dialog; } - this.raise(); + drawScroll(header, body) { + var scroll = document.createElement('div'); + scroll.className = 'site-dialog-scroll'; - Dom.removeClass(this.overlay, 'site-dialog-closed'); - Dom.removeClass(this.dialog, 'site-dialog-closed'); + scroll.appendChild(header); + scroll.appendChild(body); - // need to set state before doing initial positioning - this.state = SiteDialog.STATE_OPENED; + return scroll; + } - this.handleResize(); + drawContainer(scroll, footer) { + var container = document.createElement('div'); + container.className = 'site-dialog-container'; - // bubble up opened stack if not desktop - if (!SiteDialog.is_desktop) { - SiteDialog.raiseDialog(this); + container.appendChild(scroll); + container.appendChild(footer); + + return container; } - }; - proto.openWithAnimation = function() { - // TODO: implement animations - this.open(); - }; + drawHeader() { + var header = document.createElement('div'); + header.className = 'site-dialog-header'; + return header; + } - proto.close = function() { - if (this.isClosed()) { - return; + drawBody() { + var body = document.createElement('div'); + body.className = 'site-dialog-body'; + return body; } - // remove from opened stack - SiteDialog.lowerDialog(this); + drawFooter() { + var footer = document.createElement('div'); + footer.className = 'site-dialog-footer'; + return footer; + } - Dom.addClass(this.overlay, 'site-dialog-closed'); - Dom.addClass(this.dialog, 'site-dialog-closed'); + open() { + if (this.isOpened()) { + return; + } - this.state = SiteDialog.STATE_CLOSED; - }; + this.raise(); - proto.closeWithAnimation = function() { - // TODO: implement animations - this.close(); - }; + Dom.removeClass(this.overlay, 'site-dialog-closed'); + Dom.removeClass(this.dialog, 'site-dialog-closed'); - proto.isOpened = function() { - return this.state === SiteDialog.STATE_OPENED; - }; + // need to set state before doing initial positioning + this.state = this.constructor.STATE_OPENED; - proto.isClosed = function() { - return !this.isOpened(); - }; + this.handleResize(); - proto.raise = function() { - if (this.overlay) { - SwatZIndexManager.raiseElement(this.overlay); + // bubble up opened stack if not desktop + if (!this.constructor.is_desktop) { + this.constructor.raiseDialog(this); + } } - SwatZIndexManager.raiseElement(this.dialog); - }; - proto.toggle = function() { - if (this.isOpened()) { - this.close(); - } else { + openWithAnimation() { + // TODO: implement animations this.open(); } - }; - proto.toggleWithAnimation = function() { - if (this.isOpened()) { - this.closeWithAnimation(); - } else { - this.openWithAnimation(); - } - }; + close() { + if (this.isClosed()) { + return; + } - // }}} - // {{{ Content setup methods + // remove from opened stack + this.constructor.lowerDialog(this); - proto.appendToHeader = function(node) { - this.header.appendChild(node); - }; + Dom.addClass(this.overlay, 'site-dialog-closed'); + Dom.addClass(this.dialog, 'site-dialog-closed'); - proto.appendToBody = function(node) { - this.body.appendChild(node); - }; - - proto.appendToFooter = function(node) { - this.footer.appendChild(node); - }; + this.state = this.constructor.STATE_CLOSED; + } - proto.clearHeader = function() { - while (this.header.firstChild) { - this.header.removeChild(this.header.firstChild); + closeWithAnimation() { + // TODO: implement animations + this.close(); } - }; - proto.clearBody = function() { - while (this.body.firstChild) { - this.body.removeChild(this.body.firstChild); + isOpened() { + return this.state === this.constructor.STATE_OPENED; } - }; - proto.clearFooter = function() { - while (this.footer.firstChild) { - this.footer.removeChild(this.footer.firstChild); + isClosed() { + return !this.isOpened(); } - }; - // }}} + raise() { + if (this.overlay) { + SwatZIndexManager.raiseElement(this.overlay); + } + SwatZIndexManager.raiseElement(this.dialog); + } - proto.handleLayoutChange = function() { - // switching from mobile to desktop - if (SiteDialog.is_desktop) { - // remove from stack if opened + toggle() { if (this.isOpened()) { - SiteDialog.lowerDialog(this); + this.close(); + } else { + this.open(); } + } - // close if mobile only - if (this.config.mobile_only) { - this.close(); + toggleWithAnimation() { + if (this.isOpened()) { + this.closeWithAnimation(); + } else { + this.openWithAnimation(); } + } - // put dialog back in relative container - if (this.config.relative_container) { - this.config.relative_container.appendChild(this.dialog); + appendToHeader(node) { + this.header.appendChild(node); + } + + appendToBody(node) { + this.body.appendChild(node); + } + + appendToFooter(node) { + this.footer.appendChild(node); + } + + clearHeader() { + while (this.header.firstChild) { + this.header.removeChild(this.header.firstChild); } + } - // switching from desktop to mobile - } else { - // put dialog in body - if (this.config.relative_container) { - document.body.appendChild(this.dialog); + clearBody() { + while (this.body.firstChild) { + this.body.removeChild(this.body.firstChild); } + } - // add to stack if opened - if (this.isOpened()) { - SiteDialog.raiseDialog(this); + clearFooter() { + while (this.footer.firstChild) { + this.footer.removeChild(this.footer.firstChild); } } - }; - proto.handleResize = function() { - if (this.isClosed()) { - return; + handleLayoutChange() { + // switching from mobile to desktop + if (this.constructor.is_desktop) { + // remove from stack if opened + if (this.isOpened()) { + this.constructor.lowerDialog(this); + } + + // close if mobile only + if (this.config.mobile_only) { + this.close(); + } + + // put dialog back in relative container + if (this.config.relative_container) { + this.config.relative_container.appendChild(this.dialog); + } + + // switching from desktop to mobile + } else { + // put dialog in body + if (this.config.relative_container) { + document.body.appendChild(this.dialog); + } + + // add to stack if opened + if (this.isOpened()) { + this.constructor.raiseDialog(this); + } + } } - if ( - this.config.resize_mode === SiteDialog.RESIZE_FILL || - !SiteDialog.is_desktop - ) { - var footer_region = Dom.getRegion(this.footer); + handleResize() { + if (this.isClosed()) { + return; + } + + if ( + this.config.resize_mode === this.constructor.RESIZE_FILL || + !this.constructor.is_desktop + ) { + var footer_region = Dom.getRegion(this.footer); - var margin = - parseInt(Dom.getStyle(this.container, 'marginTop')) + - parseInt(Dom.getStyle(this.container, 'marginBottom')); + var margin = + parseInt(Dom.getStyle(this.container, 'marginTop')) + + parseInt(Dom.getStyle(this.container, 'marginBottom')); - margin = isNaN(margin) ? 0 : margin; + margin = isNaN(margin) ? 0 : margin; - this.scroll.style.height = - Dom.getViewportHeight() - footer_region.height - margin + 'px'; + this.scroll.style.height = + Dom.getViewportHeight() - footer_region.height - margin + 'px'; - this.dialog.style.height = Dom.getViewportHeight() + 'px'; - this.dialog.style.top = null; - } else if (this.config.resize_mode === SiteDialog.RESIZE_CENTER) { - this.dialog.style.height = 'auto'; - this.scroll.style.height = 'auto'; + this.dialog.style.height = Dom.getViewportHeight() + 'px'; + this.dialog.style.top = null; + } else if (this.config.resize_mode === this.constructor.RESIZE_CENTER) { + this.dialog.style.height = 'auto'; + this.scroll.style.height = 'auto'; - var margin = - parseInt(Dom.getStyle(this.container, 'marginTop')) + - parseInt(Dom.getStyle(this.container, 'marginBottom')); + var margin = + parseInt(Dom.getStyle(this.container, 'marginTop')) + + parseInt(Dom.getStyle(this.container, 'marginBottom')); - margin = isNaN(margin) ? 0 : margin; + margin = isNaN(margin) ? 0 : margin; - var region = Dom.getRegion(this.container); - var viewport = Dom.getViewportHeight(); + var region = Dom.getRegion(this.container); + var viewport = Dom.getViewportHeight(); - // center vertically in viewport - this.dialog.style.top = (viewport - region.height - margin) / 2 + 'px'; - } else { - this.dialog.style.height = 'auto'; - this.scroll.style.height = 'auto'; + // center vertically in viewport + this.dialog.style.top = (viewport - region.height - margin) / 2 + 'px'; + } else { + this.dialog.style.height = 'auto'; + this.scroll.style.height = 'auto'; + } } - }; - proto.handleDocumentClick = function(e) { - if (this.isOpened()) { - var prevent_close = false; - var target = Event.getTarget(e); - while (target.parentNode && !prevent_close) { - if (target === this.dialog || target === this.config.toggle_element) { - prevent_close = true; + handleDocumentClick(e) { + if (this.isOpened()) { + var prevent_close = false; + var target = Event.getTarget(e); + while (target.parentNode && !prevent_close) { + if (target === this.dialog || target === this.config.toggle_element) { + prevent_close = true; + } + target = target.parentNode; } - target = target.parentNode; - } - if (!prevent_close) { - this.closeWithAnimation(); + if (!prevent_close) { + this.closeWithAnimation(); + } } } - }; - proto.handleDocumentKeyDown = function(e) { - // allow escape key to close dialog - if (this.isOpened() && e.keyCode === 27) { - this.closeWithAnimation(); + handleDocumentKeyDown(e) { + // allow escape key to close dialog + if (this.isOpened() && e.keyCode === 27) { + this.closeWithAnimation(); + } } - }; - proto.handlePopState = function(e) { - // TODO: push/pop-state is not supported yet. - if (e.state && e.state.id && e.state.id === this.getPushStateId()) { - this.open(); - } else { - this.close(); + handlePopState(e) { + // TODO: push/pop-state is not supported yet. + if (e.state && e.state.id && e.state.id === this.getPushStateId()) { + this.open(); + } else { + this.close(); + } } }; })(); From e614a0db3b0f8e412de654391caa601fc8ffb52a Mon Sep 17 00:00:00 2001 From: Michael Gauthier Date: Mon, 23 Mar 2026 22:02:42 -0300 Subject: [PATCH 3/7] Remove use of YUI2 Dom, Event, and Anim --- www/javascript/site-dialog.js | 131 ++++++++++++++++------------------ 1 file changed, 60 insertions(+), 71 deletions(-) diff --git a/www/javascript/site-dialog.js b/www/javascript/site-dialog.js index b8a44aa1..ef816b05 100644 --- a/www/javascript/site-dialog.js +++ b/www/javascript/site-dialog.js @@ -1,8 +1,4 @@ const SiteDialog = (() => { - const Dom = YAHOO.util.Dom; - const Event = YAHOO.util.Event; - const Anim = YAHOO.util.Anim; - const DEFAULT_CONFIG = { USE_OVERLAY: { key: 'use_overlay', @@ -69,6 +65,8 @@ const SiteDialog = (() => { static RESIZE_FILL = 1; static RESIZE_CENTER = 2; + static id_counter = 0; + constructor(el, user_config) { this.constructor.addSentinel(); @@ -80,28 +78,19 @@ const SiteDialog = (() => { static updateLayout() { if (this.constructor.opened_dialog_stack.length === 0) { - for (i = 0; i < document.body.childNodes.length; i++) { - YAHOO.util.Dom.removeClass( - document.body.childNodes[i], - 'site-dialog-hidden' - ); - } + document.body.children.forEach(childEl => { + childEl.classList.removeClass('site-dialog-hidden'); + }); // Also re-show all dialogs. Needed because relatively positioned // dialogs may not be children of the body element when updateLayout() // is called. - for (j = 0; j < this.constructor.dialogs.length; j++) { - YAHOO.util.Dom.removeClass( - this.constructor.dialogs[j].dialog, - 'site-dialog-hidden' - ); - if (this.constructor.dialogs[j].overlay) { - YAHOO.util.Dom.removeClass( - this.constructor.dialogs[j].overlay, - 'site-dialog-hidden' - ); + this.constructor.dialogs.forEach(dialog => { + dialog.classList.remove('site-dialog-hidden'); + if (dialog.overlay) { + dialog.overlay.classList.remove('site-dialog-hidden'); } - } + }); if (this.constructor.scroll_top !== null) { window.scrollTo(0, this.constructor.scroll_top); @@ -109,7 +98,7 @@ const SiteDialog = (() => { } } else { // save scroll position - this.constructor.scroll_top = YAHOO.util.Dom.getDocumentScrollTop(); + this.constructor.scroll_top = window.scrollY; window.scrollTo(0, 0); @@ -118,13 +107,12 @@ const SiteDialog = (() => { for (i = 0; i < document.body.childNodes.length; i++) { var node = document.body.childNodes[i]; - // don't hide the top-level opened dialog if (node === top_dialog.dialog) { - YAHOO.util.Dom.removeClass(node, 'site-dialog-hidden'); - - // don't hide sentinel + // don't hide the top-level opened dialog + node.classList.remove('site-dialog-hidden'); } else if (node !== this.constructor.desktop_sentinel) { - YAHOO.util.Dom.addClass(node, 'site-dialog-hidden'); + // don't hide sentinel + node.classList.add('site-dialog-hidden'); } } } @@ -171,11 +159,10 @@ const SiteDialog = (() => { var timeout = null; - var checkSentinel = function() { - var display = YAHOO.util.Dom.getComputedStyle( - this.constructor.desktop_sentinel, - 'display' - ); + var checkSentinel = () => { + var display = window.getComputedStyle( + this.constructor.desktop_sentinel + ).display; if (display === 'none' && !this.constructor.is_desktop) { // changing from mobile to desktop @@ -189,13 +176,11 @@ const SiteDialog = (() => { }; var handleResize = () => { - for (var i = 0; i < this.constructor.dialogs.length; i++) { - this.constructor.dialogs[i].handleResize(); - } + this.constructor.dialogs.forEach(dialog => dialog.handleResize()); }; // Initialize layout state - if (YAHOO.util.Dom.hasClass(document.documentElement, 'ie8')) { + if (document.documentElement.classList.contains('ie8')) { // Give IE8 time to load responsive styles before initializing // mode. It needs to re-download and parse all the CSS. Respond.js // does not provide an event for this. To do so, we add an element @@ -205,11 +190,8 @@ const SiteDialog = (() => { var mq_detect_el = document.createElement('div'); mq_detect_el.className = 'site-dialog-mq-detect'; document.body.appendChild(mq_detect_el); - var mq_detect_interval = setInterval(function() { - var display = YAHOO.util.Dom.getComputedStyle( - mq_detect_el, - 'display' - ); + var mq_detect_interval = setInterval(() => { + var display = window.getComputedStyle(mq_detect_el).display; if (display === 'none') { mq_detect_el.parentNode.removeChild(mq_detect_el); checkSentinel(); @@ -222,7 +204,7 @@ const SiteDialog = (() => { checkSentinel(); } - YAHOO.util.Event.on(window, 'resize', function(e) { + window.addEventListener('resize', () => { // Debounce resize updates so they only fire every at most // every this.constructor.resize_debounce_delay ms. if (timeout) { @@ -238,9 +220,15 @@ const SiteDialog = (() => { } static handleLayoutChange() { - for (var i = 0; i < this.constructor.dialogs.length; i++) { - this.constructor.dialogs[i].handleLayoutChange(); + this.constructor.dialogs.forEach(dialog => dialog.handleLayoutChange()); + } + + static generateId(el) { + if (!el.id) { + this.constructor.id_counter++; + el.id = 'site-dialog' + this.constructor.id_counter; } + return el.id; } initDefaultConfig() { @@ -318,14 +306,13 @@ const SiteDialog = (() => { } if (this.config.dismissable) { - Event.on(document.body, 'click', this.handleDocumentClick, this, true); - - Event.on( - document.body, + document.body.addEventListener( + 'click', + this.handleDocumentClick.bind(this) + ); + document.body.addEventListener( 'keydown', - this.handleDocumentKeyDown, - this, - true + this.handleDocumentKeyDown.bind(this) ); } @@ -336,7 +323,7 @@ const SiteDialog = (() => { } if (this.constructor.has_push_state && this.config.use_push_state) { - Event.on(window, 'popstate', this.handlePopState, this, true); + window.addEventListener('popstate', this.handlePopState.bind(this)); } } @@ -354,7 +341,7 @@ const SiteDialog = (() => { var dialog; if (el) { - dialog = Dom.get(el); + dialog = document.getElementById(el); if (!dialog) { dialog = document.createElement('div'); dialog.id = el; @@ -363,14 +350,14 @@ const SiteDialog = (() => { dialog = document.createElement('div'); } - Dom.generateId(dialog, 'site-dialog'); + this.constructor.generateId(dialog); - Dom.addClass(dialog, 'site-dialog-dialog'); + dialog.classList.add('site-dialog-dialog'); if (this.config.relative_container) { - Dom.addClass(dialog, 'site-dialog-relative'); + dialog.classList.add('site-dialog-relative'); } if (this.config.class_name + '' !== '') { - Dom.addClass(dialog, this.config.class_name); + dialog.classList.add(this.config.class_name); } dialog.appendChild(container); @@ -423,8 +410,8 @@ const SiteDialog = (() => { this.raise(); - Dom.removeClass(this.overlay, 'site-dialog-closed'); - Dom.removeClass(this.dialog, 'site-dialog-closed'); + this.overlay.classList.remove('site-dialog-closed'); + this.dialog.classList.remove('site-dialog-closed'); // need to set state before doing initial positioning this.state = this.constructor.STATE_OPENED; @@ -450,8 +437,8 @@ const SiteDialog = (() => { // remove from opened stack this.constructor.lowerDialog(this); - Dom.addClass(this.overlay, 'site-dialog-closed'); - Dom.addClass(this.dialog, 'site-dialog-closed'); + this.overlay.classList.add('site-dialog-closed'); + this.dialog.classList.add('site-dialog-closed'); this.state = this.constructor.STATE_CLOSED; } @@ -559,35 +546,37 @@ const SiteDialog = (() => { return; } + var container_style = window.getComputedStyle(this.container); + if ( this.config.resize_mode === this.constructor.RESIZE_FILL || !this.constructor.is_desktop ) { - var footer_region = Dom.getRegion(this.footer); + var footer_region = this.footer.getBoundingClientRect(); var margin = - parseInt(Dom.getStyle(this.container, 'marginTop')) + - parseInt(Dom.getStyle(this.container, 'marginBottom')); + Math.parseInt(container_style.marginTop) + + Math.parseInt(container_style.marginBottom); margin = isNaN(margin) ? 0 : margin; this.scroll.style.height = - Dom.getViewportHeight() - footer_region.height - margin + 'px'; + window.innerHeight - footer_region.height - margin + 'px'; - this.dialog.style.height = Dom.getViewportHeight() + 'px'; + this.dialog.style.height = window.innerHeight + 'px'; this.dialog.style.top = null; } else if (this.config.resize_mode === this.constructor.RESIZE_CENTER) { this.dialog.style.height = 'auto'; this.scroll.style.height = 'auto'; var margin = - parseInt(Dom.getStyle(this.container, 'marginTop')) + - parseInt(Dom.getStyle(this.container, 'marginBottom')); + Math.parseInt(container_style.marginTop) + + Math.parseInt(container_style.marginBottom); margin = isNaN(margin) ? 0 : margin; - var region = Dom.getRegion(this.container); - var viewport = Dom.getViewportHeight(); + var region = this.container.getBoundingClientRect(); + var viewport = window.innerHeight; // center vertically in viewport this.dialog.style.top = (viewport - region.height - margin) / 2 + 'px'; @@ -600,7 +589,7 @@ const SiteDialog = (() => { handleDocumentClick(e) { if (this.isOpened()) { var prevent_close = false; - var target = Event.getTarget(e); + var target = e.target; while (target.parentNode && !prevent_close) { if (target === this.dialog || target === this.config.toggle_element) { prevent_close = true; From ff2f29c2804555e0635a23dd5e12be21478dca0b Mon Sep 17 00:00:00 2001 From: Michael Gauthier Date: Mon, 23 Mar 2026 22:15:35 -0300 Subject: [PATCH 4/7] Replace var with let/const --- www/javascript/site-dialog.js | 101 ++++++++++++++-------------------- 1 file changed, 41 insertions(+), 60 deletions(-) diff --git a/www/javascript/site-dialog.js b/www/javascript/site-dialog.js index ef816b05..391ec11a 100644 --- a/www/javascript/site-dialog.js +++ b/www/javascript/site-dialog.js @@ -102,11 +102,9 @@ const SiteDialog = (() => { window.scrollTo(0, 0); - var top_index = this.constructor.opened_dialog_stack.length - 1; - var top_dialog = this.constructor.opened_dialog_stack[top_index]; - for (i = 0; i < document.body.childNodes.length; i++) { - var node = document.body.childNodes[i]; - + const top_index = this.constructor.opened_dialog_stack.length - 1; + const top_dialog = this.constructor.opened_dialog_stack[top_index]; + document.body.childNodes.forEach(node => { if (node === top_dialog.dialog) { // don't hide the top-level opened dialog node.classList.remove('site-dialog-hidden'); @@ -114,20 +112,16 @@ const SiteDialog = (() => { // don't hide sentinel node.classList.add('site-dialog-hidden'); } - } + }); } } static raiseDialog(dialog) { - var index = null; - for (var i = 0; i < this.constructor.opened_dialog_stack.length; i++) { - if (this.constructor.opened_dialog_stack[i] === dialog) { - index = i; - break; - } - } + const index = this.constructor.opened_dialog_stack.findIndex( + opened_dialog => opened_dialog === dialog + ); - if (index !== null) { + if (index !== -1) { this.constructor.opened_dialog_stack.splice(index, 1); } @@ -136,15 +130,11 @@ const SiteDialog = (() => { } static lowerDialog(dialog) { - var index = null; - for (var i = 0; i < this.constructor.opened_dialog_stack.length; i++) { - if (this.constructor.opened_dialog_stack[i] === dialog) { - index = i; - break; - } - } + const index = this.constructor.opened_dialog_stack.findIndex( + opened_dialog => opened_dialog === dialog + ); - if (index !== null) { + if (index !== -1) { this.constructor.opened_dialog_stack.splice(index, 1); } @@ -157,10 +147,10 @@ const SiteDialog = (() => { this.constructor.desktop_sentinel.className = 'site-dialog-sentinel'; document.body.appendChild(this.constructor.desktop_sentinel); - var timeout = null; + let timeout = null; - var checkSentinel = () => { - var display = window.getComputedStyle( + const checkSentinel = () => { + const display = window.getComputedStyle( this.constructor.desktop_sentinel ).display; @@ -175,7 +165,7 @@ const SiteDialog = (() => { } }; - var handleResize = () => { + const handleResize = () => { this.constructor.dialogs.forEach(dialog => dialog.handleResize()); }; @@ -187,11 +177,11 @@ const SiteDialog = (() => { // to the DOM that has a media query style that sets its display // to 'none'. We check if the media query has been loaded on a // short interval. - var mq_detect_el = document.createElement('div'); + const mq_detect_el = document.createElement('div'); mq_detect_el.className = 'site-dialog-mq-detect'; document.body.appendChild(mq_detect_el); - var mq_detect_interval = setInterval(() => { - var display = window.getComputedStyle(mq_detect_el).display; + const mq_detect_interval = setInterval(() => { + const display = window.getComputedStyle(mq_detect_el).display; if (display === 'none') { mq_detect_el.parentNode.removeChild(mq_detect_el); checkSentinel(); @@ -224,11 +214,8 @@ const SiteDialog = (() => { } static generateId(el) { - if (!el.id) { - this.constructor.id_counter++; - el.id = 'site-dialog' + this.constructor.id_counter; - } - return el.id; + this.constructor.id_counter++; + return 'site-dialog' + this.constructor.id_counter; } initDefaultConfig() { @@ -332,25 +319,19 @@ const SiteDialog = (() => { } drawOverlay() { - var overlay = document.createElement('div'); + const overlay = document.createElement('div'); overlay.className = 'site-dialog-overlay'; return overlay; } drawDialog(container, el) { - var dialog; - - if (el) { - dialog = document.getElementById(el); - if (!dialog) { - dialog = document.createElement('div'); - dialog.id = el; - } - } else { - dialog = document.createElement('div'); - } + const dialog_id = el === null ? this.constructor.generateId() : el; - this.constructor.generateId(dialog); + // If el is null or not found, create a dialog element. Otherwise use the + // element by id. + const dialog = + (el === null ? null : document.getElementById(el)) ?? + Object.assign(document.createElement('div'), { id: dialog_id }); dialog.classList.add('site-dialog-dialog'); if (this.config.relative_container) { @@ -366,7 +347,7 @@ const SiteDialog = (() => { } drawScroll(header, body) { - var scroll = document.createElement('div'); + const scroll = document.createElement('div'); scroll.className = 'site-dialog-scroll'; scroll.appendChild(header); @@ -376,7 +357,7 @@ const SiteDialog = (() => { } drawContainer(scroll, footer) { - var container = document.createElement('div'); + const container = document.createElement('div'); container.className = 'site-dialog-container'; container.appendChild(scroll); @@ -386,19 +367,19 @@ const SiteDialog = (() => { } drawHeader() { - var header = document.createElement('div'); + const header = document.createElement('div'); header.className = 'site-dialog-header'; return header; } drawBody() { - var body = document.createElement('div'); + const body = document.createElement('div'); body.className = 'site-dialog-body'; return body; } drawFooter() { - var footer = document.createElement('div'); + const footer = document.createElement('div'); footer.className = 'site-dialog-footer'; return footer; } @@ -546,15 +527,15 @@ const SiteDialog = (() => { return; } - var container_style = window.getComputedStyle(this.container); + const container_style = window.getComputedStyle(this.container); if ( this.config.resize_mode === this.constructor.RESIZE_FILL || !this.constructor.is_desktop ) { - var footer_region = this.footer.getBoundingClientRect(); + const footer_region = this.footer.getBoundingClientRect(); - var margin = + let margin = Math.parseInt(container_style.marginTop) + Math.parseInt(container_style.marginBottom); @@ -569,14 +550,14 @@ const SiteDialog = (() => { this.dialog.style.height = 'auto'; this.scroll.style.height = 'auto'; - var margin = + let margin = Math.parseInt(container_style.marginTop) + Math.parseInt(container_style.marginBottom); margin = isNaN(margin) ? 0 : margin; - var region = this.container.getBoundingClientRect(); - var viewport = window.innerHeight; + const region = this.container.getBoundingClientRect(); + const viewport = window.innerHeight; // center vertically in viewport this.dialog.style.top = (viewport - region.height - margin) / 2 + 'px'; @@ -588,8 +569,8 @@ const SiteDialog = (() => { handleDocumentClick(e) { if (this.isOpened()) { - var prevent_close = false; - var target = e.target; + const prevent_close = false; + let target = e.target; while (target.parentNode && !prevent_close) { if (target === this.dialog || target === this.config.toggle_element) { prevent_close = true; From 82be127e55d8c31bf00ab068232f272631df0d69 Mon Sep 17 00:00:00 2001 From: Michael Gauthier Date: Mon, 23 Mar 2026 22:16:20 -0300 Subject: [PATCH 5/7] Remove ie8 hacks --- www/javascript/site-dialog.js | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/www/javascript/site-dialog.js b/www/javascript/site-dialog.js index 391ec11a..de7e7208 100644 --- a/www/javascript/site-dialog.js +++ b/www/javascript/site-dialog.js @@ -170,29 +170,7 @@ const SiteDialog = (() => { }; // Initialize layout state - if (document.documentElement.classList.contains('ie8')) { - // Give IE8 time to load responsive styles before initializing - // mode. It needs to re-download and parse all the CSS. Respond.js - // does not provide an event for this. To do so, we add an element - // to the DOM that has a media query style that sets its display - // to 'none'. We check if the media query has been loaded on a - // short interval. - const mq_detect_el = document.createElement('div'); - mq_detect_el.className = 'site-dialog-mq-detect'; - document.body.appendChild(mq_detect_el); - const mq_detect_interval = setInterval(() => { - const display = window.getComputedStyle(mq_detect_el).display; - if (display === 'none') { - mq_detect_el.parentNode.removeChild(mq_detect_el); - checkSentinel(); - clearInterval(mq_detect_interval); - mq_detect_interval = null; - } - }, 10); - setTimeout(checkSentinel, 1000); - } else { - checkSentinel(); - } + checkSentinel(); window.addEventListener('resize', () => { // Debounce resize updates so they only fire every at most From c29fb30749b9eee41083798dd4a334a95acd6799 Mon Sep 17 00:00:00 2001 From: Michael Gauthier Date: Mon, 23 Mar 2026 22:25:40 -0300 Subject: [PATCH 6/7] Remove YAHOO.util.Config --- www/javascript/site-dialog.js | 124 +++++++--------------------------- 1 file changed, 23 insertions(+), 101 deletions(-) diff --git a/www/javascript/site-dialog.js b/www/javascript/site-dialog.js index de7e7208..1697b3d3 100644 --- a/www/javascript/site-dialog.js +++ b/www/javascript/site-dialog.js @@ -1,55 +1,4 @@ const SiteDialog = (() => { - const DEFAULT_CONFIG = { - USE_OVERLAY: { - key: 'use_overlay', - value: true, - validator: YAHOO.lang.isBoolean - }, - /** - * Allow clicking outside the dialog to close the dialog. - */ - DISMISSABLE: { - key: 'dismissable', - value: true, - validator: YAHOO.lang.isBoolean - }, - /** - * Only show dialog in mobile layout. If switching back to desktop, the - * dialog is automatically closed. - */ - MOBILE_ONLY: { - key: 'mobile_only', - value: false, - validator: YAHOO.lang.isBoolean - }, - /** - * Use pushState API if available to control opening and closing the - * dialog. - */ - USE_PUSH_STATE: { - key: 'use_push_state', - value: true, - validator: YAHOO.lang.isBoolean - }, - CLASS_NAME: { - key: 'class_name', - value: '' - }, - TOGGLE_ELEMENT: { - key: 'toggle_element', - value: null - }, - RELATIVE_CONTAINER: { - key: 'relative_container', - value: null - }, - RESIZE_MODE: { - key: 'resize_mode', - value: SiteDialog.RESIZE_FILL, // TODO - validator: YAHOO.lang.isNumber - } - }; - return class { static STATE_OPENED = 1; static STATE_CLOSED = 2; @@ -191,61 +140,34 @@ const SiteDialog = (() => { this.constructor.dialogs.forEach(dialog => dialog.handleLayoutChange()); } - static generateId(el) { + static generateId() { this.constructor.id_counter++; return 'site-dialog' + this.constructor.id_counter; } - initDefaultConfig() { - this.config.addProperty(DEFAULT_CONFIG.USE_OVERLAY.key, { - value: DEFAULT_CONFIG.USE_OVERLAY.value, - validator: DEFAULT_CONFIG.USE_OVERLAY.validator - }); - - this.config.addProperty(DEFAULT_CONFIG.DISMISSABLE.key, { - value: DEFAULT_CONFIG.DISMISSABLE.value, - validator: DEFAULT_CONFIG.DISMISSABLE.validator - }); - - this.config.addProperty(DEFAULT_CONFIG.MOBILE_ONLY.key, { - value: DEFAULT_CONFIG.MOBILE_ONLY.value, - validator: DEFAULT_CONFIG.MOBILE_ONLY.validator - }); - - this.config.addProperty(DEFAULT_CONFIG.USE_PUSH_STATE.key, { - value: DEFAULT_CONFIG.USE_PUSH_STATE.value, - validator: DEFAULT_CONFIG.USE_PUSH_STATE.validator - }); - - this.config.addProperty(DEFAULT_CONFIG.CLASS_NAME.key, { - value: DEFAULT_CONFIG.CLASS_NAME.value - }); - - this.config.addProperty(DEFAULT_CONFIG.TOGGLE_ELEMENT.key, { - value: DEFAULT_CONFIG.TOGGLE_ELEMENT.value - }); - - this.config.addProperty(DEFAULT_CONFIG.RESIZE_MODE.key, { - value: DEFAULT_CONFIG.RESIZE_MODE.value, - validator: DEFAULT_CONFIG.RESIZE_MODE.validator - }); - - this.config.addProperty(DEFAULT_CONFIG.RELATIVE_CONTAINER.key, { - value: DEFAULT_CONFIG.RELATIVE_CONTAINER.value - }); - } - initConfig(user_config) { - this.config = new YAHOO.util.Config(this); - this.initDefaultConfig(); - - // Merge user and default config values. - if (user_config) { - this.config.applyConfig(user_config, true); - } - - // Flatten config object. We're not using events. - this.config = this.config.getConfig(); + this.config = { + use_overlay: true, + /** + * Allow clicking outside the dialog to close the dialog. + */ + dismissable: true, + /** + * Only show dialog in mobile layout. If switching back to desktop, the + * dialog is automatically closed. + */ + mobile_only: false, + /** + * Use pushState API if available to control opening and closing the + * dialog. + */ + use_push_state: true, + class_name: '', + toggle_element: null, + relative_container: null, + resize_mode: SiteDialog.RESIZE_FILL, + ...user_config + }; } initElements(el) { From 095bf2a5f9a7520bf18f49e5137b861558191f5f Mon Sep 17 00:00:00 2001 From: Michael Gauthier Date: Mon, 23 Mar 2026 22:26:34 -0300 Subject: [PATCH 7/7] Remove unnecessary IIFE --- www/javascript/site-dialog.js | 806 +++++++++++++++++----------------- 1 file changed, 402 insertions(+), 404 deletions(-) diff --git a/www/javascript/site-dialog.js b/www/javascript/site-dialog.js index 1697b3d3..7e2eaf79 100644 --- a/www/javascript/site-dialog.js +++ b/www/javascript/site-dialog.js @@ -1,503 +1,501 @@ -const SiteDialog = (() => { - return class { - static STATE_OPENED = 1; - static STATE_CLOSED = 2; - static dialogs = []; - static has_push_state = window.pushState; - static opened_dialog_stack = []; - static desktop_sentinel = null; - static is_desktop = false; - static scroll_top = null; - static resize_debounce_delay = 30; - - static RESIZE_NONE = 0; - static RESIZE_FILL = 1; - static RESIZE_CENTER = 2; - - static id_counter = 0; - - constructor(el, user_config) { - this.constructor.addSentinel(); - - this.initConfig(user_config); - this.initElements(el); - - this.constructor.dialogs.push(this); - } - - static updateLayout() { - if (this.constructor.opened_dialog_stack.length === 0) { - document.body.children.forEach(childEl => { - childEl.classList.removeClass('site-dialog-hidden'); - }); - - // Also re-show all dialogs. Needed because relatively positioned - // dialogs may not be children of the body element when updateLayout() - // is called. - this.constructor.dialogs.forEach(dialog => { - dialog.classList.remove('site-dialog-hidden'); - if (dialog.overlay) { - dialog.overlay.classList.remove('site-dialog-hidden'); - } - }); - - if (this.constructor.scroll_top !== null) { - window.scrollTo(0, this.constructor.scroll_top); - this.constructor.scroll_top = null; +class SiteDialog { + static STATE_OPENED = 1; + static STATE_CLOSED = 2; + static dialogs = []; + static has_push_state = window.pushState; + static opened_dialog_stack = []; + static desktop_sentinel = null; + static is_desktop = false; + static scroll_top = null; + static resize_debounce_delay = 30; + + static RESIZE_NONE = 0; + static RESIZE_FILL = 1; + static RESIZE_CENTER = 2; + + static id_counter = 0; + + constructor(el, user_config) { + this.constructor.addSentinel(); + + this.initConfig(user_config); + this.initElements(el); + + this.constructor.dialogs.push(this); + } + + static updateLayout() { + if (this.constructor.opened_dialog_stack.length === 0) { + document.body.children.forEach(childEl => { + childEl.classList.removeClass('site-dialog-hidden'); + }); + + // Also re-show all dialogs. Needed because relatively positioned + // dialogs may not be children of the body element when updateLayout() + // is called. + this.constructor.dialogs.forEach(dialog => { + dialog.classList.remove('site-dialog-hidden'); + if (dialog.overlay) { + dialog.overlay.classList.remove('site-dialog-hidden'); } - } else { - // save scroll position - this.constructor.scroll_top = window.scrollY; - - window.scrollTo(0, 0); - - const top_index = this.constructor.opened_dialog_stack.length - 1; - const top_dialog = this.constructor.opened_dialog_stack[top_index]; - document.body.childNodes.forEach(node => { - if (node === top_dialog.dialog) { - // don't hide the top-level opened dialog - node.classList.remove('site-dialog-hidden'); - } else if (node !== this.constructor.desktop_sentinel) { - // don't hide sentinel - node.classList.add('site-dialog-hidden'); - } - }); + }); + + if (this.constructor.scroll_top !== null) { + window.scrollTo(0, this.constructor.scroll_top); + this.constructor.scroll_top = null; } + } else { + // save scroll position + this.constructor.scroll_top = window.scrollY; + + window.scrollTo(0, 0); + + const top_index = this.constructor.opened_dialog_stack.length - 1; + const top_dialog = this.constructor.opened_dialog_stack[top_index]; + document.body.childNodes.forEach(node => { + if (node === top_dialog.dialog) { + // don't hide the top-level opened dialog + node.classList.remove('site-dialog-hidden'); + } else if (node !== this.constructor.desktop_sentinel) { + // don't hide sentinel + node.classList.add('site-dialog-hidden'); + } + }); } + } - static raiseDialog(dialog) { - const index = this.constructor.opened_dialog_stack.findIndex( - opened_dialog => opened_dialog === dialog - ); - - if (index !== -1) { - this.constructor.opened_dialog_stack.splice(index, 1); - } + static raiseDialog(dialog) { + const index = this.constructor.opened_dialog_stack.findIndex( + opened_dialog => opened_dialog === dialog + ); - this.constructor.opened_dialog_stack.push(dialog); - this.constructor.updateLayout(); + if (index !== -1) { + this.constructor.opened_dialog_stack.splice(index, 1); } - static lowerDialog(dialog) { - const index = this.constructor.opened_dialog_stack.findIndex( - opened_dialog => opened_dialog === dialog - ); + this.constructor.opened_dialog_stack.push(dialog); + this.constructor.updateLayout(); + } - if (index !== -1) { - this.constructor.opened_dialog_stack.splice(index, 1); - } + static lowerDialog(dialog) { + const index = this.constructor.opened_dialog_stack.findIndex( + opened_dialog => opened_dialog === dialog + ); - this.constructor.updateLayout(); + if (index !== -1) { + this.constructor.opened_dialog_stack.splice(index, 1); } - static addSentinel() { - if (this.constructor.desktop_sentinel === null) { - this.constructor.desktop_sentinel = document.createElement('div'); - this.constructor.desktop_sentinel.className = 'site-dialog-sentinel'; - document.body.appendChild(this.constructor.desktop_sentinel); - - let timeout = null; - - const checkSentinel = () => { - const display = window.getComputedStyle( - this.constructor.desktop_sentinel - ).display; - - if (display === 'none' && !this.constructor.is_desktop) { - // changing from mobile to desktop - this.constructor.is_desktop = true; - this.constructor.handleLayoutChange(); - } else if (display === 'block' && this.constructor.is_desktop) { - // changing from desktop to mobile - this.constructor.is_desktop = false; - this.constructor.handleLayoutChange(); - } - }; - - const handleResize = () => { - this.constructor.dialogs.forEach(dialog => dialog.handleResize()); - }; - - // Initialize layout state - checkSentinel(); - - window.addEventListener('resize', () => { - // Debounce resize updates so they only fire every at most - // every this.constructor.resize_debounce_delay ms. - if (timeout) { - clearTimeout(timeout); - } - timeout = setTimeout(() => { - checkSentinel(); - handleResize(); - timeout = null; - }, this.constructor.resize_debounce_delay); - }); - } - } + this.constructor.updateLayout(); + } - static handleLayoutChange() { - this.constructor.dialogs.forEach(dialog => dialog.handleLayoutChange()); - } + static addSentinel() { + if (this.constructor.desktop_sentinel === null) { + this.constructor.desktop_sentinel = document.createElement('div'); + this.constructor.desktop_sentinel.className = 'site-dialog-sentinel'; + document.body.appendChild(this.constructor.desktop_sentinel); - static generateId() { - this.constructor.id_counter++; - return 'site-dialog' + this.constructor.id_counter; - } + let timeout = null; - initConfig(user_config) { - this.config = { - use_overlay: true, - /** - * Allow clicking outside the dialog to close the dialog. - */ - dismissable: true, - /** - * Only show dialog in mobile layout. If switching back to desktop, the - * dialog is automatically closed. - */ - mobile_only: false, - /** - * Use pushState API if available to control opening and closing the - * dialog. - */ - use_push_state: true, - class_name: '', - toggle_element: null, - relative_container: null, - resize_mode: SiteDialog.RESIZE_FILL, - ...user_config + const checkSentinel = () => { + const display = window.getComputedStyle( + this.constructor.desktop_sentinel + ).display; + + if (display === 'none' && !this.constructor.is_desktop) { + // changing from mobile to desktop + this.constructor.is_desktop = true; + this.constructor.handleLayoutChange(); + } else if (display === 'block' && this.constructor.is_desktop) { + // changing from desktop to mobile + this.constructor.is_desktop = false; + this.constructor.handleLayoutChange(); + } }; - } - initElements(el) { - this.header = this.drawHeader(); - this.body = this.drawBody(); - this.scroll = this.drawScroll(this.header, this.body); - this.footer = this.drawFooter(); - this.container = this.drawContainer(this.scroll, this.footer); + const handleResize = () => { + this.constructor.dialogs.forEach(dialog => dialog.handleResize()); + }; - this.dialog = this.drawDialog(this.container, el); + // Initialize layout state + checkSentinel(); - this.id = this.dialog.id; + window.addEventListener('resize', () => { + // Debounce resize updates so they only fire every at most + // every this.constructor.resize_debounce_delay ms. + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(() => { + checkSentinel(); + handleResize(); + timeout = null; + }, this.constructor.resize_debounce_delay); + }); + } + } + + static handleLayoutChange() { + this.constructor.dialogs.forEach(dialog => dialog.handleLayoutChange()); + } + + static generateId() { + this.constructor.id_counter++; + return 'site-dialog' + this.constructor.id_counter; + } + + initConfig(user_config) { + this.config = { + use_overlay: true, + /** + * Allow clicking outside the dialog to close the dialog. + */ + dismissable: true, + /** + * Only show dialog in mobile layout. If switching back to desktop, the + * dialog is automatically closed. + */ + mobile_only: false, + /** + * Use pushState API if available to control opening and closing the + * dialog. + */ + use_push_state: true, + class_name: '', + toggle_element: null, + relative_container: null, + resize_mode: SiteDialog.RESIZE_FILL, + ...user_config + }; + } + + initElements(el) { + this.header = this.drawHeader(); + this.body = this.drawBody(); + this.scroll = this.drawScroll(this.header, this.body); + this.footer = this.drawFooter(); + this.container = this.drawContainer(this.scroll, this.footer); + + this.dialog = this.drawDialog(this.container, el); + + this.id = this.dialog.id; + + if (this.config.use_overlay) { + this.overlay = this.drawOverlay(); + } + + this.state = this.constructor.STATE_OPENED; + this.close(); + + if (this.config.use_overlay) { + document.body.appendChild(this.overlay); + } + + if (this.config.dismissable) { + document.body.addEventListener( + 'click', + this.handleDocumentClick.bind(this) + ); + document.body.addEventListener( + 'keydown', + this.handleDocumentKeyDown.bind(this) + ); + } - if (this.config.use_overlay) { - this.overlay = this.drawOverlay(); - } + if (this.constructor.is_desktop && this.config.relative_container) { + this.config.relative_container.appendChild(this.dialog); + } else { + document.body.appendChild(this.dialog); + } - this.state = this.constructor.STATE_OPENED; - this.close(); + if (this.constructor.has_push_state && this.config.use_push_state) { + window.addEventListener('popstate', this.handlePopState.bind(this)); + } + } - if (this.config.use_overlay) { - document.body.appendChild(this.overlay); - } + getPushStateId() { + return this.id; + } - if (this.config.dismissable) { - document.body.addEventListener( - 'click', - this.handleDocumentClick.bind(this) - ); - document.body.addEventListener( - 'keydown', - this.handleDocumentKeyDown.bind(this) - ); - } + drawOverlay() { + const overlay = document.createElement('div'); + overlay.className = 'site-dialog-overlay'; + return overlay; + } - if (this.constructor.is_desktop && this.config.relative_container) { - this.config.relative_container.appendChild(this.dialog); - } else { - document.body.appendChild(this.dialog); - } + drawDialog(container, el) { + const dialog_id = el === null ? this.constructor.generateId() : el; - if (this.constructor.has_push_state && this.config.use_push_state) { - window.addEventListener('popstate', this.handlePopState.bind(this)); - } - } + // If el is null or not found, create a dialog element. Otherwise use the + // element by id. + const dialog = + (el === null ? null : document.getElementById(el)) ?? + Object.assign(document.createElement('div'), { id: dialog_id }); - getPushStateId() { - return this.id; + dialog.classList.add('site-dialog-dialog'); + if (this.config.relative_container) { + dialog.classList.add('site-dialog-relative'); } - - drawOverlay() { - const overlay = document.createElement('div'); - overlay.className = 'site-dialog-overlay'; - return overlay; + if (this.config.class_name + '' !== '') { + dialog.classList.add(this.config.class_name); } - drawDialog(container, el) { - const dialog_id = el === null ? this.constructor.generateId() : el; - - // If el is null or not found, create a dialog element. Otherwise use the - // element by id. - const dialog = - (el === null ? null : document.getElementById(el)) ?? - Object.assign(document.createElement('div'), { id: dialog_id }); + dialog.appendChild(container); - dialog.classList.add('site-dialog-dialog'); - if (this.config.relative_container) { - dialog.classList.add('site-dialog-relative'); - } - if (this.config.class_name + '' !== '') { - dialog.classList.add(this.config.class_name); - } + return dialog; + } - dialog.appendChild(container); + drawScroll(header, body) { + const scroll = document.createElement('div'); + scroll.className = 'site-dialog-scroll'; - return dialog; - } + scroll.appendChild(header); + scroll.appendChild(body); - drawScroll(header, body) { - const scroll = document.createElement('div'); - scroll.className = 'site-dialog-scroll'; + return scroll; + } - scroll.appendChild(header); - scroll.appendChild(body); + drawContainer(scroll, footer) { + const container = document.createElement('div'); + container.className = 'site-dialog-container'; - return scroll; - } + container.appendChild(scroll); + container.appendChild(footer); - drawContainer(scroll, footer) { - const container = document.createElement('div'); - container.className = 'site-dialog-container'; + return container; + } - container.appendChild(scroll); - container.appendChild(footer); + drawHeader() { + const header = document.createElement('div'); + header.className = 'site-dialog-header'; + return header; + } - return container; - } + drawBody() { + const body = document.createElement('div'); + body.className = 'site-dialog-body'; + return body; + } - drawHeader() { - const header = document.createElement('div'); - header.className = 'site-dialog-header'; - return header; - } + drawFooter() { + const footer = document.createElement('div'); + footer.className = 'site-dialog-footer'; + return footer; + } - drawBody() { - const body = document.createElement('div'); - body.className = 'site-dialog-body'; - return body; + open() { + if (this.isOpened()) { + return; } - drawFooter() { - const footer = document.createElement('div'); - footer.className = 'site-dialog-footer'; - return footer; - } + this.raise(); - open() { - if (this.isOpened()) { - return; - } + this.overlay.classList.remove('site-dialog-closed'); + this.dialog.classList.remove('site-dialog-closed'); - this.raise(); + // need to set state before doing initial positioning + this.state = this.constructor.STATE_OPENED; - this.overlay.classList.remove('site-dialog-closed'); - this.dialog.classList.remove('site-dialog-closed'); + this.handleResize(); - // need to set state before doing initial positioning - this.state = this.constructor.STATE_OPENED; + // bubble up opened stack if not desktop + if (!this.constructor.is_desktop) { + this.constructor.raiseDialog(this); + } + } - this.handleResize(); + openWithAnimation() { + // TODO: implement animations + this.open(); + } - // bubble up opened stack if not desktop - if (!this.constructor.is_desktop) { - this.constructor.raiseDialog(this); - } + close() { + if (this.isClosed()) { + return; } - openWithAnimation() { - // TODO: implement animations - this.open(); - } + // remove from opened stack + this.constructor.lowerDialog(this); - close() { - if (this.isClosed()) { - return; - } + this.overlay.classList.add('site-dialog-closed'); + this.dialog.classList.add('site-dialog-closed'); - // remove from opened stack - this.constructor.lowerDialog(this); + this.state = this.constructor.STATE_CLOSED; + } - this.overlay.classList.add('site-dialog-closed'); - this.dialog.classList.add('site-dialog-closed'); + closeWithAnimation() { + // TODO: implement animations + this.close(); + } - this.state = this.constructor.STATE_CLOSED; - } + isOpened() { + return this.state === this.constructor.STATE_OPENED; + } - closeWithAnimation() { - // TODO: implement animations - this.close(); - } + isClosed() { + return !this.isOpened(); + } - isOpened() { - return this.state === this.constructor.STATE_OPENED; + raise() { + if (this.overlay) { + SwatZIndexManager.raiseElement(this.overlay); } + SwatZIndexManager.raiseElement(this.dialog); + } - isClosed() { - return !this.isOpened(); + toggle() { + if (this.isOpened()) { + this.close(); + } else { + this.open(); } + } - raise() { - if (this.overlay) { - SwatZIndexManager.raiseElement(this.overlay); - } - SwatZIndexManager.raiseElement(this.dialog); + toggleWithAnimation() { + if (this.isOpened()) { + this.closeWithAnimation(); + } else { + this.openWithAnimation(); } + } - toggle() { - if (this.isOpened()) { - this.close(); - } else { - this.open(); - } - } + appendToHeader(node) { + this.header.appendChild(node); + } - toggleWithAnimation() { - if (this.isOpened()) { - this.closeWithAnimation(); - } else { - this.openWithAnimation(); - } - } + appendToBody(node) { + this.body.appendChild(node); + } - appendToHeader(node) { - this.header.appendChild(node); - } + appendToFooter(node) { + this.footer.appendChild(node); + } - appendToBody(node) { - this.body.appendChild(node); + clearHeader() { + while (this.header.firstChild) { + this.header.removeChild(this.header.firstChild); } + } - appendToFooter(node) { - this.footer.appendChild(node); + clearBody() { + while (this.body.firstChild) { + this.body.removeChild(this.body.firstChild); } + } - clearHeader() { - while (this.header.firstChild) { - this.header.removeChild(this.header.firstChild); - } + clearFooter() { + while (this.footer.firstChild) { + this.footer.removeChild(this.footer.firstChild); } + } - clearBody() { - while (this.body.firstChild) { - this.body.removeChild(this.body.firstChild); + handleLayoutChange() { + // switching from mobile to desktop + if (this.constructor.is_desktop) { + // remove from stack if opened + if (this.isOpened()) { + this.constructor.lowerDialog(this); } - } - clearFooter() { - while (this.footer.firstChild) { - this.footer.removeChild(this.footer.firstChild); + // close if mobile only + if (this.config.mobile_only) { + this.close(); } - } - - handleLayoutChange() { - // switching from mobile to desktop - if (this.constructor.is_desktop) { - // remove from stack if opened - if (this.isOpened()) { - this.constructor.lowerDialog(this); - } - - // close if mobile only - if (this.config.mobile_only) { - this.close(); - } - // put dialog back in relative container - if (this.config.relative_container) { - this.config.relative_container.appendChild(this.dialog); - } + // put dialog back in relative container + if (this.config.relative_container) { + this.config.relative_container.appendChild(this.dialog); + } - // switching from desktop to mobile - } else { - // put dialog in body - if (this.config.relative_container) { - document.body.appendChild(this.dialog); - } + // switching from desktop to mobile + } else { + // put dialog in body + if (this.config.relative_container) { + document.body.appendChild(this.dialog); + } - // add to stack if opened - if (this.isOpened()) { - this.constructor.raiseDialog(this); - } + // add to stack if opened + if (this.isOpened()) { + this.constructor.raiseDialog(this); } } + } - handleResize() { - if (this.isClosed()) { - return; - } + handleResize() { + if (this.isClosed()) { + return; + } - const container_style = window.getComputedStyle(this.container); + const container_style = window.getComputedStyle(this.container); - if ( - this.config.resize_mode === this.constructor.RESIZE_FILL || - !this.constructor.is_desktop - ) { - const footer_region = this.footer.getBoundingClientRect(); + if ( + this.config.resize_mode === this.constructor.RESIZE_FILL || + !this.constructor.is_desktop + ) { + const footer_region = this.footer.getBoundingClientRect(); - let margin = - Math.parseInt(container_style.marginTop) + - Math.parseInt(container_style.marginBottom); + let margin = + Math.parseInt(container_style.marginTop) + + Math.parseInt(container_style.marginBottom); - margin = isNaN(margin) ? 0 : margin; + margin = isNaN(margin) ? 0 : margin; - this.scroll.style.height = - window.innerHeight - footer_region.height - margin + 'px'; + this.scroll.style.height = + window.innerHeight - footer_region.height - margin + 'px'; - this.dialog.style.height = window.innerHeight + 'px'; - this.dialog.style.top = null; - } else if (this.config.resize_mode === this.constructor.RESIZE_CENTER) { - this.dialog.style.height = 'auto'; - this.scroll.style.height = 'auto'; + this.dialog.style.height = window.innerHeight + 'px'; + this.dialog.style.top = null; + } else if (this.config.resize_mode === this.constructor.RESIZE_CENTER) { + this.dialog.style.height = 'auto'; + this.scroll.style.height = 'auto'; - let margin = - Math.parseInt(container_style.marginTop) + - Math.parseInt(container_style.marginBottom); + let margin = + Math.parseInt(container_style.marginTop) + + Math.parseInt(container_style.marginBottom); - margin = isNaN(margin) ? 0 : margin; + margin = isNaN(margin) ? 0 : margin; - const region = this.container.getBoundingClientRect(); - const viewport = window.innerHeight; + const region = this.container.getBoundingClientRect(); + const viewport = window.innerHeight; - // center vertically in viewport - this.dialog.style.top = (viewport - region.height - margin) / 2 + 'px'; - } else { - this.dialog.style.height = 'auto'; - this.scroll.style.height = 'auto'; - } + // center vertically in viewport + this.dialog.style.top = (viewport - region.height - margin) / 2 + 'px'; + } else { + this.dialog.style.height = 'auto'; + this.scroll.style.height = 'auto'; } + } - handleDocumentClick(e) { - if (this.isOpened()) { - const prevent_close = false; - let target = e.target; - while (target.parentNode && !prevent_close) { - if (target === this.dialog || target === this.config.toggle_element) { - prevent_close = true; - } - target = target.parentNode; - } - - if (!prevent_close) { - this.closeWithAnimation(); + handleDocumentClick(e) { + if (this.isOpened()) { + const prevent_close = false; + let target = e.target; + while (target.parentNode && !prevent_close) { + if (target === this.dialog || target === this.config.toggle_element) { + prevent_close = true; } + target = target.parentNode; } - } - handleDocumentKeyDown(e) { - // allow escape key to close dialog - if (this.isOpened() && e.keyCode === 27) { + if (!prevent_close) { this.closeWithAnimation(); } } + } - handlePopState(e) { - // TODO: push/pop-state is not supported yet. - if (e.state && e.state.id && e.state.id === this.getPushStateId()) { - this.open(); - } else { - this.close(); - } + handleDocumentKeyDown(e) { + // allow escape key to close dialog + if (this.isOpened() && e.keyCode === 27) { + this.closeWithAnimation(); + } + } + + handlePopState(e) { + // TODO: push/pop-state is not supported yet. + if (e.state && e.state.id && e.state.id === this.getPushStateId()) { + this.open(); + } else { + this.close(); } - }; -})(); + } +}