Index: umc/widgets/Form.js =================================================================== --- umc/widgets/Form.js (Revision 36481) +++ umc/widgets/Form.js (Arbeitskopie) @@ -26,19 +26,19 @@ * /usr/share/common-licenses/AGPL-3; if not, see * . */ -/*global define */ +/*global define require console*/ define([ "dojo/_base/declare", "dojo/_base/lang", "dojo/_base/array", "dojo/Deferred", - "dojo/on", + "dojo/promise/all", "dojo/dom-style", "dijit/form/Form", "umc/tools", "umc/render" -], function(declare, lang, array, Deferred, on, style, Form, tools, render) { +], function(declare, lang, array, Deferred, all, style, Form, tools, render) { // in order to break circular dependencies (umc.dialog needs a Form and // Form needs umc/dialog), we define umc/dialog as an empty object and @@ -121,13 +121,14 @@ //'class': 'umcNoBorder', - _initializingElements: 0, + _allReady: null, - _initializedDeferred: null, - postMixInProperties: function() { this.inherited(arguments); + // initialize with empty list + this._allReady = []; + // in case no layout is specified and no content, either, create one automatically if ((!this.layout || !this.layout.length) && !this.content) { this.layout = []; @@ -183,8 +184,6 @@ buildRendering: function() { this.inherited(arguments); - this._initializedDeferred = new Deferred(); - if (this.scrollable) { style.set(this.containerNode, { overflow: 'auto' @@ -225,47 +224,19 @@ } } - - // send an event when all dynamic elements have been initialized - this._initializingElements = 0; - //console.log('# Form.buildRendering()'); - tools.forIn(this._widgets, function(iname, iwidget) { - // only consider elements that load values dynamically - //console.log('# iwidget:', iwidget.name); - if ('onValuesLoaded' in iwidget && !(iwidget._valuesLoaded && !iwidget._deferredOrValues)) { - // widget values have not been loaded completely so far - //console.log('# -> has event "valuesLoaded"'); - ++this._initializingElements; - on.once(iwidget, 'valuesLoaded', lang.hitch(this, function() { - //console.log('# -> valuesLoaded:', iwidget.name, iwidget.get('value')); - // decrement the internal counter - --this._initializingElements; - //console.log('# _initializingElements:', this._initializingElements); - - // send event when the last element has been initialized - if (0 === this._initializingElements) { - this._initializedDeferred.resolve(); - } - })); - } - }, this); - - // maybe all elements are already initialized - if (!this._initializingElements) { - this._initializedDeferred.resolve(); - } - + this._updateAllReady(); }, startup: function() { + //console.log('### Form: startup - container:', this._container); this.inherited(arguments); if (this._container) { // call the containers startup function if necessary this._container.startup(); } - this._initializedDeferred.then(lang.hitch(this, function() { + this.ready().then(lang.hitch(this, function() { + //console.log('### Form: all ready'); this.onValuesInitialized(); - //console.log('# valuesInitialized'); })); }, @@ -553,8 +524,31 @@ dialog.notify(error_msg); } })); - } + }, + _updateAllReady: function() { + // wait for all widgets to be ready + this._allReady = []; + //console.log('### Form: iterate over widgets.ready'); + tools.forIn(this._widgets, function(iname, iwidget) { + //console.log('### ' + iname + ' -> ', iwidget.ready ? iwidget.ready() : null); + this._allReady.push(iwidget.ready ? iwidget.ready() : null); + }, this); + }, + + ready: function() { + // update the internal list in order to wait until everybody is ready + if (!this._allReady.length) { + _updateAllReady(); + } + var ret = all(this._allReady); + + // empty list when all widgets are ready + ret.then(lang.hitch(this, function() { + this._allReady = []; + })); + return ret; + } }); }); Index: umc/widgets/MultiInput.js =================================================================== --- umc/widgets/MultiInput.js (Revision 36481) +++ umc/widgets/MultiInput.js (Arbeitskopie) @@ -32,13 +32,15 @@ "dojo/_base/declare", "dojo/_base/lang", "dojo/_base/array", + "dojo/Deferred", + "dojo/promise/all", "dijit/form/Button", "umc/tools", "umc/render", "umc/widgets/ContainerWidget", "umc/widgets/_FormWidgetMixin", "umc/widgets/LabelPane" -], function(declare, lang, array, Button, tools, render, ContainerWidget, _FormWidgetMixin, LabelPane) { +], function(declare, lang, array, Deferred, all, Button, tools, render, ContainerWidget, _FormWidgetMixin, LabelPane) { return declare("umc.widgets.MultiInput", [ ContainerWidget, _FormWidgetMixin ], { // summary: // Widget for a small list of simple and complex entries. An entry can be one or @@ -77,6 +79,12 @@ _lastDepends: null, + _valuesLoaded: false, + + _readyDeferred: null, + + _startupDeferred: null, + _createHandler: function(ifunc) { // This handler will be called by all subwidgets of the MultiInput widget. // When the first request comes in, we will execute the function to compute @@ -106,8 +114,14 @@ postMixInProperties: function() { this.inherited(arguments); + // delete the size class this.sizeClass = null; + // the _readyDeferred is being resolved as soon as everything has been set up + this._readyDeferred = new Deferred(); + + this._startupDeferred = new Deferred(); + // check the property 'subtypes' tools.assert(this.subtypes instanceof Array, 'umc/widgets/ContainerWidget: The property subtypes needs to be a string or an array of strings: ' + this.subtypes); @@ -149,6 +163,12 @@ this._appendElements(1); }, + startup: function() { + this.inherited(arguments); + + this._startupDeferred.resolve(); + }, + _loadValues: function(depends) { // delegate the call to _loadValues to all widgets this._lastDepends = depends; @@ -316,6 +336,11 @@ return; } + // initiate a new Deferred object in case there is none already pending + if (this._readyDeferred.isFulfilled()) { + this._readyDeferred = new Deferred(); + } + // remove the 'new' button this._removeNewButton(); @@ -403,7 +428,7 @@ // add row this._widgets.push(visibleWidgets); this._rowContainers.push(rowContainer); - rowContainer.startup(); + this._startupDeferred.then(lang.hitch(rowContainer, 'startup')); this.addChild(rowContainer); // call the _loadValues method by hand @@ -415,6 +440,21 @@ }, this); } + // wait for all widgets to be ready + var allReady = []; + var i, j; + for (i = 0; i < this._widgets.length; ++i) { + for (j = 0; j < this._widgets[i].length; ++j) { + //console.log(lang.replace('### MultiInput: widget[{0}][{1}]: waiting -> ', [i, j]), this._widgets[i][j].ready()); + allReady.push(this._widgets[i][j].ready ? this._widgets[i][j].ready() : null); + } + } + all(allReady).then(lang.hitch(this, function() { + //console.log('### MultiInput: all resolved'); + this._readyDeferred.resolve(); + this.onValuesLoaded(); + })); + // add the new button if (this._nRenderedElements < this.max) { this._addNewButton(); @@ -494,6 +534,19 @@ if (this._widget) { tools.delegateCall(this, arguments, this._widget); } + }, + + onValuesLoaded: function(values) { + // summary: + // This event is triggered when all values (static and dynamic) have been loaded. + // values: + // Array containing all dynamic and static values. + }, + + // ready: + // Similiar to `umc/widgets/_FormWidgetMixin:ready`. + ready: function() { + return this._readyDeferred; } }); }); Index: umc/widgets/_SelectMixin.js =================================================================== --- umc/widgets/_SelectMixin.js (Revision 36481) +++ umc/widgets/_SelectMixin.js (Arbeitskopie) @@ -34,13 +34,12 @@ "dojo/_base/array", "dojo/Deferred", "dojo/when", - "dojo/on", "dojo/json", "dojo/Stateful", // TODO: should use dojo/store/ "dojo/data/ItemFileWriteStore", "umc/tools" -], function(declare, lang, array, Deferred, when, on, json, Stateful, ItemFileWriteStore, tools) { +], function(declare, lang, array, Deferred, when, json, Stateful, ItemFileWriteStore, tools) { return declare("umc.widgets._SelectMixin", Stateful, { // umcpCommand: // Reference to the umcpCommand the widget should use. @@ -105,6 +104,8 @@ _deferredOrValues: null, + _readyDeferred: null, + _createStore: function() { return new ItemFileWriteStore({ data: { @@ -128,6 +129,10 @@ this.inherited(arguments); this._saveInitialValue(); + + // the _readyDeferred is being resolved as soon as everything has been set up + //console.log('### _SelectMixin ['+this.name+']: initiate _readyDeferred'); + this._readyDeferred = new Deferred(); }, postCreate: function() { @@ -382,7 +387,7 @@ }, _loadValues: function(/*Object?*/ _dependValues) { - //console.log('###', this.name, ' _loadValues(', _dependValues, ')'); + //console.log('### _SelectMixin ['+this.name+']: _loadValues'); this._valuesLoaded = true; // unify `depends` property to be an array @@ -404,6 +409,7 @@ // only load dynamic values in case all dependencies are fullfilled if (dependList.length != nDepValues) { + //console.log('### _SelectMixin ['+this.name+']: return'); return; } @@ -420,9 +426,16 @@ // block concurrent events for value loading if (this._deferredOrValues) { // another request is pending + //console.log('### _SelectMixin ['+this.name+']: return (2)'); return; } + // initiate a new Deferred object in case there is none already pending + if (this._readyDeferred.isFulfilled()) { + //console.log('### _SelectMixin ['+this.name+']: re-initiate _readyDeferred'); + this._readyDeferred = new Deferred(); + } + // get dynamic values var func = tools.stringOrFunction(this.dynamicValues, this.umcpCommand); var deferredOrValues = func(params); @@ -439,9 +452,11 @@ this._clearValues(); this._setStaticValues(); this._setDynamicValues(res); - this.onDynamicValuesLoaded(res); // values have been loaded + //console.log('### _SelectMixin ['+this.name+']: resolved _readyDeferred'); + this._readyDeferred.resolve(this.getAllItems()); + this.onDynamicValuesLoaded(res); this.onValuesLoaded(this.getAllItems()); // unblock value loading @@ -452,6 +467,8 @@ this._setStaticValues(); // error handler + //console.log('### _SelectMixin ['+this.name+']: resolved _readyDeferred'); + this._readyDeferred.resolve([]); this.onDynamicValuesLoaded([]); this.onValuesLoaded(this.getAllItems()); @@ -465,6 +482,8 @@ this._setStaticValues(); // values have been loaded + //console.log('### _SelectMixin ['+this.name+']: resolved _readyDeferred'); + this._readyDeferred.resolve(this.getAllItems()); this.onValuesLoaded(this.getAllItems()); // unblock value loading @@ -532,6 +551,13 @@ if (this._valuesLoaded) { this._loadValues(); } + }, + + // ready: + // Returns null or a Deferred which resolves as soon as any + // loading activity of the widget is finished. + ready: function() { + return this._readyDeferred; } }); }); Index: umc/widgets/MixedInput.js =================================================================== --- umc/widgets/MixedInput.js (Revision 36481) +++ umc/widgets/MixedInput.js (Arbeitskopie) @@ -31,6 +31,7 @@ define([ "dojo/_base/declare", "dojo/_base/lang", + "dojo/Deferred", "dojo/json", "dijit/layout/ContentPane", "umc/tools", @@ -38,7 +39,7 @@ "umc/widgets/TextBox", "umc/widgets/ComboBox", "umc/widgets/CheckBox" -], function(declare, lang, json, ContentPane, tools, _FormWidgetMixin) { +], function(declare, lang, Deferred, json, ContentPane, tools, _FormWidgetMixin) { return declare("umc.widgets.MixedInput", [ ContentPane, _FormWidgetMixin ], { // umcpCommand: // Reference to the umcpCommand the widget should use. @@ -74,6 +75,8 @@ style: 'padding: 0', + _readyDeferred: null, + constructor: function(/*Object*/ props) { // mixin in the 'disabled' property props.disabled = this.disabled; @@ -94,6 +97,8 @@ this._userProperties.sizeClass = this.sizeClass; this.sizeClass = null; + + this._readyDeferred = new Deferred(); }, buildRendering: function() { @@ -135,6 +140,11 @@ return; } + // initiate a new Deferred object in case there is none already pending + if (this._readyDeferred.isFulfilled()) { + this._readyDeferred = new Deferred(); + } + // mixin additional options for the UMCP command if (this.dynamicOptions && typeof this.dynamicOptions == "object") { lang.mixin(params, this.dynamicOptions); @@ -199,6 +209,7 @@ this._widget.startup(); this.onValuesLoaded(); + this._readyDeferred.resolve(); }, _setValueAttr: function(newVal) { @@ -249,6 +260,13 @@ if (lang.getObject('_widget.focus', false, this)) { this._widget.focus(); } + }, + + // ready: + // Returns null or a Deferred which resolves as soon as any + // loading activity of the widget is finished. + ready: function() { + return this._readyDeferred; } }); }); Index: umc/widgets/LabelPane.js =================================================================== --- umc/widgets/LabelPane.js (Revision 36481) +++ umc/widgets/LabelPane.js (Arbeitskopie) @@ -31,13 +31,14 @@ define([ "dojo/_base/declare", "dojo/_base/lang", + "dojo/Deferred", "dojo/dom-class", "dojo/dom-attr", "dijit/_WidgetBase", "dijit/_TemplatedMixin", "dijit/_Container", "umc/tools" -], function(declare, lang, domClass, attr, _WidgetBase, _TemplatedMixin, _Container, tools) { +], function(declare, lang, Deferred, domClass, attr, _WidgetBase, _TemplatedMixin, _Container, tools) { lang.extend(_WidgetBase, { // isLabelDisplayed: Boolean? // If specified as true, LabelPane assumes that the widget itself will take @@ -81,6 +82,18 @@ labelNodeRight: null, + _startupDeferred: null, + + constructor: function(params) { + this._startupDeferred = new Deferred(); + + // lang._mixin() would not work sometimes, leaving this.content empty, see + // https://forge.univention.org/bugzilla/show_bug.cgi?id=26214#c3 + tools.forIn(params, function(ikey, ival) { + this[ikey] = ival; + }, this); + }, + postMixInProperties: function() { this.inherited(arguments); @@ -120,6 +133,15 @@ domClass.toggle(this.domNode, 'dijitHidden', this.content.visible === false); }, + startup: function() { + this.inherited(arguments); + + this._startupDeferred.resolve(); + if (lang.getObject('content.startup', false, this)) { + this.content.startup(); + } + }, + _setLabelAttr: function(label) { if (lang.getObject('content.isLabelDisplayed', false, this)) { // the widget displays the label itself @@ -164,6 +186,12 @@ else if (lang.getObject('domNode', false, content) && lang.getObject('declaredClass', false, content)) { this.contentNode.innerHTML = ''; this.addChild(content); + if (content.startup) { + this._startupDeferred.then(function() { + // call widget's startup after we have been started up + content.startup(); + }); + } } this.set( 'disabled', this.disabled ); }, Index: umc/widgets/ComplexInput.js =================================================================== --- umc/widgets/ComplexInput.js (Revision 36481) +++ umc/widgets/ComplexInput.js (Arbeitskopie) @@ -57,6 +57,13 @@ umcpCommand: tools.umcpCommand, + _allReady: null, + + constructor: function() { + // initialize with empty list + this._allReady = []; + }, + buildRendering: function() { this.inherited(arguments); @@ -101,6 +108,7 @@ } }, this); + this._updateAllReady(); }, _getValueAttr: function() { @@ -127,6 +135,28 @@ var iisValid = areValid instanceof Array ? areValid[i] : areValid; this._widgets[ iname ].setValid( iisValid, imessage ); }, this ); + }, + + _updateAllReady: function() { + // wait for all widgets to be ready + this._allReady = []; + tools.forIn(this._widgets, function(iname, iwidget) { + this._allReady.push(iwidget.ready ? iwidget.ready() : null); + }, this); + }, + + ready: function() { + // update the internal list in order to wait until everybody is ready + if (!this._allReady.length) { + _updateAllReady(); + } + var ret = all(this._allReady); + + // empty list when all widgets are ready + ret.then(lang.hitch(this, function() { + this._allReady = []; + })); + return ret; } }); }); Index: umc/widgets/LoginDialog.js =================================================================== --- umc/widgets/LoginDialog.js (Revision 36481) +++ umc/widgets/LoginDialog.js (Arbeitskopie) @@ -88,7 +88,6 @@ style: 'margin-left: auto; margin-right: auto; margin-top: 1em; width: 280px;', content: '' }); - this._text.placeAt(this.containerNode, 'first'); // create the language_combobox this._languageBox = new ComboBox({ @@ -100,14 +99,6 @@ label: _('Language'), content: this._languageBox }); - // we need to manually startup the widgets - this._languageBox.startup(); - this._languageLabel.startup(); - this._languageLabel.placeAt('umc_LoginDialog_FormContainer'); - // register onchange event - this.own(this._languageBox.watch('value', function(name, oldLang, newLang) { - i18nTools.setLanguage(newLang); - })); // automatically resize the DialogUnderlay container this.own(on(win.global, 'resize', lang.hitch(this, function() { @@ -117,6 +108,26 @@ }))); }, + postCreate: function() { + this.inherited(arguments); + + this._text.placeAt(this.containerNode, 'first'); + this._languageLabel.placeAt('umc_LoginDialog_FormContainer'); + }, + + startup: function() { + this.inherited(arguments); + + // we need to manually startup the widgets + this._languageBox.startup(); + this._languageLabel.startup(); + + // register onchange event + this.own(this._languageBox.watch('value', function(name, oldLang, newLang) { + i18nTools.setLanguage(newLang); + })); + }, + _initForm: function() { // wait until the iframe is completely loaded setTimeout(lang.hitch(this, function() { Index: tests/depends.html =================================================================== --- tests/depends.html (Revision 36481) +++ tests/depends.html (Arbeitskopie) @@ -30,7 +30,7 @@ async: true }; - +