Univention Bugzilla – Attachment 5340 Details for
Bug 31376
Large MultiInput loads slow (-> progress needed) and heavy (-> relaxation needed)
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Patch that implements lazy loading of elements in MultiInput
umc-lazyload-multiinput.patch (text/plain), 17.92 KB, created by
Alexander Kläser
on 2013-07-29 19:42:17 CEST
(
hide
)
Description:
Patch that implements lazy loading of elements in MultiInput
Filename:
MIME Type:
Creator:
Alexander Kläser
Created:
2013-07-29 19:42:17 CEST
Size:
17.92 KB
patch
obsolete
>Index: umc/widgets/MultiInput.js >=================================================================== >--- umc/widgets/MultiInput.js (Revision 42892) >+++ umc/widgets/MultiInput.js (Arbeitskopie) >@@ -83,10 +83,14 @@ > > _readyDeferred: null, > >+ _allWidgetsBuiltDeferred: null, >+ > _startupDeferred: null, > > _blockChangeEvents: false, > >+ _hasSubtypeLabel: false, >+ > _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 >@@ -126,11 +130,16 @@ > this._readyDeferred = new Deferred(); > > this._startupDeferred = new Deferred(); >+ this._allWidgetsBuiltDeferred = 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); > >+ this._hasSubtypeLabel = array.some(this.subtypes, function(iwidget) { >+ return iwidget.label; >+ }); >+ > // initiate other properties > this._rowContainers = []; > this._widgets = []; >@@ -171,7 +180,7 @@ > this.inherited(arguments); > > // add empty element >- this._appendElements(1); >+ this._appendRows(); > }, > > _loadValues: function(depends) { >@@ -179,7 +188,7 @@ > this._lastDepends = depends; > array.forEach(this._widgets, function(iwidgets) { > array.forEach(iwidgets, function(jwidget) { >- if ('_loadValues' in jwidget) { >+ if (jwidget && '_loadValues' in jwidget) { > jwidget._loadValues(depends); > } > }); >@@ -187,6 +196,7 @@ > }, > > _setAllValues: function(_valList) { >+ var _valList = lang.clone(_valList); > this._blockChangeEvents = true; > var valList = _valList; > if (!(valList instanceof Array)) { >@@ -196,45 +206,46 @@ > // adjust the number of rows > var diff = valList.length - this._nRenderedElements; > if (diff > 0) { >- this._appendElements(diff); >+ this._appendRows(diff); > } > else if (diff < 0) { > this._popElements(-diff); > } > >- // set all values >- array.forEach(valList, function(ival, irow) { >- if (irow >= this._widgets.length) { >- // break >- return false; >- } >+ this._allWidgetsBuiltDeferred.then(lang.hitch(this, function() { >+ // set all values >+ array.forEach(valList, function(ival, irow) { >+ if (irow >= this._widgets.length) { >+ return; >+ } > >- var rowVals = []; >- if (typeof ival == "string") { >- // entry is string .. we need to parse it if we have a delimiter >- if (this.delimiter) { >- rowVals = ival.split(this.delimiter); >+ var rowVals = []; >+ if (typeof ival == "string") { >+ // entry is string .. we need to parse it if we have a delimiter >+ if (this.delimiter) { >+ rowVals = ival.split(this.delimiter); >+ } >+ else { >+ rowVals = [ ival ]; >+ } > } >- else { >- rowVals = [ ival ]; >+ else if (ival instanceof Array) { >+ rowVals = ival; > } >- } >- else if (ival instanceof Array) { >- rowVals = ival; >- } > >- // set values >- for (var j = 0; j < this.subtypes.length; ++j) { >- var val = j >= rowVals.length ? '' : rowVals[j]; >- this._widgets[irow][j].set('value', val); >+ // set values >+ for (var j = 0; j < this.subtypes.length; ++j) { >+ var val = j >= rowVals.length ? '' : rowVals[j]; >+ this._widgets[irow][j].set('value', val); > >- // for dynamic combo boxes, we need to save the value as "initial value" >- if (this._widgets[irow][j].setInitialValue) { >- this._widgets[irow][j].setInitialValue(val, false); >+ // for dynamic combo boxes, we need to save the value as "initial value" >+ if (this._widgets[irow][j].setInitialValue) { >+ this._widgets[irow][j].setInitialValue(val, false); >+ } > } >- } >- }, this); >- this._blockChangeEvents = false; >+ }, this); >+ this._blockChangeEvents = false; >+ })); > }, > > _setValueAttr: function(_vals) { >@@ -252,22 +263,29 @@ > }, > > _setDisabledAttr: function ( value ) { >- var i; >- for ( i = 0; i < this._rowContainers.length; ++i) { >- array.forEach( this._rowContainers[ i ].getChildren(), function( widget ) { >- widget.set( 'disabled', value ); >- } ); >- } >- this.disabled = value; >+ this._allWidgetsBuiltDeferred.then(lang.hitch(this, function() { >+ var i; >+ for ( i = 0; i < this._rowContainers.length; ++i) { >+ var irow = this._rowContainers[i]; >+ array.forEach( irow ? irow.getChildren() : [], function( widget ) { >+ widget.set('disabled', value); >+ } ); >+ } >+ })); >+ this._set('disabled', value); > }, > > _getAllValues: function() { >- var i, j, val, isSet, vals = [], rowVals = []; >+ var i, j, jwidget, val, isSet, vals = [], rowVals = []; > for (i = 0; i < this._widgets.length; ++i) { > rowVals = []; > isSet = false; > for (j = 0; j < this._widgets[i].length; ++j) { >- val = this._widgets[i][j].get('value'); >+ jwidget = this._widgets[i][j]; >+ if (!jwidget) { >+ continue; >+ } >+ val = jwidget.get('value'); > isSet = isSet || ('' !== val); > if (!tools.inheritsFrom(this._widgets[i][j], 'umc.widgets.Button')) { > rowVals.push(val); >@@ -315,161 +333,210 @@ > return; > } > >- // verify whether label information for subtypes are given >- var hasSubTypeLabels = false; >- array.forEach(this.subtypes, function(iwidget) { >- hasSubTypeLabels = hasSubTypeLabels || iwidget.label; >- }); >- > // create 'new' button > var btn = this.own(new Button({ > disabled: this.disabled, > iconClass: 'umcIconAdd', >- onClick: lang.hitch(this, '_appendElements', 1), >+ onClick: lang.hitch(this, '_appendRows'), > 'class': 'umcMultiInputAddButton' > }))[0]; > > // wrap a button with a LabelPane > this._newButton = this.own(new LabelPane({ > content: btn, >- label: this._nRenderedElements === 1 && hasSubTypeLabels ? ' ' : '' // only keep the label for the first row >+ label: this._nRenderedElements === 1 && this._hasSubtypeLabel ? ' ' : '' // only keep the label for the first row > }))[0]; > > // add button to last row > this._rowContainers[this._rowContainers.length - 1].addChild(this._newButton); > }, > >- _appendElements: function(n) { >- if (n < 1) { >- return; >+ _updateReadyDeferred: function() { >+ // check all current elements whether they are ready >+ var nReady = 0; >+ var nElements = 0; >+ var nBuiltElements = 0; >+ var i, j; >+ for (i = 0; i < this._widgets.length; ++i) { >+ for (j = 0; j < this._widgets[i].length; ++j, ++nElements) { >+ //console.log(lang.replace('### MultiInput: widget[{0}][{1}]: waiting -> ', [i, j]), this._widgets[i][j].ready()); >+ var jwidget = this._widgets[i][j]; >+ nBuiltElements += jwidget ? 1 : 0; >+ var jreadyDeferred = jwidget && jwidget.ready ? jwidget.ready() : null; >+ if (!jreadyDeferred) { >+ ++nReady; >+ } >+ else if (jreadyDeferred.isFulfilled()) { >+ ++nReady; >+ } >+ } > } > >- // initiate a new Deferred object in case there is none already pending >- if (this._readyDeferred.isFulfilled()) { >- this._readyDeferred = new Deferred(); >+ if (nReady < nElements) { >+ if (this._readyDeferred.isFulfilled()) { >+ // initiate a new Deferred object if none is pending >+ this._readyDeferred = new Deferred(); >+ } >+ >+ // update progress >+ this._readyDeferred.progress({ >+ max: nElements, >+ current: nReady, >+ percentage: 100 * nReady / nElements >+ }); > } > >- // remove the 'new' button >- this._removeNewButton(); >+ if (nReady == nElements && !this._readyDeferred.isFulfilled()) { >+ // all elements are ready >+ this._readyDeferred.resolve(); >+ this.onValuesLoaded(); >+ } > >- var nFinal = this._nRenderedElements + n; >- for (var irow = this._nRenderedElements; irow < nFinal && irow < this.max; ++irow, ++this._nRenderedElements) { >- // add all other elements with '__' such that they will be ignored by umc/widgets/Form >- var order = [], widgetConfs = []; >- array.forEach(this.subtypes, function(iwidget, i) { >- // add the widget configuration dict to the list of widgets >- var iname = '__' + this.name + '-' + irow + '-' + i; >- var iconf = lang.mixin({}, iwidget, { >- disabled: this.disabled, >- threshold: this.threshold, // for UDM-threshold >- name: iname, >- value: '', >- dynamicValues: lang.partial(iwidget.dynamicValues, iname) >- }); >- if (iwidget.dynamicValuesInfo) { >- iconf.dynamicValuesInfo = lang.partial(iwidget.dynamicValuesInfo, iname); >- } >- widgetConfs.push(iconf); >+ if (nBuiltElements < nElements && this._allWidgetsBuiltDeferred.isFulfilled()) { >+ // initiate a new Deferred object >+ this._allWidgetsBuiltDeferred = new Deferred(); >+ } > >- // add the name of the widget to the list of widget names >- order.push(iname); >- }, this); >+ if (nBuiltElements == nElements && !this._allWidgetsBuiltDeferred.isFulfilled()) { >+ // all elements have been built >+ this._allWidgetsBuiltDeferred.resolve(); >+ } >+ }, > >+ __appendRow: function(irow) { >+ var order = [], widgetConfs = []; >+ array.forEach(this.subtypes, function(iwidget, i) { >+ // add the widget configuration dict to the list of widgets >+ var iname = '__' + this.name + '-' + irow + '-' + i; >+ var iconf = lang.mixin({}, iwidget, { >+ disabled: this.disabled, >+ threshold: this.threshold, // for UDM-threshold >+ name: iname, >+ value: '', >+ dynamicValues: lang.partial(iwidget.dynamicValues, iname) >+ }); >+ if (iwidget.dynamicValuesInfo) { >+ iconf.dynamicValuesInfo = lang.partial(iwidget.dynamicValuesInfo, iname); >+ } >+ widgetConfs.push(iconf); > >- // render the widgets >- var widgets = render.widgets(widgetConfs, this); >+ // add the name of the widget to the list of widget names >+ order.push(iname); >+ }, this); > >- // if we have a button, we need to pass the value and index if the >- // current element >- tools.forIn(widgets, function(ikey, iwidget) { >- var myrow = irow; >- if (tools.inheritsFrom(iwidget, 'umc.widgets.Button') && typeof iwidget.callback == "function") { >- var callbackOrg = iwidget.callback; >- iwidget.callback = lang.hitch(this, function() { >- callbackOrg(this.get('value')[myrow], myrow); >- }); >- } >- }, this); > >- // find out whether all items do have a label >- var hasSubTypeLabels = array.filter(this.subtypes, function(iwidget) { >- return iwidget.label; >- }).length > 0; >+ // render the widgets >+ var widgets = render.widgets(widgetConfs, this); > >- // layout widgets >- var visibleWidgets = array.map(order, function(iname) { >- return widgets[iname]; >- }); >- var rowContainer = this.own(new ContainerWidget({}))[0]; >- array.forEach(order, function(iname) { >- // add widget to row container (wrapped by a LabelPane) >- // only keep the label for the first row >- var iwidget = widgets[iname]; >- var label = irow !== 0 ? '' : iwidget.label; >- if (tools.inheritsFrom(iwidget, 'umc.widgets.Button')) { >- label = irow !== 0 ? '' : ' '; >- } >- rowContainer.addChild(new LabelPane({ >- disabled: this.disabled, >- content: iwidget, >- label: label >- })); >+ // if we have a button, we need to pass the value and index of the >+ // current element >+ tools.forIn(widgets, function(ikey, iwidget) { >+ var myrow = irow; >+ if (tools.inheritsFrom(iwidget, 'umc.widgets.Button') && typeof iwidget.callback == "function") { >+ var callbackOrg = iwidget.callback; >+ iwidget.callback = lang.hitch(this, function() { >+ callbackOrg(this.get('value')[myrow], myrow); >+ }); >+ } >+ }, this); > >- // register to value changes >- this.own(iwidget.watch('value', lang.hitch(this, function() { >- if (!this._blockChangeEvents) { >- this._set('value', this.get('value')); >- } >- }))); >- }, this); >- >- // add a 'remove' button at the end of the row >- var button = this.own(new Button({ >+ // layout widgets >+ var visibleWidgets = array.map(order, function(iname) { >+ return widgets[iname]; >+ }); >+ var rowContainer = this.own(new ContainerWidget({}))[0]; >+ array.forEach(order, function(iname) { >+ // add widget to row container (wrapped by a LabelPane) >+ // only keep the label for the first row >+ var iwidget = widgets[iname]; >+ var label = irow !== 0 ? '' : iwidget.label; >+ if (tools.inheritsFrom(iwidget, 'umc.widgets.Button')) { >+ label = irow !== 0 ? '' : ' '; >+ } >+ rowContainer.addChild(new LabelPane({ > disabled: this.disabled, >- iconClass: 'umcIconDelete', >- onClick: lang.hitch(this, '_removeElement', irow), >- 'class': 'umcMultiInputRemoveButton' >- }))[0]; >- rowContainer.addChild(new LabelPane({ >- content: button, >- label: irow === 0 && hasSubTypeLabels ? ' ' : '' // only keep the label for the first row >+ content: iwidget, >+ label: label > })); > >- // add row >- this._widgets.push(visibleWidgets); >- this._rowContainers.push(rowContainer); >- this._startupDeferred.then(lang.hitch(rowContainer, 'startup')); >- this.addChild(rowContainer); >+ // register to value changes >+ this.own(iwidget.watch('value', lang.hitch(this, function() { >+ if (!this._blockChangeEvents) { >+ this._set('value', this.get('value')); >+ } >+ }))); >+ }, this); > >- // call the _loadValues method by hand >- array.forEach(order, function(iname) { >- var iwidget = widgets[iname]; >- if ('_loadValues' in iwidget) { >- iwidget._loadValues(this._lastDepends); >- } >- }, this); >- //this._readyDeferred.progress(_('%(i)s / %(len)s values built', {i: irow, len: nFinal})); >+ // add a 'remove' button at the end of the row >+ var button = this.own(new Button({ >+ disabled: this.disabled, >+ iconClass: 'umcIconDelete', >+ onClick: lang.hitch(this, '_removeElement', irow), >+ 'class': 'umcMultiInputRemoveButton' >+ }))[0]; >+ rowContainer.addChild(new LabelPane({ >+ content: button, >+ label: irow === 0 && this._hasSubtypeLabel ? ' ' : '' // only keep the label for the first row >+ })); >+ >+ // add row >+ this._widgets[irow] = visibleWidgets; >+ this._rowContainers[irow] = rowContainer; >+ this._startupDeferred.then(lang.hitch(rowContainer, 'startup')); >+ this.addChild(rowContainer); >+ >+ // call the _loadValues method by hand >+ array.forEach(order, function(iname) { >+ var iwidget = widgets[iname]; >+ if ('_loadValues' in iwidget) { >+ iwidget._loadValues(this._lastDepends); >+ } >+ }, this); >+ >+ // update the ready deferred know and when the widget itself is ready >+ this._updateReadyDeferred(); >+ var allReady = []; >+ tools.forIn(widgets, function(ikey, iwidget) { >+ allReady.push(iwidget.ready ? iwidget.ready() : null); >+ }); >+ all(allReady).then(lang.hitch(this, '_updateReadyDeferred')); >+ }, >+ >+ _appendRows: function(n) { >+ n = n || 1; >+ if (n < 1) { >+ return; > } > >- // 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); >+ // remove the 'new' button >+ this._removeNewButton(); >+ >+ var nFinal = this._nRenderedElements + n; >+ var newRows = []; >+ for (var irow = this._nRenderedElements; irow < nFinal && irow < this.max; ++irow, ++this._nRenderedElements) { >+ newRows.push(irow); >+ >+ // allocate indeces in 2D array _widget this allows _updateReadyDeferred() >+ // to know how many entries there will be at the end >+ this._rowContainers[irow] = null; >+ this._widgets[irow] = []; >+ for (var jsubWidget = 0; jsubWidget < this.subtypes.length; ++jsubWidget) { >+ this._widgets[irow][jsubWidget] = null; > } > } >- all(allReady).then(lang.hitch(this, function() { >- //console.log('### MultiInput: all resolved'); >- this._readyDeferred.resolve(); >- this.onValuesLoaded(); >+ >+ // force the ready deferred to be updated >+ this._updateReadyDeferred(); >+ >+ // perform adding rows asynchronously >+ tools.forEachAsync(newRows, lang.hitch(this, '__appendRow')).then(lang.hitch(this, function() { >+ // all elements have been added to the DOM >+ // add the new button >+ if (this._nRenderedElements < this.max) { >+ this._addNewButton(); >+ } >+ this._updateReadyDeferred(); > })); >- >- // add the new button >- if (this._nRenderedElements < this.max) { >- this._addNewButton(); >- } > }, > > _popElements: function(n) { >@@ -492,6 +559,7 @@ > // update the number of render elements > this._nRenderedElements -= n; > >+ > // add the new button > this._addNewButton(); > }, >Index: umc/tools.js >=================================================================== >--- umc/tools.js (Revision 42892) >+++ umc/tools.js (Arbeitskopie) >@@ -704,6 +704,36 @@ > return res; > }, > >+ forEachAsync: function(/*Array*/ list, /*Function*/ callback, /*Object?*/ scope, /*Integer?*/ chunkSize, /*Integer?*/ timeout) { >+ chunkSize = chunkSize || 1; >+ scope = scope || _window.global; >+ timeout = timeout || 0; >+ >+ var nChunks = Math.ceil(list.length / chunkSize); >+ var nChunksDone = 0; >+ var deferred = new Deferred(); >+ var _hasFinished = function() { >+ ++nChunksDone; >+ if (nChunksDone >= nChunks) { >+ deferred.resolve(); >+ } >+ }; >+ >+ var _processChunk = function(istart) { >+ for (var i = istart; i < list.length && i < istart + chunkSize; ++i) { >+ callback(list[i], i); >+ } >+ _hasFinished(); >+ }; >+ >+ // process each chunk asynchronously by calling setTimeout >+ for (var ichunk = 0; ichunk < list.length; ichunk += chunkSize) { >+ setTimeout(lang.hitch(scope, _processChunk, ichunk), timeout * ichunk); >+ } >+ >+ return deferred; >+ }, >+ > assert: function(/* boolean */ booleanValue, /* string? */ message){ > // summary: > // Throws an exception if the assertion fails.
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 31376
: 5340