View | Details | Raw Unified | Return to bug 31376
Collapse All | Expand All

(-)umc/widgets/MultiInput.js (-157 / +225 lines)
 Lines 83-92    Link Here 
83
83
84
		_readyDeferred: null,
84
		_readyDeferred: null,
85
85
86
		_allWidgetsBuiltDeferred: null,
87
86
		_startupDeferred: null,
88
		_startupDeferred: null,
87
89
88
		_blockChangeEvents: false,
90
		_blockChangeEvents: false,
89
91
92
		_hasSubtypeLabel: false,
93
90
		_createHandler: function(ifunc) {
94
		_createHandler: function(ifunc) {
91
			// This handler will be called by all subwidgets of the MultiInput widget.
95
			// This handler will be called by all subwidgets of the MultiInput widget.
92
			// When the first request comes in, we will execute the function to compute
96
			// When the first request comes in, we will execute the function to compute
 Lines 126-136    Link Here 
126
			this._readyDeferred = new Deferred();
130
			this._readyDeferred = new Deferred();
127
131
128
			this._startupDeferred = new Deferred();
132
			this._startupDeferred = new Deferred();
133
			this._allWidgetsBuiltDeferred = new Deferred();
129
134
130
			// check the property 'subtypes'
135
			// check the property 'subtypes'
131
			tools.assert(this.subtypes instanceof Array,
136
			tools.assert(this.subtypes instanceof Array,
132
					'umc/widgets/ContainerWidget: The property subtypes needs to be a string or an array of strings: ' + this.subtypes);
137
					'umc/widgets/ContainerWidget: The property subtypes needs to be a string or an array of strings: ' + this.subtypes);
133
138
139
			this._hasSubtypeLabel = array.some(this.subtypes, function(iwidget) {
140
				return iwidget.label;
141
			});
142
134
			// initiate other properties
143
			// initiate other properties
135
			this._rowContainers = [];
144
			this._rowContainers = [];
136
			this._widgets = [];
145
			this._widgets = [];
 Lines 171-177    Link Here 
171
			this.inherited(arguments);
180
			this.inherited(arguments);
172
181
173
			// add empty element
182
			// add empty element
174
			this._appendElements(1);
183
			this._appendRows();
175
		},
184
		},
176
185
177
		_loadValues: function(depends) {
186
		_loadValues: function(depends) {
 Lines 179-185    Link Here 
179
			this._lastDepends = depends;
188
			this._lastDepends = depends;
180
			array.forEach(this._widgets, function(iwidgets) {
189
			array.forEach(this._widgets, function(iwidgets) {
181
				array.forEach(iwidgets, function(jwidget) {
190
				array.forEach(iwidgets, function(jwidget) {
182
					if ('_loadValues' in jwidget) {
191
					if (jwidget && '_loadValues' in jwidget) {
183
						jwidget._loadValues(depends);
192
						jwidget._loadValues(depends);
184
					}
193
					}
185
				});
194
				});
 Lines 187-192    Link Here 
187
		},
196
		},
188
197
189
		_setAllValues: function(_valList) {
198
		_setAllValues: function(_valList) {
199
			var _valList = lang.clone(_valList);
190
			this._blockChangeEvents = true;
200
			this._blockChangeEvents = true;
191
			var valList = _valList;
201
			var valList = _valList;
192
			if (!(valList instanceof Array)) {
202
			if (!(valList instanceof Array)) {
 Lines 196-240    Link Here 
196
			// adjust the number of rows
206
			// adjust the number of rows
197
			var diff = valList.length - this._nRenderedElements;
207
			var diff = valList.length - this._nRenderedElements;
198
			if (diff > 0) {
208
			if (diff > 0) {
199
				this._appendElements(diff);
209
				this._appendRows(diff);
200
			}
210
			}
201
			else if (diff < 0) {
211
			else if (diff < 0) {
202
				this._popElements(-diff);
212
				this._popElements(-diff);
203
			}
213
			}
204
214
205
			// set all values
215
			this._allWidgetsBuiltDeferred.then(lang.hitch(this, function() {
206
			array.forEach(valList, function(ival, irow) {
216
				// set all values
207
				if (irow >= this._widgets.length) {
217
				array.forEach(valList, function(ival, irow) {
208
					// break
218
					if (irow >= this._widgets.length) {
209
					return false;
219
						return;
210
				}
220
					}
211
221
212
				var rowVals = [];
222
					var rowVals = [];
213
				if (typeof ival == "string") {
223
					if (typeof ival == "string") {
214
					// entry is string .. we need to parse it if we have a delimiter
224
						// entry is string .. we need to parse it if we have a delimiter
215
					if (this.delimiter) {
225
						if (this.delimiter) {
216
						rowVals = ival.split(this.delimiter);
226
							rowVals = ival.split(this.delimiter);
227
						}
228
						else {
229
							rowVals = [ ival ];
230
						}
217
					}
231
					}
218
					else {
232
					else if (ival instanceof Array) {
219
						rowVals = [ ival ];
233
						rowVals = ival;
220
					}
234
					}
221
				}
222
				else if (ival instanceof Array) {
223
					rowVals = ival;
224
				}
225
235
226
				// set values
236
					// set values
227
				for (var j = 0; j < this.subtypes.length; ++j) {
237
					for (var j = 0; j < this.subtypes.length; ++j) {
228
					var val = j >= rowVals.length ? '' : rowVals[j];
238
						var val = j >= rowVals.length ? '' : rowVals[j];
229
					this._widgets[irow][j].set('value', val);
239
						this._widgets[irow][j].set('value', val);
230
240
231
					// for dynamic combo boxes, we need to save the value as "initial value"
241
						// for dynamic combo boxes, we need to save the value as "initial value"
232
					if (this._widgets[irow][j].setInitialValue) {
242
						if (this._widgets[irow][j].setInitialValue) {
233
						this._widgets[irow][j].setInitialValue(val, false);
243
							this._widgets[irow][j].setInitialValue(val, false);
244
						}
234
					}
245
					}
235
				}
246
				}, this);
236
			}, this);
247
				this._blockChangeEvents = false;
237
			this._blockChangeEvents = false;
248
			}));
238
		},
249
		},
239
250
240
		_setValueAttr: function(_vals) {
251
		_setValueAttr: function(_vals) {
 Lines 252-273    Link Here 
252
		},
263
		},
253
264
254
		_setDisabledAttr: function ( value ) {
265
		_setDisabledAttr: function ( value ) {
255
			var i;
266
			this._allWidgetsBuiltDeferred.then(lang.hitch(this, function() {
256
			for ( i = 0; i < this._rowContainers.length; ++i) {
267
				var i;
257
				array.forEach( this._rowContainers[ i ].getChildren(), function( widget ) {
268
				for ( i = 0; i < this._rowContainers.length; ++i) {
258
					widget.set( 'disabled', value );
269
					var irow = this._rowContainers[i];
259
				} );
270
					array.forEach( irow ? irow.getChildren() : [], function( widget ) {
260
			}
271
						widget.set('disabled', value);
261
			this.disabled = value;
272
					} );
273
				}
274
			}));
275
			this._set('disabled', value);
262
		},
276
		},
263
277
264
		_getAllValues: function() {
278
		_getAllValues: function() {
265
			var i, j, val, isSet, vals = [], rowVals = [];
279
			var i, j, jwidget, val, isSet, vals = [], rowVals = [];
266
			for (i = 0; i < this._widgets.length; ++i) {
280
			for (i = 0; i < this._widgets.length; ++i) {
267
				rowVals = [];
281
				rowVals = [];
268
				isSet = false;
282
				isSet = false;
269
				for (j = 0; j < this._widgets[i].length; ++j) {
283
				for (j = 0; j < this._widgets[i].length; ++j) {
270
					val = this._widgets[i][j].get('value');
284
					jwidget = this._widgets[i][j];
285
					if (!jwidget) {
286
						continue;
287
					}
288
					val = jwidget.get('value');
271
					isSet = isSet || ('' !== val);
289
					isSet = isSet || ('' !== val);
272
					if (!tools.inheritsFrom(this._widgets[i][j], 'umc.widgets.Button')) {
290
					if (!tools.inheritsFrom(this._widgets[i][j], 'umc.widgets.Button')) {
273
						rowVals.push(val);
291
						rowVals.push(val);
 Lines 315-475    Link Here 
315
				return;
333
				return;
316
			}
334
			}
317
335
318
			// verify whether label information for subtypes are given
319
			var hasSubTypeLabels = false;
320
			array.forEach(this.subtypes, function(iwidget) {
321
				hasSubTypeLabels = hasSubTypeLabels || iwidget.label;
322
			});
323
324
			// create 'new' button
336
			// create 'new' button
325
			var btn = this.own(new Button({
337
			var btn = this.own(new Button({
326
				disabled: this.disabled,
338
				disabled: this.disabled,
327
				iconClass: 'umcIconAdd',
339
				iconClass: 'umcIconAdd',
328
				onClick: lang.hitch(this, '_appendElements', 1),
340
				onClick: lang.hitch(this, '_appendRows'),
329
				'class': 'umcMultiInputAddButton'
341
				'class': 'umcMultiInputAddButton'
330
			}))[0];
342
			}))[0];
331
343
332
			// wrap a button with a LabelPane
344
			// wrap a button with a LabelPane
333
			this._newButton = this.own(new LabelPane({
345
			this._newButton = this.own(new LabelPane({
334
				content: btn,
346
				content: btn,
335
				label: this._nRenderedElements === 1 && hasSubTypeLabels ? '&nbsp;' : '' // only keep the label for the first row
347
				label: this._nRenderedElements === 1 && this._hasSubtypeLabel ? '&nbsp;' : '' // only keep the label for the first row
336
			}))[0];
348
			}))[0];
337
349
338
			// add button to last row
350
			// add button to last row
339
			this._rowContainers[this._rowContainers.length - 1].addChild(this._newButton);
351
			this._rowContainers[this._rowContainers.length - 1].addChild(this._newButton);
340
		},
352
		},
341
353
342
		_appendElements: function(n) {
354
		_updateReadyDeferred: function() {
343
			if (n < 1) {
355
			// check all current elements whether they are ready
344
				return;
356
			var nReady = 0;
357
			var nElements = 0;
358
			var nBuiltElements = 0;
359
			var i, j;
360
			for (i = 0; i < this._widgets.length; ++i) {
361
				for (j = 0; j < this._widgets[i].length; ++j, ++nElements) {
362
					//console.log(lang.replace('### MultiInput: widget[{0}][{1}]: waiting -> ', [i, j]), this._widgets[i][j].ready());
363
					var jwidget = this._widgets[i][j];
364
					nBuiltElements += jwidget ? 1 : 0;
365
					var jreadyDeferred = jwidget && jwidget.ready ? jwidget.ready() : null;
366
					if (!jreadyDeferred) {
367
						++nReady;
368
					}
369
					else if (jreadyDeferred.isFulfilled()) {
370
						++nReady;
371
					}
372
				}
345
			}
373
			}
346
374
347
			// initiate a new Deferred object in case there is none already pending
375
			if (nReady < nElements) {
348
			if (this._readyDeferred.isFulfilled()) {
376
				if (this._readyDeferred.isFulfilled()) {
349
				this._readyDeferred = new Deferred();
377
					// initiate a new Deferred object if none is pending
378
					this._readyDeferred = new Deferred();
379
				}
380
381
				// update progress
382
				this._readyDeferred.progress({
383
					max: nElements,
384
					current: nReady,
385
					percentage: 100 * nReady / nElements
386
				});
350
			}
387
			}
351
388
352
			// remove the 'new' button
389
			if (nReady == nElements && !this._readyDeferred.isFulfilled()) {
353
			this._removeNewButton();
390
				// all elements are ready
391
				this._readyDeferred.resolve();
392
				this.onValuesLoaded();
393
			}
354
394
355
			var nFinal = this._nRenderedElements + n;
395
			if (nBuiltElements < nElements && this._allWidgetsBuiltDeferred.isFulfilled()) {
356
			for (var irow = this._nRenderedElements; irow < nFinal && irow < this.max; ++irow, ++this._nRenderedElements) {
396
				// initiate a new Deferred object
357
				// add all other elements with '__' such that they will be ignored by umc/widgets/Form
397
				this._allWidgetsBuiltDeferred = new Deferred();
358
				var order = [], widgetConfs = [];
398
			}
359
				array.forEach(this.subtypes, function(iwidget, i) {
360
					// add the widget configuration dict to the list of widgets
361
					var iname = '__' + this.name + '-' + irow + '-' + i;
362
					var iconf = lang.mixin({}, iwidget, {
363
						disabled: this.disabled,
364
						threshold: this.threshold, // for UDM-threshold
365
						name: iname,
366
						value: '',
367
						dynamicValues: lang.partial(iwidget.dynamicValues, iname)
368
					});
369
					if (iwidget.dynamicValuesInfo) {
370
						iconf.dynamicValuesInfo = lang.partial(iwidget.dynamicValuesInfo, iname);
371
					}
372
					widgetConfs.push(iconf);
373
399
374
					// add the name of the widget to the list of widget names
400
			if (nBuiltElements == nElements && !this._allWidgetsBuiltDeferred.isFulfilled()) {
375
					order.push(iname);
401
				// all elements have been built
376
				}, this);
402
				this._allWidgetsBuiltDeferred.resolve();
403
			}
404
		},
377
405
406
		__appendRow: function(irow) {
407
			var order = [], widgetConfs = [];
408
			array.forEach(this.subtypes, function(iwidget, i) {
409
				// add the widget configuration dict to the list of widgets
410
				var iname = '__' + this.name + '-' + irow + '-' + i;
411
				var iconf = lang.mixin({}, iwidget, {
412
					disabled: this.disabled,
413
					threshold: this.threshold, // for UDM-threshold
414
					name: iname,
415
					value: '',
416
					dynamicValues: lang.partial(iwidget.dynamicValues, iname)
417
				});
418
				if (iwidget.dynamicValuesInfo) {
419
					iconf.dynamicValuesInfo = lang.partial(iwidget.dynamicValuesInfo, iname);
420
				}
421
				widgetConfs.push(iconf);
378
422
379
				// render the widgets
423
				// add the name of the widget to the list of widget names
380
				var widgets = render.widgets(widgetConfs, this);
424
				order.push(iname);
425
			}, this);
381
426
382
				// if we have a button, we need to pass the value and index if the
383
				// current element
384
				tools.forIn(widgets, function(ikey, iwidget) {
385
					var myrow = irow;
386
					if (tools.inheritsFrom(iwidget, 'umc.widgets.Button') && typeof iwidget.callback == "function") {
387
						var callbackOrg = iwidget.callback;
388
						iwidget.callback = lang.hitch(this, function() {
389
							callbackOrg(this.get('value')[myrow], myrow);
390
						});
391
					}
392
				}, this);
393
427
394
				// find out whether all items do have a label
428
			// render the widgets
395
				var hasSubTypeLabels = array.filter(this.subtypes, function(iwidget) {
429
			var widgets = render.widgets(widgetConfs, this);
396
					return iwidget.label;
397
				}).length > 0;
398
430
399
				// layout widgets
431
			// if we have a button, we need to pass the value and index of the
400
				var visibleWidgets = array.map(order, function(iname) {
432
			// current element
401
					return widgets[iname];
433
			tools.forIn(widgets, function(ikey, iwidget) {
402
				});
434
				var myrow = irow;
403
				var rowContainer = this.own(new ContainerWidget({}))[0];
435
				if (tools.inheritsFrom(iwidget, 'umc.widgets.Button') && typeof iwidget.callback == "function") {
404
				array.forEach(order, function(iname) {
436
					var callbackOrg = iwidget.callback;
405
					// add widget to row container (wrapped by a LabelPane)
437
					iwidget.callback = lang.hitch(this, function() {
406
					// only keep the label for the first row
438
						callbackOrg(this.get('value')[myrow], myrow);
407
					var iwidget = widgets[iname];
439
					});
408
					var label = irow !== 0 ? '' : iwidget.label;
440
				}
409
					if (tools.inheritsFrom(iwidget, 'umc.widgets.Button')) {
441
			}, this);
410
						label = irow !== 0 ? '' : '&nbsp;';
411
					}
412
					rowContainer.addChild(new LabelPane({
413
						disabled: this.disabled,
414
						content: iwidget,
415
						label: label
416
					}));
417
442
418
					// register to value changes
443
			// layout widgets
419
					this.own(iwidget.watch('value', lang.hitch(this, function() {
444
			var visibleWidgets = array.map(order, function(iname) {
420
						if (!this._blockChangeEvents) {
445
				return widgets[iname];
421
							this._set('value', this.get('value'));
446
			});
422
						}
447
			var rowContainer = this.own(new ContainerWidget({}))[0];
423
					})));
448
			array.forEach(order, function(iname) {
424
				}, this);
449
				// add widget to row container (wrapped by a LabelPane)
425
450
				// only keep the label for the first row
426
				// add a 'remove' button at the end of the row
451
				var iwidget = widgets[iname];
427
				var button = this.own(new Button({
452
				var label = irow !== 0 ? '' : iwidget.label;
453
				if (tools.inheritsFrom(iwidget, 'umc.widgets.Button')) {
454
					label = irow !== 0 ? '' : '&nbsp;';
455
				}
456
				rowContainer.addChild(new LabelPane({
428
					disabled: this.disabled,
457
					disabled: this.disabled,
429
					iconClass: 'umcIconDelete',
458
					content: iwidget,
430
					onClick: lang.hitch(this, '_removeElement', irow),
459
					label: label
431
					'class': 'umcMultiInputRemoveButton'
432
				}))[0];
433
				rowContainer.addChild(new LabelPane({
434
					content: button,
435
					label: irow === 0 && hasSubTypeLabels ? '&nbsp;' : '' // only keep the label for the first row
436
				}));
460
				}));
437
461
438
				// add row
462
				// register to value changes
439
				this._widgets.push(visibleWidgets);
463
				this.own(iwidget.watch('value', lang.hitch(this, function() {
440
				this._rowContainers.push(rowContainer);
464
					if (!this._blockChangeEvents) {
441
				this._startupDeferred.then(lang.hitch(rowContainer, 'startup'));
465
						this._set('value', this.get('value'));
442
				this.addChild(rowContainer);
466
					}
467
				})));
468
			}, this);
443
469
444
				// call the _loadValues method by hand
470
			// add a 'remove' button at the end of the row
445
				array.forEach(order, function(iname) {
471
			var button = this.own(new Button({
446
					var iwidget = widgets[iname];
472
				disabled: this.disabled,
447
					if ('_loadValues' in iwidget) {
473
				iconClass: 'umcIconDelete',
448
						iwidget._loadValues(this._lastDepends);
474
				onClick: lang.hitch(this, '_removeElement', irow),
449
					}
475
				'class': 'umcMultiInputRemoveButton'
450
				}, this);
476
			}))[0];
451
				//this._readyDeferred.progress(_('%(i)s / %(len)s values built', {i: irow, len: nFinal}));
477
			rowContainer.addChild(new LabelPane({
478
				content: button,
479
				label: irow === 0 && this._hasSubtypeLabel ? '&nbsp;' : '' // only keep the label for the first row
480
			}));
481
482
			// add row
483
			this._widgets[irow] = visibleWidgets;
484
			this._rowContainers[irow] = rowContainer;
485
			this._startupDeferred.then(lang.hitch(rowContainer, 'startup'));
486
			this.addChild(rowContainer);
487
488
			// call the _loadValues method by hand
489
			array.forEach(order, function(iname) {
490
				var iwidget = widgets[iname];
491
				if ('_loadValues' in iwidget) {
492
					iwidget._loadValues(this._lastDepends);
493
				}
494
			}, this);
495
496
			// update the ready deferred know and when the widget itself is ready
497
			this._updateReadyDeferred();
498
			var allReady = [];
499
			tools.forIn(widgets, function(ikey, iwidget) {
500
				allReady.push(iwidget.ready ? iwidget.ready() : null);
501
			});
502
			all(allReady).then(lang.hitch(this, '_updateReadyDeferred'));
503
		},
504
505
		_appendRows: function(n) {
506
			n = n || 1;
507
			if (n < 1) {
508
				return;
452
			}
509
			}
453
510
454
			// wait for all widgets to be ready
511
			// remove the 'new' button
455
			var allReady = [];
512
			this._removeNewButton();
456
			var i, j;
513
457
			for (i = 0; i < this._widgets.length; ++i) {
514
			var nFinal = this._nRenderedElements + n;
458
				for (j = 0; j < this._widgets[i].length; ++j) {
515
			var newRows = [];
459
					//console.log(lang.replace('### MultiInput: widget[{0}][{1}]: waiting -> ', [i, j]), this._widgets[i][j].ready());
516
			for (var irow = this._nRenderedElements; irow < nFinal && irow < this.max; ++irow, ++this._nRenderedElements) {
460
					allReady.push(this._widgets[i][j].ready ? this._widgets[i][j].ready() : null);
517
				newRows.push(irow);
518
519
				// allocate indeces in 2D array _widget this allows _updateReadyDeferred()
520
				// to know how many entries there will be at the end
521
				this._rowContainers[irow] = null;
522
				this._widgets[irow] = [];
523
				for (var jsubWidget = 0; jsubWidget < this.subtypes.length; ++jsubWidget) {
524
					this._widgets[irow][jsubWidget] = null;
461
				}
525
				}
462
			}
526
			}
463
			all(allReady).then(lang.hitch(this, function() {
527
464
				//console.log('### MultiInput: all resolved');
528
			// force the ready deferred to be updated
465
				this._readyDeferred.resolve();
529
			this._updateReadyDeferred();
466
				this.onValuesLoaded();
530
531
			// perform adding rows asynchronously
532
			tools.forEachAsync(newRows, lang.hitch(this, '__appendRow')).then(lang.hitch(this, function() {
533
				// all elements have been added to the DOM
534
				// add the new button
535
				if (this._nRenderedElements < this.max) {
536
					this._addNewButton();
537
				}
538
				this._updateReadyDeferred();
467
			}));
539
			}));
468
469
			// add the new button
470
			if (this._nRenderedElements < this.max) {
471
				this._addNewButton();
472
			}
473
		},
540
		},
474
541
475
		_popElements: function(n) {
542
		_popElements: function(n) {
 Lines 492-497    Link Here 
492
			// update the number of render elements
559
			// update the number of render elements
493
			this._nRenderedElements -= n;
560
			this._nRenderedElements -= n;
494
561
562
495
			// add the new button
563
			// add the new button
496
			this._addNewButton();
564
			this._addNewButton();
497
		},
565
		},
(-)umc/tools.js (+30 lines)
 Lines 704-709    Link Here 
704
			return res;
704
			return res;
705
		},
705
		},
706
706
707
		forEachAsync: function(/*Array*/ list, /*Function*/ callback, /*Object?*/ scope, /*Integer?*/ chunkSize, /*Integer?*/ timeout) {
708
			chunkSize = chunkSize || 1;
709
			scope = scope || _window.global;
710
			timeout = timeout || 0;
711
712
			var nChunks = Math.ceil(list.length / chunkSize);
713
			var nChunksDone = 0;
714
			var deferred = new Deferred();
715
			var _hasFinished = function() {
716
				++nChunksDone;
717
				if (nChunksDone >= nChunks) {
718
					deferred.resolve();
719
				}
720
			};
721
722
			var _processChunk = function(istart) {
723
				for (var i = istart; i < list.length && i < istart + chunkSize; ++i) {
724
					callback(list[i], i);
725
				}
726
				_hasFinished();
727
			};
728
729
			// process each chunk asynchronously by calling setTimeout
730
			for (var ichunk = 0; ichunk < list.length; ichunk += chunkSize) {
731
				setTimeout(lang.hitch(scope, _processChunk, ichunk), timeout * ichunk);
732
			}
733
734
			return deferred;
735
		},
736
707
		assert: function(/* boolean */ booleanValue, /* string? */ message){
737
		assert: function(/* boolean */ booleanValue, /* string? */ message){
708
			// summary:
738
			// summary:
709
			// 		Throws an exception if the assertion fails.
739
			// 		Throws an exception if the assertion fails.

Return to bug 31376