diff --git a/api/js/etemplate/et2_extension_nextmatch.js b/api/js/etemplate/et2_extension_nextmatch.js index e0425b91f7..47869f1110 100644 --- a/api/js/etemplate/et2_extension_nextmatch.js +++ b/api/js/etemplate/et2_extension_nextmatch.js @@ -113,6 +113,8 @@ var et2_nextmatch = /** @class */ (function (_super) { */ function et2_nextmatch(_parent, _attrs, _child) { var _this = _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_nextmatch._attributes, _child || {})) || this; + // Nextmatch can't render while hidden, we store refresh requests for later + _this._queued_refreshes = []; // When printing, we change the layout around. Keep some values so it can be restored after _this.print = { old_height: 0, @@ -470,14 +472,12 @@ var et2_nextmatch = /** @class */ (function (_super) { if (this.controller === null || !this.div) { return; } + // Make sure we're dealing with arrays + if (typeof _row_ids == 'string' || typeof _row_ids == 'number') + _row_ids = [_row_ids]; if (!this.div.is(':visible')) // run refresh, once we become visible again { - jQuery(this.getInstanceManager().DOMContainer.parentNode).one('show.et2_nextmatch', - // Important to use anonymous function instead of just 'this.refresh' because - // of the parameters passed - function () { this.nm.refresh(this.ids, this.type); } - .bind({ nm: this, ids: _row_ids, type: _type })); - return; + return this._queue_refresh(_row_ids, _type); } // Make some changes in what we're doing based on preference var update_pref = egw.preference("lazy-update") || 'lazy'; @@ -492,8 +492,6 @@ var et2_nextmatch = /** @class */ (function (_super) { } if (typeof _type == 'undefined') _type = et2_nextmatch.EDIT; - if (typeof _row_ids == 'string' || typeof _row_ids == 'number') - _row_ids = [_row_ids]; if (typeof _row_ids == "undefined" || _row_ids === null) { this.applyFilters(); // Trigger an event so app code can act on it @@ -684,6 +682,62 @@ var et2_nextmatch = /** @class */ (function (_super) { } this.nm.egw().dataUnregisterUID(this.uid, this.nm._push_add_callback, this); }; + /** + * Queue a refresh request until later, when nextmatch is visible + * + * Nextmatch can't re-draw anything while it's hidden (it messes up the sizing when it renders) so we can't actually + * do a refresh right now. Queue it up and when visible again we'll update then. If we get too many changes + * queued, we'll throw them all away and do a full refresh. + * + * @param _row_ids + * @param _type + * @private + */ + et2_nextmatch.prototype._queue_refresh = function (_row_ids, _type) { + // Maximum number of requests to queue. 50 chosen arbitrarily just to limit things + var max_queued = 50; + if (this._queued_refreshes === null) { + // Already too many, we'll refresh later + return; + } + // Cancel any existing listener + var tab = jQuery(this.getInstanceManager().DOMContainer.parentNode) + .off('show.et2_nextmatch') + .one('show.et2_nextmatch', this._queue_refresh_callback.bind(this)); + // Too many? Forget it, we'll refresh everything. + if (this._queued_refreshes.length >= max_queued) { + this._queued_refreshes = null; + return; + } + // Skip if already in array + if (this._queued_refreshes.some(function (queue) { return queue.ids.length === _row_ids.length && queue.ids.every(function (value, index) { return value === _row_ids[index]; }); })) { + return; + } + this._queued_refreshes.push({ ids: _row_ids, type: _type }); + }; + et2_nextmatch.prototype._queue_refresh_callback = function () { + if (this._queued_refreshes === null) { + // Still bound, but length is 0 - full refresh time + this._queued_refreshes = []; + return this.applyFilters(); + } + var types = {}; + types[et2_nextmatch.ADD] = []; + types[et2_nextmatch.UPDATE] = []; + types[et2_nextmatch.UPDATE_IN_PLACE] = []; + types[et2_nextmatch.DELETE] = []; + for (var _i = 0, _a = this._queued_refreshes; _i < _a.length; _i++) { + var refresh = _a[_i]; + types[refresh.type] = types[refresh.type].concat(refresh.ids); + } + this._queued_refreshes = []; + for (var type in types) { + if (types[type].length > 0) { + // Fire each change type once will all changed IDs + this.refresh(types[type].filter(function (v, i, a) { return a.indexOf(v) === i; }), type); + } + } + }; /** * Is this nextmatch currently sorted by "modified" date * diff --git a/api/js/etemplate/et2_extension_nextmatch.ts b/api/js/etemplate/et2_extension_nextmatch.ts index 326fe41e42..4a0f84c18e 100644 --- a/api/js/etemplate/et2_extension_nextmatch.ts +++ b/api/js/etemplate/et2_extension_nextmatch.ts @@ -269,6 +269,9 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2 // Window timer for automatically refreshing private _autorefresh_timer: number; + // Nextmatch can't render while hidden, we store refresh requests for later + private _queued_refreshes : null|{type : string, ids: string[]}[] = []; + // When printing, we change the layout around. Keep some values so it can be restored after private print: PrintSettings = { old_height: 0, @@ -739,15 +742,13 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2 { return; } + + // Make sure we're dealing with arrays + if (typeof _row_ids == 'string' || typeof _row_ids == 'number') _row_ids = [_row_ids]; + if (!this.div.is(':visible')) // run refresh, once we become visible again { - jQuery(this.getInstanceManager().DOMContainer.parentNode).one('show.et2_nextmatch', - // Important to use anonymous function instead of just 'this.refresh' because - // of the parameters passed - function() {this.nm.refresh(this.ids, this.type);} - .bind({nm: this, ids: _row_ids, type: _type}) - ); - return; + return this._queue_refresh(_row_ids, _type); } // Make some changes in what we're doing based on preference @@ -766,7 +767,6 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2 } if (typeof _type == 'undefined') _type = et2_nextmatch.EDIT; - if (typeof _row_ids == 'string' || typeof _row_ids == 'number') _row_ids = [_row_ids]; if (typeof _row_ids == "undefined" || _row_ids === null) { this.applyFilters(); @@ -1001,6 +1001,77 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2 this.nm.egw().dataUnregisterUID(this.uid, this.nm._push_add_callback, this); } + /** + * Queue a refresh request until later, when nextmatch is visible + * + * Nextmatch can't re-draw anything while it's hidden (it messes up the sizing when it renders) so we can't actually + * do a refresh right now. Queue it up and when visible again we'll update then. If we get too many changes + * queued, we'll throw them all away and do a full refresh. + * + * @param _row_ids + * @param _type + * @private + */ + protected _queue_refresh(_row_ids : string[], _type : string) + { + // Maximum number of requests to queue. 50 chosen arbitrarily just to limit things + const max_queued = 50; + + if(this._queued_refreshes === null) + { + // Already too many, we'll refresh later + return; + } + + // Cancel any existing listener + let tab = jQuery(this.getInstanceManager().DOMContainer.parentNode) + .off('show.et2_nextmatch') + .one('show.et2_nextmatch', this._queue_refresh_callback.bind(this)); + + + // Too many? Forget it, we'll refresh everything. + if(this._queued_refreshes.length >= max_queued) + { + this._queued_refreshes = null; + return; + } + + // Skip if already in array + if(this._queued_refreshes.some( queue => queue.ids.length === _row_ids.length && queue.ids.every((value, index) => value === _row_ids[index]))) + { + return; + } + this._queued_refreshes.push({ids: _row_ids, type: _type}); + } + + protected _queue_refresh_callback() + { + if(this._queued_refreshes === null) + { + // Still bound, but length is 0 - full refresh time + this._queued_refreshes = []; + return this.applyFilters(); + } + let types = {}; + types[et2_nextmatch.ADD] = []; + types[et2_nextmatch.UPDATE] = []; + types[et2_nextmatch.UPDATE_IN_PLACE] = []; + types[et2_nextmatch.DELETE] = []; + for(let refresh of this._queued_refreshes) + { + types[refresh.type] = types[refresh.type].concat(refresh.ids); + } + this._queued_refreshes = []; + for(let type in types) + { + if(types[type].length > 0) + { + // Fire each change type once will all changed IDs + this.refresh(types[type].filter((v, i, a) => a.indexOf(v) === i), type); + } + } + } + /** * Is this nextmatch currently sorted by "modified" date *