From: Alexander Ebert Date: Mon, 1 Jun 2015 16:37:20 +0000 (+0200) Subject: Improved implementation of `UI/Dialog` X-Git-Tag: 3.0.0_Beta_1~2291 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=626fda0244100c384a8e91c0af50baa52d2c7df5;p=GitHub%2FWoltLab%2FWCF.git Improved implementation of `UI/Dialog` The `open()` method has been altered to act similar to `Ajax.api()` and now features a lazy dialog initialization with a broad support for initialization methods. --- diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Controller/Sitemap.js b/wcfsetup/install/files/js/WoltLab/WCF/Controller/Sitemap.js index 7e6c6888c2..7a23b5c7a1 100644 --- a/wcfsetup/install/files/js/WoltLab/WCF/Controller/Sitemap.js +++ b/wcfsetup/install/files/js/WoltLab/WCF/Controller/Sitemap.js @@ -31,32 +31,7 @@ define(['Ajax', 'EventHandler', 'Language', 'DOM/Util', 'UI/Dialog', 'UI/TabMenu _click: function(event) { event.preventDefault(); - if (UIDialog.getDialog('sitemapDialog') === undefined) { - Ajax.apiOnce({ - data: { - actionName: 'getSitemap', - className: 'wcf\\data\\sitemap\\SitemapAction' - }, - success: (function(data) { - _cache.push(data.returnValues.sitemapName); - - _dialog = UIDialog.open('sitemapDialog', data.returnValues.template, { - disableContentPadding: true, - title: Language.get('wcf.page.sitemap') - }); - - var tabMenuContainer = _dialog.content.querySelector('.tabMenuContainer'); - var menuId = DOMUtil.identify(tabMenuContainer); - - UITabMenu.getTabMenu(menuId).select('sitemap_' + data.returnValues.sitemapName); - - EventHandler.add('com.woltlab.wcf.simpleTabMenu_' + menuId, 'select', this.showTab.bind(this)); - }).bind(this) - }); - } - else { - UIDialog.open('sitemapDialog'); - } + UIDialog.open(this); }, _ajaxSetup: function() { @@ -74,6 +49,32 @@ define(['Ajax', 'EventHandler', 'Language', 'DOM/Util', 'UI/Dialog', 'UI/TabMenu document.getElementById('sitemap_' + data.returnValues.sitemapName).innerHTML = data.returnValues.template; }, + _dialogSetup: function() { + return { + id: 'sitemapDialog', + options: { + disableContentPadding: true, + title: Language.get('wcf.page.sitemap') + }, + source: { + data: { + actionName: 'getSitemap', + className: 'wcf\\data\\sitemap\\SitemapAction' + }, + after: (function(content, data) { + _cache.push(data.returnValues.sitemapName); + + var tabMenuContainer = content.querySelector('.tabMenuContainer'); + var menuId = DOMUtil.identify(tabMenuContainer); + + UITabMenu.getTabMenu(menuId).select('sitemap_' + data.returnValues.sitemapName); + + EventHandler.add('com.woltlab.wcf.simpleTabMenu_' + menuId, 'select', this.showTab.bind(this)); + }).bind(this) + } + }; + }, + /** * Callback for tab links, lazy loads content. * diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Controller/Style/Changer.js b/wcfsetup/install/files/js/WoltLab/WCF/Controller/Style/Changer.js index 37240ca419..033823426c 100644 --- a/wcfsetup/install/files/js/WoltLab/WCF/Controller/Style/Changer.js +++ b/wcfsetup/install/files/js/WoltLab/WCF/Controller/Style/Changer.js @@ -42,19 +42,23 @@ define(['Ajax', 'Language', 'UI/Dialog'], function(Ajax, Language, UIDialog) { showDialog: function(event) { event.preventDefault(); - if (UIDialog.getDialog('styleChanger') === undefined) { - Ajax.apiOnce({ + UIDialog.show(this); + }, + + _dialogSetup: function() { + return { + id: 'styleChanger', + options: { + disableContentPadding: true, + title: Language.get('wcf.style.changeStyle') + }, + source: { data: { actionName: 'getStyleChooser', className: 'wcf\\data\\style\\StyleAction' }, - success: (function(data) { - var dialog = UIDialog.open('styleChanger', data.returnValues.template, { - disableContentPadding: true, - title: Language.get('wcf.style.changeStyle') - }); - - var styles = dialog.content.querySelectorAll('.styleList > li'); + after: (function(content) { + var styles = content.querySelectorAll('.styleList > li'); for (var i = 0, length = styles.length; i < length; i++) { var style = styles[i]; @@ -62,11 +66,8 @@ define(['Ajax', 'Language', 'UI/Dialog'], function(Ajax, Language, UIDialog) { style.addEventListener('click', this._click.bind(this)); } }).bind(this) - }); - } - else { - UIDialog.open('styleChanger'); - } + } + }; }, /** diff --git a/wcfsetup/install/files/js/WoltLab/WCF/UI/Confirmation.js b/wcfsetup/install/files/js/WoltLab/WCF/UI/Confirmation.js index d82224396e..836411beb0 100644 --- a/wcfsetup/install/files/js/WoltLab/WCF/UI/Confirmation.js +++ b/wcfsetup/install/files/js/WoltLab/WCF/UI/Confirmation.js @@ -66,11 +66,18 @@ define(['Core', 'Language', 'UI/Dialog'], function(Core, Language, UIDialog) { _active = true; - UIDialog.open('wcfSystemConfirmation', null, { - onClose: this._onClose.bind(this), - onShow: this._onShow.bind(this), - title: Language.get('wcf.global.confirmation.title') - }); + UIDialog.open(this); + }, + + _dialogSetup: function() { + return { + id: 'wcfSystemConfirmation', + options: { + onClose: this._onClose.bind(this), + onShow: this._onShow.bind(this), + title: Language.get('wcf.global.confirmation.title') + } + }; }, /** diff --git a/wcfsetup/install/files/js/WoltLab/WCF/UI/Dialog.js b/wcfsetup/install/files/js/WoltLab/WCF/UI/Dialog.js index 8f33b34da6..3b8e1671d6 100644 --- a/wcfsetup/install/files/js/WoltLab/WCF/UI/Dialog.js +++ b/wcfsetup/install/files/js/WoltLab/WCF/UI/Dialog.js @@ -7,14 +7,23 @@ * @module WoltLab/WCF/UI/Dialog */ define( - [ 'enquire', 'Core', 'Dictionary', 'Environment', 'Language', 'DOM/ChangeListener', 'DOM/Util', 'UI/Confirmation'], - function(enquire, Core, Dictionary, Environment, Language, DOMChangeListener, DOMUtil, UIConfirmation) + [ + 'enquire', 'Core', 'Dictionary', 'Environment', + 'Language', 'ObjectMap', 'DOM/ChangeListener', 'DOM/Util', + 'UI/Confirmation' + ], + function( + enquire, Core, Dictionary, Environment, + Language, ObjectMap, DOMChangeListener, DOMUtil, + UIConfirmation + ) { "use strict"; var _activeDialog = null; var _container = null; var _dialogs = new Dictionary(); + var _dialogObjects = new ObjectMap(); var _dialogFullHeight = false; var _keyupListener = null; @@ -53,6 +62,85 @@ define( }); }, + /** + * Opens the dialog and implicitly creates it on first usage. + * + * @param {object} callbackObject used to invoke `_dialogSetup()` on first call + * @param {(string|DocumentFragment=} html html content or document fragment to use for dialog content + * @returns {object} dialog data + */ + open: function(callbackObject, html) { + var dialogData = _dialogObjects.get(callbackObject); + if (Core.isPlainObject(dialogData)) { + // dialog already exists + return this.openStatic(dialogData.id, html); + } + + // initialize a new dialog + if (typeof callbackObject._dialogSetup !== 'function') { + throw new Error("Callback object does not implement the method '_dialogSetup()'."); + } + + var setupData = callbackObject._dialogSetup(); + if (!Core.isPlainObject(setupData)) { + throw new Error("Expected an object literal as return value of '_dialogSetup()'."); + } + + dialogData = { id: setupData.id }; + + var createOnly = true; + if (setupData.source === undefined) { + var dialogElement = document.getElementById(setupData.id); + if (dialogElement === null) { + throw new Error("Element id '" + setupData.id + "' is invalid and no source attribute was given."); + } + + setupData.source = document.createDocumentFragment(); + setupData.source.appendChild(dialogElement); + } + else if (typeof setupData.source === null) { + // `null` means there is no static markup and `html` should be used instead + setupData.source = html; + } + + else if (typeof setupData.source === 'function') { + setupData.source(); + } + else if (Core.isPlainObject(setupData.source)) { + Ajax.api(this, { + data: setupData.source.data + }, (function(data) { + if (data.returnValues && typeof data.returnValues.template === 'string') { + this.open(callbackObject, data.returnValues.template); + + if (typeof setupData.source.after === 'function') { + setupData.source.after(_dialogs.get(setupData.id).content, data); + } + } + }).bind(this)); + } + else { + if (typeof setupData.source === 'string') { + var dialogElement = document.createElement('div'); + dialogElement.setAttribute('id', setupData.id); + dialogElement.innerHTML = setupData.source; + + setupData.source = document.createDocumentFragment(); + setupData.source.appendChild(dialogElement); + } + + if (!setupData.source.nodeType || setupData.source.nodeType !== Node.DOCUMENT_FRAGMENT_NODE) { + throw new Error("Expected at least a document fragment as 'source' attribute."); + } + + createOnly = false; + } + + _dialogObjects.set(callbackObject, dialogData); + + return this.openStatic(setupData.id, setupData.source, setupData.options, createOnly); + }, + /** * Opens an dialog, if the dialog is already open the content container * will be replaced by the HTML string contained in the parameter html. @@ -60,12 +148,13 @@ define( * If id is an existing element id, html will be ignored and the referenced * element will be appended to the content element instead. * - * @param {string} id element id, if exists the html parameter is ignored in favor of the existing element - * @param {?string} html content html - * @param {object} options list of options, is completely ignored if the dialog already exists - * @return {object} dialog data + * @param {string} id element id, if exists the html parameter is ignored in favor of the existing element + * @param {?(string|DocumentFragment)} html content html + * @param {object} options list of options, is completely ignored if the dialog already exists + * @param {boolean=} createOnly create the dialog but do not open it + * @return {object} dialog data */ - open: function(id, html, options) { + openStatic: function(id, html, options, createOnly) { if (_dialogs.has(id)) { this._updateDialog(id, html); } @@ -120,11 +209,12 @@ define( /** * Creates the DOM for a new dialog and opens it. * - * @param {string} id element id, if exists the html parameter is ignored in favor of the existing element - * @param {?string} html content html - * @param {object} options list of options + * @param {string} id element id, if exists the html parameter is ignored in favor of the existing element + * @param {?(string|DocumentFragment)} html content html + * @param {object} options list of options + * @param {boolean=} createOnly create the dialog but do not open it */ - _createDialog: function(id, html, options) { + _createDialog: function(id, html, options, createOnly) { var element = null; if (html === null) { element = document.getElementById(id); @@ -178,8 +268,20 @@ define( var content; if (element === null) { content = document.createElement('div'); - content.setAttribute('id', id); - content.innerHTML = html; + + if (typeof html === 'string') { + content.innerHTML = html; + } + else if (html instanceof DocumentFragment) { + if (html.children[0].nodeName !== 'div' || html.childElementCount > 1) { + content.appendChild(html); + } + else { + content = html.children[0]; + } + } + + content.id = id; } else { content = element; @@ -201,24 +303,11 @@ define( onShow: options.onShow }); - if (_container.getAttribute('aria-hidden') === 'true') { - window.addEventListener('keyup', _keyupListener); - } - DOMUtil.prepend(dialog, _container); - _container.setAttribute('aria-hidden', 'false'); - _container.setAttribute('data-close-on-click', (options.backdropCloseOnClick ? 'true' : 'false')); - dialog.setAttribute('aria-hidden', 'false'); - - this.rebuild(id); - - _activeDialog = id; - if (typeof options.onShow === 'function') { - options.onShow(id); + if (createOnly !== true) { + this._updateDialog(id, null); } - - DOMChangeListener.trigger(); }, /** @@ -243,13 +332,15 @@ define( } if (data.dialog.getAttribute('aria-hidden') === 'true') { + if (_container.getAttribute('aria-hidden') === 'true') { + window.addEventListener('keyup', _keyupListener); + } + data.dialog.setAttribute('aria-hidden', 'false'); _container.setAttribute('aria-hidden', 'false'); _container.setAttribute('data-close-on-click', (data.backdropCloseOnClick ? 'true' : 'false')); _activeDialog = id; - window.addEventListener('keyup', _keyupListener); - this.rebuild(id); if (typeof data.onShow === 'function') { diff --git a/wcfsetup/install/files/js/WoltLab/WCF/UI/Mobile.js b/wcfsetup/install/files/js/WoltLab/WCF/UI/Mobile.js index 5087ff3583..76de64c0eb 100644 --- a/wcfsetup/install/files/js/WoltLab/WCF/UI/Mobile.js +++ b/wcfsetup/install/files/js/WoltLab/WCF/UI/Mobile.js @@ -7,8 +7,8 @@ * @module WoltLab/WCF/UI/Mobile */ define( - [ 'enquire', 'Environment', 'Language', 'DOM/ChangeListener', 'DOM/Traverse', 'UI/CloseHandler'], - function(enquire, Environment, Language, DOMChangeListener, DOMTraverse, UICloseHandler) + [ 'enquire', 'Environment', 'Language', 'DOM/ChangeListener', 'DOM/Traverse', 'UI/CloseOverlay'], + function(enquire, Environment, Language, DOMChangeListener, DOMTraverse, UICloseOverlay) { "use strict"; @@ -78,7 +78,7 @@ define( this._initSearchBar(); this._initButtonGroupNavigation(); - UICloseHandler.add('WoltLab/WCF/UI/Mobile', this._closeAllMenus.bind(this)); + UICloseOverlay.add('WoltLab/WCF/UI/Mobile', this._closeAllMenus.bind(this)); DOMChangeListener.add('WoltLab/WCF/UI/Mobile', this._initButtonGroupNavigation.bind(this)); }, diff --git a/wcfsetup/install/files/js/WoltLab/WCF/UI/TabMenu/Simple.js b/wcfsetup/install/files/js/WoltLab/WCF/UI/TabMenu/Simple.js index c6cde541aa..6e48d58735 100644 --- a/wcfsetup/install/files/js/WoltLab/WCF/UI/TabMenu/Simple.js +++ b/wcfsetup/install/files/js/WoltLab/WCF/UI/TabMenu/Simple.js @@ -254,6 +254,8 @@ define(['Dictionary', 'DOM/Util', 'EventHandler'], function(Dictionary, DOMUtil, * @param {object} event event object */ _onClick: function(event) { + event.preventDefault(); + var tab = event.currentTarget.parentNode; this.select(null, tab);