Improved implementation of `UI/Dialog`
authorAlexander Ebert <ebert@woltlab.com>
Mon, 1 Jun 2015 16:37:20 +0000 (18:37 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Mon, 1 Jun 2015 16:37:20 +0000 (18:37 +0200)
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.

wcfsetup/install/files/js/WoltLab/WCF/Controller/Sitemap.js
wcfsetup/install/files/js/WoltLab/WCF/Controller/Style/Changer.js
wcfsetup/install/files/js/WoltLab/WCF/UI/Confirmation.js
wcfsetup/install/files/js/WoltLab/WCF/UI/Dialog.js
wcfsetup/install/files/js/WoltLab/WCF/UI/Mobile.js
wcfsetup/install/files/js/WoltLab/WCF/UI/TabMenu/Simple.js

index 7e6c6888c2d54c10d71044849d8bc0e0140a6317..7a23b5c7a120b61beb19a5ee9b889a72c500dbfc 100644 (file)
@@ -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.
                 * 
index 37240ca4192a2078f9cc2c385f3db00abb9cdc72..033823426c9b6df1889b0e9e161221befba31167 100644 (file)
@@ -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');
-                       }
+                               }
+                       };
                },
                
                /**
index d82224396e5905da682afa85b2f3bb1b3dcc7bf0..836411beb0feda30866599798e27ecc1832bc4e7 100644 (file)
@@ -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')
+                               }
+                       };
                },
                
                /**
index 8f33b34da6893892c041ba724b98019b7c1e1b4c..3b8e1671d62dd25c652ca20fd0c03cab0350d758 100644 (file)
@@ -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<string, *>}             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<string, *>}     options         list of options, is completely ignored if the dialog already exists
-                * @return      {object<string, *>}     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<string, *>}             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<string, *>}             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<string, *>}     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<string, *>}             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') {
index 5087ff35836bac5db09cb67959a3118f8e17a6f6..76de64c0ebe22dbb19e68ee5e2517db341932209 100644 (file)
@@ -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));
                },
                
index c6cde541aa100049e9f4a80c01e8c37f0a3fdb7f..6e48d587350f712319f79453dff8aa8d853fce7d 100644 (file)
@@ -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);