From 3f8bd1b494041a4e252289f9b62b39c3f4b14969 Mon Sep 17 00:00:00 2001 From: nathangray Date: Fri, 31 Jan 2020 13:07:27 -0700 Subject: [PATCH] Get nextmatch rendering, sort of --- api/js/etemplate/et2_core_arrayMgr.js | 1 + api/js/etemplate/et2_core_arrayMgr.ts | 2 +- api/js/etemplate/et2_dataview.js | 27 +- api/js/etemplate/et2_dataview.ts | 20 +- api/js/etemplate/et2_dataview_controller.js | 1823 +++++++---------- api/js/etemplate/et2_dataview_controller.ts | 1128 ++++++++++ .../et2_dataview_controller_selection.js | 1155 +++++------ .../et2_dataview_controller_selection.ts | 720 +++++++ api/js/etemplate/et2_dataview_interfaces.js | 12 + api/js/etemplate/et2_dataview_interfaces.ts | 20 +- .../etemplate/et2_dataview_model_columns.js | 106 +- .../etemplate/et2_dataview_model_columns.ts | 125 +- api/js/etemplate/et2_dataview_view_grid.js | 60 +- api/js/etemplate/et2_dataview_view_grid.ts | 21 +- api/js/etemplate/et2_dataview_view_row.js | 327 ++- api/js/etemplate/et2_dataview_view_row.ts | 203 ++ .../et2_dataview_view_rowProvider.js | 200 +- .../et2_dataview_view_rowProvider.ts | 139 ++ api/js/etemplate/et2_dataview_view_spacer.js | 176 +- api/js/etemplate/et2_dataview_view_spacer.ts | 112 + api/js/etemplate/et2_dataview_view_tile.js | 172 +- api/js/etemplate/et2_dataview_view_tile.ts | 107 + api/js/etemplate/et2_extension_nextmatch.js | 169 +- api/js/etemplate/et2_extension_nextmatch.ts | 41 +- .../et2_extension_nextmatch_controller.js | 1323 ++++++------ .../et2_extension_nextmatch_controller.ts | 769 +++++++ .../et2_extension_nextmatch_rowProvider.js | 1175 +++++------ .../et2_extension_nextmatch_rowProvider.ts | 706 +++++++ api/js/etemplate/et2_types.d.ts | 2 +- api/js/etemplate/et2_widget_number.js | 18 +- api/js/etemplate/et2_widget_number.ts | 2 +- api/js/etemplate/et2_widget_radiobox.js | 2 +- api/js/etemplate/et2_widget_radiobox.ts | 2 +- 33 files changed, 7070 insertions(+), 3795 deletions(-) create mode 100644 api/js/etemplate/et2_dataview_controller.ts create mode 100644 api/js/etemplate/et2_dataview_controller_selection.ts create mode 100644 api/js/etemplate/et2_dataview_view_row.ts create mode 100644 api/js/etemplate/et2_dataview_view_rowProvider.ts create mode 100644 api/js/etemplate/et2_dataview_view_spacer.ts create mode 100644 api/js/etemplate/et2_dataview_view_tile.ts create mode 100644 api/js/etemplate/et2_extension_nextmatch_controller.ts create mode 100644 api/js/etemplate/et2_extension_nextmatch_rowProvider.ts diff --git a/api/js/etemplate/et2_core_arrayMgr.js b/api/js/etemplate/et2_core_arrayMgr.js index ecd18ada7b..5b6029d423 100644 --- a/api/js/etemplate/et2_core_arrayMgr.js +++ b/api/js/etemplate/et2_core_arrayMgr.js @@ -416,4 +416,5 @@ function et2_arrayMgrs_expand(_owner, _mgrs, _data, _row) { // Return the resulting managers object return result; } +exports.et2_arrayMgrs_expand = et2_arrayMgrs_expand; //# sourceMappingURL=et2_core_arrayMgr.js.map \ No newline at end of file diff --git a/api/js/etemplate/et2_core_arrayMgr.ts b/api/js/etemplate/et2_core_arrayMgr.ts index 58637791ef..29e91e47e4 100644 --- a/api/js/etemplate/et2_core_arrayMgr.ts +++ b/api/js/etemplate/et2_core_arrayMgr.ts @@ -432,7 +432,7 @@ export class et2_readonlysArrayMgr extends et2_arrayMgr { * existing array managers. * @param _row is the row for which the array managers will be opened. */ -function et2_arrayMgrs_expand(_owner: et2_widget, _mgrs: object, _data: object, _row: number) { +export function et2_arrayMgrs_expand(_owner: et2_widget, _mgrs: object, _data: object, _row: number) { // Create a copy of the given _mgrs associative array let result = {}; diff --git a/api/js/etemplate/et2_dataview.js b/api/js/etemplate/et2_dataview.js index bdb70885af..17e0df8fc5 100644 --- a/api/js/etemplate/et2_dataview.js +++ b/api/js/etemplate/et2_dataview.js @@ -9,19 +9,22 @@ * @author Andreas Stöckel * @copyright Stylite 2011-2012 * @version $Id$ - */ -Object.defineProperty(exports, "__esModule", { value: true }); + * + /*egw:uses /vendor/bower-asset/jquery/dist/jquery.js; et2_core_common; et2_dataview_model_columns; - et2_dataview_view_rowProvider; et2_dataview_view_grid; + et2_dataview_view_rowProvider; et2_dataview_view_resizeable; */ +Object.defineProperty(exports, "__esModule", { value: true }); var et2_dataview_model_columns_1 = require("./et2_dataview_model_columns"); var et2_dataview_view_resizeable_1 = require("./et2_dataview_view_resizeable"); +var et2_dataview_view_grid_1 = require("./et2_dataview_view_grid"); +var et2_dataview_view_rowProvider_1 = require("./et2_dataview_view_rowProvider"); /** * The et2_dataview class is the main class for displaying a dataview. The * dataview class manages the creation of the outer html nodes (like the table, @@ -64,11 +67,11 @@ var et2_dataview = /** @class */ (function () { this._clearHeader(); // Free the grid if (this.grid) { - this.grid.free(); + this.grid.destroy(); } // Free the row provider if (this.rowProvider) { - this.rowProvider.free(); + this.rowProvider.destroy(); } // Detatch the outer element this.table.remove(); @@ -323,13 +326,13 @@ var et2_dataview = /** @class */ (function () { if (this.relativeWidth) { // Set to selected width this.set_width(_w + "px"); - self.columnMgr.updated = true; + self.columnMgr.updated(); // Just triggers recalculation self.columnMgr.getColumnWidth(0); // Set relative widths to match var relative = self.columnMgr.totalWidth - self.columnMgr.totalFixed + _w; this.set_width(_w / relative); - for (var i = 0; i < self.columnMgr.columns.length; i++) { + for (var i = 0; i < self.columnMgr.columnCount(); i++) { var col = self.columnMgr.getColumnById(i); if (col == this || col.fixedWidth) continue; @@ -340,7 +343,7 @@ var et2_dataview = /** @class */ (function () { } else { this.set_width(this.relativeWidth ? (_w / self.columnMgr.totalWidth) : _w + "px"); - self.columnMgr.updated = true; + self.columnMgr.updated(); self.updateColumns(); } }, enc_column); @@ -383,13 +386,13 @@ var et2_dataview = /** @class */ (function () { } // Create the row provider if (this.rowProvider) { - this.rowProvider.free(); + this.rowProvider.destroy(); } - this.rowProvider = new et2_dataview_rowProvider(this.uniqueId, colIds); + this.rowProvider = new et2_dataview_view_rowProvider_1.et2_dataview_rowProvider(this.uniqueId, colIds); // Create the grid class and pass "19" as the starting average row height - this.grid = new et2_dataview_grid(null, null, this.egw, this.rowProvider, 19); + this.grid = new et2_dataview_view_grid_1.et2_dataview_grid(null, null, this.egw, this.rowProvider, 19); // Insert the grid into the DOM-Tree - var tr = jQuery(this.grid._nodes[0]); + var tr = jQuery(this.grid.getFirstNode()); this.containerTr.replaceWith(tr); this.containerTr = tr; }; diff --git a/api/js/etemplate/et2_dataview.ts b/api/js/etemplate/et2_dataview.ts index 86ac1f303b..aa22248be2 100644 --- a/api/js/etemplate/et2_dataview.ts +++ b/api/js/etemplate/et2_dataview.ts @@ -8,20 +8,22 @@ * @author Andreas Stöckel * @copyright Stylite 2011-2012 * @version $Id$ - */ + * /*egw:uses /vendor/bower-asset/jquery/dist/jquery.js; et2_core_common; et2_dataview_model_columns; - et2_dataview_view_rowProvider; et2_dataview_view_grid; + et2_dataview_view_rowProvider; et2_dataview_view_resizeable; */ import {et2_dataview_columns} from './et2_dataview_model_columns'; import {et2_dataview_view_resizable} from "./et2_dataview_view_resizeable"; +import {et2_dataview_grid} from "./et2_dataview_view_grid"; +import {et2_dataview_rowProvider} from "./et2_dataview_view_rowProvider" /** * The et2_dataview class is the main class for displaying a dataview. The @@ -122,13 +124,13 @@ export class et2_dataview // Free the grid if (this.grid) { - this.grid.free(); + this.grid.destroy(); } // Free the row provider if (this.rowProvider) { - this.rowProvider.free(); + this.rowProvider.destroy(); } // Detatch the outer element @@ -468,14 +470,14 @@ export class et2_dataview { // Set to selected width this.set_width(_w + "px"); - self.columnMgr.updated = true; + self.columnMgr.updated(); // Just triggers recalculation self.columnMgr.getColumnWidth(0); // Set relative widths to match var relative = self.columnMgr.totalWidth - self.columnMgr.totalFixed + _w; this.set_width(_w / relative); - for(var i = 0; i < self.columnMgr.columns.length; i++) + for(var i = 0; i < self.columnMgr.columnCount(); i++) { var col = self.columnMgr.getColumnById(i); if(col == this || col.fixedWidth) continue; @@ -487,7 +489,7 @@ export class et2_dataview else { this.set_width(this.relativeWidth ? (_w / self.columnMgr.totalWidth) : _w + "px"); - self.columnMgr.updated = true; + self.columnMgr.updated(); self.updateColumns(); } @@ -541,7 +543,7 @@ export class et2_dataview // Create the row provider if (this.rowProvider) { - this.rowProvider.free(); + this.rowProvider.destroy(); } this.rowProvider = new et2_dataview_rowProvider(this.uniqueId, colIds); @@ -550,7 +552,7 @@ export class et2_dataview this.grid = new et2_dataview_grid(null, null, this.egw, this.rowProvider, 19); // Insert the grid into the DOM-Tree - var tr = jQuery(this.grid._nodes[0]); + var tr = jQuery(this.grid.getFirstNode()); this.containerTr.replaceWith(tr); this.containerTr = tr; } diff --git a/api/js/etemplate/et2_dataview_controller.js b/api/js/etemplate/et2_dataview_controller.js index 4587ccc3f9..87abcd7ce7 100644 --- a/api/js/etemplate/et2_dataview_controller.js +++ b/api/js/etemplate/et2_dataview_controller.js @@ -1,3 +1,4 @@ +"use strict"; /** * EGroupware eTemplate2 * @@ -7,1052 +8,808 @@ * @link http://www.egroupware.org * @author Andreas Stöckel * @copyright Stylite 2011-2012 - * @version $Id$ - */ /*egw:uses - et2_core_common; - et2_core_inheritance; + et2_core_common; + et2_core_inheritance; - et2_dataview_interfaces; - et2_dataview_controller_selection; - et2_dataview_view_row; + et2_dataview_interfaces; + et2_dataview_controller_selection; + et2_dataview_view_row; + et2_dataview_view_tile; - egw_action.egw_action; + egw_action.egw_action; */ - +Object.defineProperty(exports, "__esModule", { value: true }); +var et2_dataview_controller_selection_1 = require("./et2_dataview_controller_selection"); +var et2_dataview_view_row_1 = require("./et2_dataview_view_row"); /** * The fetch timeout specifies the time during which the controller tries to * consolidate requests for rows. */ var ET2_DATAVIEW_FETCH_TIMEOUT = 50; - var ET2_DATAVIEW_STEPSIZE = 50; - /** * The et2_dataview_controller class is the intermediate layer between a grid * instance and the corresponding data source. It manages updating the grid, * as well as inserting and deleting rows. */ -var et2_dataview_controller = (function(){ "use strict"; return Class.extend({ - - // Maximum concurrent data requests. Additional ones are held in the queue. - CONCURRENT_REQUESTS: 5, - - /** - * Constructor of the et2_dataview_controller, connects to the grid - * callback. - * - * @param _grid is the grid the controller should controll. - * @param _dataProvider is an object implementing the et2_IDataProvider - * interface. - * @param _rowCallback is the callback function that gets called when a row - * is requested. - * @param _linkCallback is the callback function that gets called for - * requesting action links for a row. The row data, the index of the row and - * the uid are passed as parameters to the function. - * uid is passed to the function. - * @param _context is the context in which the _rowCallback and the - * _linkCallback are called. - * @param _actionObjectManager is the object that manages the action - * objects. - */ - init: function (_parentController, _grid, _dataProvider, _rowCallback, - _linkCallback, _context, _actionObjectManager) - { - // Copy the given arguments - this._parentController = _parentController; - this._grid = _grid; - this._dataProvider = _dataProvider; - this._rowCallback = _rowCallback; - this._linkCallback = _linkCallback; - this._context = _context; - - // Initialize list of child controllers - this._children = []; - - // Initialize the "index map" which contains all currently displayed - // containers hashed by the "index" - this._indexMap = {}; - - // Timer used for queing fetch requests - this._queueTimer = null; - - // Array which contains all currently queued row indices in the form of - // an associative array - this._queue = {}; - - // Current concurrent requests we have - this._request_queue = []; - - // Register the dataFetch callback - this._grid.setDataCallback(this._gridCallback, this); - - // Create the selection manager - this._selectionMgr = new et2_dataview_selectionManager( - this._parentController ? this._parentController._selectionMgr : null, - this._indexMap, - _actionObjectManager, - this._selectionFetchRange, - this._makeIndexVisible, - this - ); - - // Record the child - if(this._parentController != null) - { - this._parentController._children.push(this); - } - }, - - destroy: function () { - - // Destroy the selection manager - this._selectionMgr.free(); - - // Clear the selection timeout - this._clearTimer(); - - // Remove the child from the child list - if(this._parentController != null) - { - var idx = this._parentController._children.indexOf(this); - - if (idx >= 0) - { - // This element is no longer parent of the child - this._parentController._children.splice(idx, 1); - this._parentController = null; - } - } - }, - - /** - * The update function queries the server for changes in the currently - * managed index range -- those changes are then merged into the current - * view without a complete rebuild of every row. - * - * @param {boolean} clear Skip the fancy stuff, dump everything and start again. - * Completely clears the grid and selection. - */ - update: function (clear) { - - // --------- - - // TODO: Actually stuff here should be done if the server responds that - // there at all were some changes (needs implementation of "refresh") - - // Tell the grid not to try and update itself while we do this - this._grid.doInvalidate = false; - - if(clear) - { - // Scroll to top - this._grid.makeIndexVisible(0); - this._grid.clear(); - - // Free selection manager - this._selectionMgr.clear(); - - // Clear object manager - this._objectManager.clear(); - - // Clear the map - this._indexMap = {} - // Update selection manager, it uses this by reference - this._selectionMgr.setIndexMap(this._indexMap); - - // Clear the queue - this._queue = {}; - - // Invalidate the change detection, re-fetches any known rows - this._lastModification = 0; - } - // Remove all rows which are outside the view range - this._grid.cleanup(); - - // Get the currently visible range from the grid - var range = this._grid.getIndexRange(); - - // Force range.top and range.bottom to contain an integer - if (range.top === false) - { - range.top = range.bottom = 0; - } - this._request_queue = []; - - // Require that range from the server - this._queueFetch(et2_bounds(range.top, clear ? 0 : range.bottom + 1), 0, true); - }, - - /** - * Rebuilds the complete grid. - */ - reset: function () { - // Throw away all internal mappings and reset the timestamp - this._indexMap = {}; - // Update selection manager, it uses this by reference - this._selectionMgr.setIndexMap(this._indexMap); - - // Clear the grid - this._grid.clear(); - - // Clear the row queue - this._queue = {}; - - // Reset the request queue - this._request_queue = []; - - // Update the data - this.update(); - }, - - /** - * Loads the initial order. Do not call multiple times. - */ - loadInitialOrder: function (order) { - for (var i = 0; i < order.length; i++) - { - this._getIndexEntry(i).uid = order[i]; - } - }, - - /** - * Load initial data - * - * @param {string} uid_key Name of the unique row identifier field - * @param {Object} data Key / Value mapping of initial data. - */ - loadInitialData: function (uid_prefix, uid_key, data) { - var idx = 0; - for(var key in data) - { - // Skip any extra keys - if(typeof data[key] != "object" || data[key] == null || typeof data[key][uid_key] == "undefined") continue; - - // Add to row / uid map - var entry = this._getIndexEntry(idx++); - entry.uid = data[key][uid_key]+""; - if(entry.uid.indexOf(uid_prefix) < 0) - { - entry.uid = uid_prefix + "::" + entry.uid; - } - - // Add to data cache so grid will find it - egw.dataStoreUID(entry.uid, data[key]) - - // Don't try to insert the rows, grid will do that automatically - } - if(idx == 0) - { - // No rows, start with an empty - this._selectionMgr.clear(); - this._emptyRow(this._grid._total == 0); - } - }, - - /** - * Returns the depth of the controller instance. - */ - getDepth: function () { - - if (this._parentController) - { - return this._parentController.getDepth() + 1; - } - - return 0; - }, - - /** - * Set the data cache prefix - * The default is to use appname, but if you need to set it explicitly to - * something else to avoid conflicts. Use the same prefix everywhere for - * each type of data. eg. infolog for infolog entries, even if accessed via addressbook - */ - setPrefix: function(prefix) { - this.dataStorePrefix = prefix; - }, - - /** - * Returns the row information of the passed node, or null if not available - * - * @param {DOMNode} node - * @return {string|false} UID, or false if not found - */ - getRowByNode: function(node) { - // Whatever the node, find a TR - var row_node = jQuery(node).closest('tr'); - var row = false - - // Check index map - simple case - var indexed = this._getIndexEntry(row_node.index()); - if(indexed && indexed.row && indexed.row.getDOMNode() == row_node[0]) - { - row = indexed; - } - else - { - // Check whole index map - for(var index in this._indexMap) - { - indexed = this._indexMap[index]; - if( indexed && indexed.row && indexed.row.getDOMNode() == row_node[0]) - { - row = indexed; - break; - } - } - } - - // Check children - for(var i = 0; !row && i < this._children.length; i++) - { - var child_row = this._children[i].getRowByNode(node); - if(child_row !== false) row = child_row; - } - if(row && !row.controller) - { - row.controller = this; - } - return row; - }, - - /* -- PRIVATE FUNCTIONS -- */ - - - _getIndexEntry: function (_idx) { - // Create an entry in the index map if it does not exist yet - if (typeof this._indexMap[_idx] === "undefined") - { - this._indexMap[_idx] = { - "row": null, - "uid": null - }; - } - - // Always update the index of the entries before returning them. This is - // neccessary, as when we remove the uid from an entry without row, its - // index does not get updated any further - this._indexMap[_idx]["idx"] = _idx; - - return this._indexMap[_idx]; - }, - - /** - * Inserts a new data row into the grid. index and uid are derived from the - * given management entry. If the data for the given uid does not exist yet, - * a "loading" placeholder will be shown instead. The function will do - * nothing if there already is a row associated to the entry. This function - * will not re-insert a row if the entry already had a row. - * - * @param _entry is the management entry for the index the row will be - * displayed at. - * @param _update specifies whether the row should be updated if _entry.row - * already exists. - * @return true, if all data for the row has been available, false - * otherwise. - */ - _insertDataRow: function (_entry, _update) { - // Abort if the entry already has a row but the _insert flag is not set - if (_entry.row && !_update) - { - return true; - } - - // Context used for the callback functions - var ctx = {"self": this, "entry": _entry}; - - // Create a new row instance, if it does not exist yet - var createdRow = false; - if (!_entry.row) - { - createdRow = true; - _entry.row = this._createRow(ctx); - _entry.row.setDestroyCallback(this._destroyCallback, ctx); - } - - // Load the row data if we have a uid for the entry - this.hasData = false; // Gets updated by the _dataCallback - if (_entry.uid) - { - // Register the callback / immediately load the data - this._dataProvider.dataRegisterUID(_entry.uid, this._dataCallback, - ctx); - } - - // Display the loading "row prototype" if we don't have data for the row - if (!this.hasData) - { - // Get the average height, the "-5" derives from the td padding - var avg = Math.round(this._grid.getAverageHeight() - 5) + "px"; - var prototype = this._grid.getRowProvider().getPrototype("loading"); - jQuery("div", prototype).css("height", avg); - var node = _entry.row.getJNode(); - node.empty(); - node.append(prototype.children()); - } - - // Insert the row into the table -- the same row must never be inserted - // twice into the grid, so this function only executes the following - // code only if it is a newly created row. - if (createdRow && _entry.row) - { - this._grid.insertRow(_entry.idx, _entry.row); - } - - return this.hasData; - }, - - - /** - * Create a new row. - * - * @param {type} ctx - * @returns {et2_dataview_container} - */ - _createRow: function(ctx) { - return new et2_dataview_row(this._grid); - }, - - /** - * Function which gets called by the grid when data is requested. - * - * @param _idxStart is the index of the first row for which data is - * requested. - * @param _idxEnd is the index of the last requested row. - */ - _gridCallback: function (_idxStart, _idxEnd) { - - var needsData = false; - - // Iterate over all elements the dataview requested and create a row - // which indicates that we are currently loading data - for (var i = _idxStart; i <= _idxEnd; i++) - { - var entry = this._getIndexEntry(i); - - // Insert the row for the entry -- do not update rows which are - // already existing, as we do not have new data for those. - if (!this._insertDataRow(entry, false) && needsData === false) - { - needsData = i; - } - } - - // Queue fetching that data range - if (needsData !== false) - { - this._queueFetch(et2_bounds(needsData, _idxEnd + 1), needsData == _idxStart ? 0 : needsData > _idxStart ? 1 : -1, false); - } - }, - - /** - * The _queueFetch function is used to queue a fetch request. - * TODO: Refresh is currently not used - */ - _queueFetch: function (_range, _direction, _isUpdate) { - - // Force immediate to be false - _isUpdate = _isUpdate ? _isUpdate : false; - - // Push the requests onto the request queue - var start = Math.max(0, _range.top); - var end = Math.min(this._grid.getTotalCount(), _range.bottom); - for (var i = start; i < end; i++) - { - if (typeof this._queue[i] === "undefined") - { - this._queue[i] = _direction; // Stage 1 - queue for after current, -1 -- queue for before current - } - } - - // Start the queue timer, if this has not already been done - if (this._queueTimer === null && !_isUpdate) - { - var self = this; - egw.debug('log', 'Dataview queue: ', _range); - this._queueTimer = window.setTimeout(function () { - self._flushQueue(false); - }, ET2_DATAVIEW_FETCH_TIMEOUT); - } - - if (_isUpdate) - { - this._flushQueue(true); - } - }, - - /** - * Flushes the queue. - */ - _flushQueue: function (_isUpdate) { - - // Clear any still existing timer - this._clearTimer(); - - // Mark all elements in a radius of ET2_DATAVIEW_STEPSIZE - var marked = {}; - var r = _isUpdate ? 0 : Math.floor(ET2_DATAVIEW_STEPSIZE / 2); - var total = this._grid.getTotalCount(); - for (var key in this._queue) - { - if (this._queue[key] > 1) - continue; - - key = parseInt(key); - - var b = Math.max(0, key - r + (r * this._queue[key])); - var t = Math.min(key + r + (r * this._queue[key]), total - 1); - var c = 0; - for (var i = b; i <= t && c < ET2_DATAVIEW_STEPSIZE; i ++) - { - if (typeof this._queue[i] == "undefined" - || this._queue[i] <= 1) - { - this._queue[i] = 2; // Stage 2 -- pending or available - marked[i] = true; - c++; - } - } - } - - // Create a list with start indices and counts - var fetchList = []; - var entry = null; - var last = 0; - - // Get the int keys and sort the array numeric - var arr = et2_arrayIntKeys(marked).sort( - function(a,b){return a > b ? 1 : (a == b ? 0 : -1)}); - - for (var i = 0; i < arr.length; i++) - { - if (i == 0 || arr[i] - last > 1) - { - if (entry) - { - fetchList.push(entry); - } - entry = { - "start": arr[i], - "count": 1 - }; - } - else - { - entry.count++; - } - - last = arr[i]; - } - - if (entry) - { - fetchList.push(entry); - } - - // Special case: If there are no entries in the fetch list and this is - // an update, create an dummy entry, so that we'll get the current count - if (fetchList.length === 0 && _isUpdate) - { - fetchList.push({ - "start": 0, "count": 0 - }); - - // Disable grid invalidate, or it might request again before we're done - this._grid.doInvalidate = false; - } - - egw.debug("log", "Dataview flush", fetchList); - // Execute all queries - for (var i = 0; i < fetchList.length; i++) - { - // Build the query - var query = { - "start": fetchList[i].start, - "num_rows": fetchList[i].count, - "refresh": false - }; - - // Context used in the callback function - var ctx = { - "self": this, - "start": query.start, - "count": query.num_rows, - "lastModification": this._lastModification - }; - if(this.dataStorePrefix) - { - ctx.prefix = this.dataStorePrefix; - } - - this._queueRequest(query, ctx); - } - }, - - /** - * Queue a request for data - * @param {Object} query - * @param {Object} ctx - */ - _queueRequest: function _queueRequest(query, ctx) - { - this._request_queue.push({ - query: query, - context: ctx, - // Start pending, set to 1 when request sent - status: 0 - }); - - this._fetchQueuedRequest(); - }, - - /** - * Fetch data for a queued request, subject to rate limit - */ - _fetchQueuedRequest: function _fetchQueuedRequest() - { - // Check to see if there's room - var count = 0; - for (var i = 0; i < this._request_queue.length; i++) - { - if(this._request_queue[i].status > 0) count++; - } - // Too many requests, will try again after response is received - if(count >= this.CONCURRENT_REQUESTS || this._request_queue.length === 0) - { - return; - } - - // Keep at least 1 previous pending - var keep = 1; - - // The most recent is the one the user's most interested in - var request = null; - for(var i = this._request_queue.length - 1; i >= 0; i--) - { - // Only interested in pending requests (status 0) - if(this._request_queue[i].status != 0) - { - continue; - } - if(request == null) - { - request = this._request_queue[i]; - } - else if (keep > 0) - { - keep--; - } - else if (keep <= 0) - { - // Cancel pending, they've probably scrolled past. - this._request_queue.splice(i,1); - } - } - if(request == null) return; - - // Request being sent - request.status = 1; - - // Call the callback - this._dataProvider.dataFetch(request.query, this._fetchCallback, request.context); - }, - - _clearTimer: function () { - - // Reset the queue timer upon destruction - if (this._queueTimer) - { - window.clearTimeout(this._queueTimer); - this._queueTimer = null; - } - - }, - - /** - * Called by the data source when the data changes - * - * @param _data Object|null New data, or null. Null will remove the row. - */ - _dataCallback: function (_data) { - // Set the "hasData" flag - this.self.hasData = true; - - // Call the row callback with the new data -- the row callback then - // generates the row DOM nodes that will be inserted into the grid - if (this.self._rowCallback) - { - // Remove everything from the current row - this.entry.row.clear(); - - // If there's no data, stop - if(typeof _data == "undefined" || _data == null) - { - this.self._destroyCallback.call( - this, - this.entry.row - ); - return; - } - - // Fill the row DOM Node with data - this.self._rowCallback.call( - this.self._context, - _data, - this.entry.row, - this.entry.idx, - this.entry - ); - - // Attach the "subgrid" tag to the row, if the depth of this - // controller is larger than zero - var tr = this.entry.row.getDOMNode(); - var d = this.self.getDepth(); - if (d > 0) - { - jQuery(tr).addClass("subentry"); - jQuery("td:first",tr).children("div").last().addClass("level_" + d + " indentation"); - - if(this.entry.idx == 0) - { - // Set the CSS for the level - required so columns line up - var indent = jQuery("").appendTo('body'); - egw.css(".subentry td div.innerContainer.level_"+d, - "margin-right:" + (parseInt(indent.css("margin-right")) * d) + "px" - ); - indent.remove(); - } - } - - var links = null; - - // Look for a flag in the row to avoid actions. Use for sums or extra header rows. - if(!_data.no_actions) - { - // Get the action links if the links callback is set - if (this.self._linkCallback) - { - links = this.self._linkCallback.call( - this.self._context, - _data, - this.entry.idx, - this.entry.uid - ); - } - - // Register the row in the selection manager - this.self._selectionMgr.registerRow(this.entry.uid, this.entry.idx, - tr, links); - } - else - { - // Remember that - this.entry.no_actions = true; - } - - // Invalidate the current row entry - this.entry.row.invalidate(); - } - }, - - /** - * - */ - _destroyCallback: function (_row) { - - // Unregister the row from the selection manager, if not selected - // If it is selected, leave it there - allows selecting rows and scrolling - var selection = this.self._selectionMgr._getRegisteredRowsEntry(this.entry.uid); - if (this.entry.row && selection && !egwBitIsSet(selection.state, EGW_AO_STATE_SELECTED)) - { - var tr = this.entry.row.getDOMNode(); - this.self._selectionMgr._updateState(this.entry.uid, EGW_AO_STATE_NORMAL) - this.self._selectionMgr.unregisterRow(this.entry.uid, tr); - } - - // There is no further row connected to the entry - this.entry.row = null; - - // Unregister the data callback - this.self._dataProvider.dataUnregisterUID(this.entry.uid, - this.self._dataCallback, this); - }, - - /** - * Returns an array containing "_count" index mapping entries starting from - * the index given in "_start". - */ - _getIndexMapping: function (_start, _count) { - var result = []; - - for (var i = _start; i < _start + _count; i++) - { - result.push(this._getIndexEntry(i)); - } - - return result; - }, - - /** - * Updates the grid according to the new order. The function simply does the - * following: It iterates along the new order (given in _order) and the old - * order given in _idxMap. Iteration variables used are - * a) i -- points to the current entry in _order - * b) idx -- points to the current grid row that will be effected by - * this operation. - * c) mapIdx -- points to the current entry in _indexMap - * The following cases may occur: - * a) The current entry in the old order has no uid or no row -- in that - * case the row at the current position is simply updated, - * the old pointer will be incremented. - * b) The two uids differ -- insert a new row with the new uid, do not - * increment the old pointer. - * c) The two uids are the same -- increment the old pointer. - * In a last step all rows that are left in the old order are deleted. All - * newly created index entries are returned. This function does not update - * the internal mapping in _idxMap. - */ - _updateOrder: function (_start, _count, _idxMap, _order) { - // The result contains the newly created index map entries which have to - // be merged with the result - var result = []; - - // Iterate over the new order - var mapIdx = 0; - var idx = _start; - for (var i = 0; i < _order.length; i++, idx++) - { - var current = _idxMap[mapIdx]; - - if (!current.row || !current.uid) - { - // If there is no row yet at the current position or the uid - // of that entry is unknown, simply update the entry. - current.uid = _order[i]; - current.idx = idx; - - // Only update the row, if it is displayed (e.g. has a "loading" - // row displayed) -- this is needed for prefetching - if (current.row) - { - this._insertDataRow(current, true); - } - - mapIdx++; - } - else if (current.uid !== _order[i]) - { - // Insert a new row at the new position - var entry = { - "idx": idx, - "uid": _order[i], - "row": null - }; - - this._insertDataRow(entry, true); - - // Remember the new entry - result.push(entry); - } - else - { - // Do nothing, the uids do not differ, just update the index of - // the element - current.idx = idx; - mapIdx++; - } - } - - // Delete as many rows as we have left, invalidate the corresponding - // index entry - for (var i = mapIdx; i < _idxMap.length; i++) - { - if(typeof _idxMap[i] != 'undefined') - { - _idxMap[i].uid = null; - } - } - - return result; - }, - - _mergeResult: function (_newEntries, _invalidStartIdx, _diff, _total) { - - if (_newEntries.length > 0 || _diff > 0) - { - // Create a new index map - var newMap = {}; - - // Insert all new entries into the new index map - for (var i = 0; i < _newEntries.length; i++) - { - newMap[_newEntries[i].idx] = _newEntries[i]; - } - - // Merge the old map with all old entries - for (var key in this._indexMap) - { - // Get the corresponding index entry - var entry = this._indexMap[key]; - - // Calculate the new index -- if rows were deleted, we'll - // have to adjust the index - var newIdx = entry.idx >= _invalidStartIdx - ? entry.idx - _diff : entry.idx; - if (newIdx >= 0 && newIdx < _total - && typeof newMap[newIdx] === "undefined") - { - entry.idx = newIdx; - newMap[newIdx] = entry; - } - else - { - // Make sure the old entry gets invalidated - entry.idx = null; - entry.row = null; - } - } - - // Make the new index map the current index map - this._indexMap = newMap; - this._selectionMgr.setIndexMap(newMap); - } - - }, - - _fetchCallback: function (_response) { - // Remove answered request from queue - var request = null; - for(var i = 0; i < this.self._request_queue.length; i++) - { - if(this.self._request_queue[i].context == this) - { - request = this.self._request_queue[i]; - this.self._request_queue.splice(i,1); - break; - } - } - - this.self._lastModification = _response.lastModification; - - // Do nothing if _response.order evaluates to false - if (!_response.order) - { - return; - } - - // Make sure _response.order.length is not longer than the requested - // count, if a specific count was requested - var order = this.count != 0 ? _response.order.splice(0, this.count) : _response.order; - - // Remove from queue, or it will not be fetched again - if(_response.total < this.count) - { - // Less rows than we expected - // Clear the queue, or the remnants will never be loaded again - this.self._queue = {}; - } - else - { - for(var i = this.start; i < this.start + order.length; i++) - delete this.self._queue[i]; - } - - // Get the current index map for the updated region - var idxMap = this.self._getIndexMapping(this.start, order.length); - - // Update the grid using the new order. The _updateOrder function does - // not update the internal mapping while inserting and deleting rows, as - // this would move us to another asymptotic runtime level. - var res = this.self._updateOrder(this.start, this.count, idxMap, order); - - // Merge the new indices, update all indices with rows that were not - // affected and invalidate all indices if there were changes - this.self._mergeResult(res, this.start + order.length, - idxMap.length - order.length, _response.total); - - if(_response.total == 0) - { - this.self._emptyRow(true); - } - else - { - var row = jQuery(".egwGridView_empty",this.self._grid.innerTbody).remove(); - this.self._selectionMgr.unregisterRow("",0,row.get(0)); - } - - // Now it's OK to invalidate, if it wasn't before - this.self._grid.doInvalidate = true; - - // Update the total element count in the grid - this.self._grid.setTotalCount(_response.total); - this.self._selectionMgr.setTotalCount(_response.total); - - // Schedule an invalidate, in case total is the same - this.self._grid.invalidate(); - - // Check if requests are waiting - this.self._fetchQueuedRequest(); - }, - - /** - * Insert an empty / placeholder row when there is no data to display - */ - _emptyRow: function(_noRows) - { - var noRows = !_noRows ? false : true; - jQuery(".egwGridView_empty",this._grid.innerTbody).remove(); - if(typeof this._grid._rowProvider != "undefined" && this._grid._rowProvider.getPrototype("empty")) - { - var placeholder = this._grid._rowProvider.getPrototype("empty"); - if(jQuery("td",placeholder).length == 1) - { - jQuery("td",placeholder).css("width",this._grid.outerCell.width() + "px") - } - placeholder.appendTo(this._grid.innerTbody); - - // Register placeholder action only if no rows - if (noRows) - { - // Get the action links if the links callback is set - var links = null; - if (this._linkCallback) - { - links = this._linkCallback.call( - this._context, - {}, - 0, - "" - ); - } - this._selectionMgr.registerRow("",0,placeholder.get(0), links); - } - } - }, - - /** - * Callback function used by the selection manager to translate the selected - * range to uids. - */ - _selectionFetchRange: function (_range, _callback, _context) { - this._dataProvider.dataFetch( - { "start": _range.top, "num_rows": _range.bottom - _range.top + 1, - "no_data": true }, - function (_response) { - _callback.call(_context, _response.order); - }, - _context - ); - }, - - /** - * Tells the grid to make the given index visible. - */ - _makeIndexVisible: function (_idx) { - this._grid.makeIndexVisible(_idx); - } - -});}).call(this); - +var et2_dataview_controller = /** @class */ (function () { + /** + * Constructor of the et2_dataview_controller, connects to the grid + * callback. + * + * @param _grid is the grid the controller should controll. + * @param _rowCallback is the callback function that gets called when a row + * is requested. + * @param _linkCallback is the callback function that gets called for + * requesting action links for a row. The row data, the index of the row and + * the uid are passed as parameters to the function. + * uid is passed to the function. + * @param _actionObjectManager is the object that manages the action + * objects. + */ + function et2_dataview_controller(_parentController, _grid) { + this._indexMap = {}; + // Copy the given arguments + this._parentController = _parentController; + this._grid = _grid; + // Initialize list of child controllers + this._children = []; + // Initialize the "index map" which contains all currently displayed + // containers hashed by the "index" + this._indexMap = {}; + // Timer used for queing fetch requests + this._queueTimer = null; + // Array which contains all currently queued row indices in the form of + // an associative array + this._queue = {}; + // Current concurrent requests we have + this._request_queue = []; + // Register the dataFetch callback + this._grid.setDataCallback(this._gridCallback, this); + // Record the child + if (this._parentController != null) { + this._parentController._children.push(this); + } + } + et2_dataview_controller.prototype.destroy = function () { + // Destroy the selection manager + this._selectionMgr.destroy(); + // Clear the selection timeout + this._clearTimer(); + // Remove the child from the child list + if (this._parentController != null) { + var idx = this._parentController._children.indexOf(this); + if (idx >= 0) { + // This element is no longer parent of the child + this._parentController._children.splice(idx, 1); + this._parentController = null; + } + } + }; + /** + * @param value is an object implementing the et2_IDataProvider + * interface + */ + et2_dataview_controller.prototype.setDataProvider = function (value) { + this._dataProvider = value; + }; + et2_dataview_controller.prototype.setRowCallback = function (value) { + this._rowCallback = value; + }; + et2_dataview_controller.prototype.setLinkCallback = function (value) { + this._linkCallback = value; + }; + /** + * @param value is the context in which the _rowCallback and the + * _linkCallback are called. + */ + et2_dataview_controller.prototype.setContext = function (value) { + this._context = value; + }; + et2_dataview_controller.prototype.setActionObjectManager = function (_actionObjectManager) { + if (this._selectionMgr) { + this._selectionMgr.destroy(); + } + // Create the selection manager + this._selectionMgr = new et2_dataview_controller_selection_1.et2_dataview_selectionManager(this._parentController ? this._parentController._selectionMgr : null, this._indexMap, _actionObjectManager, this._selectionFetchRange, this._makeIndexVisible, this); + }; + /** + * The update function queries the server for changes in the currently + * managed index range -- those changes are then merged into the current + * view without a complete rebuild of every row. + * + * @param {boolean} clear Skip the fancy stuff, dump everything and start again. + * Completely clears the grid and selection. + */ + et2_dataview_controller.prototype.update = function (clear) { + // --------- + // TODO: Actually stuff here should be done if the server responds that + // there at all were some changes (needs implementation of "refresh") + // Tell the grid not to try and update itself while we do this + this._grid.doInvalidate = false; + if (clear) { + // Scroll to top + this._grid.makeIndexVisible(0); + this._grid.clear(); + // Free selection manager + this._selectionMgr.clear(); + // Clear object manager + this._objectManager.clear(); + // Clear the map + this._indexMap = {}; + // Update selection manager, it uses this by reference + this._selectionMgr.setIndexMap(this._indexMap); + // Clear the queue + this._queue = {}; + // Invalidate the change detection, re-fetches any known rows + this._lastModification = 0; + } + // Remove all rows which are outside the view range + this._grid.cleanup(); + // Get the currently visible range from the grid + var range = this._grid.getIndexRange(); + // Force range.top and range.bottom to contain an integer + if (range.top === false) { + range.top = range.bottom = 0; + } + this._request_queue = []; + // Require that range from the server + this._queueFetch(et2_bounds(range.top, clear ? 0 : range.bottom + 1), 0, true); + }; + /** + * Rebuilds the complete grid. + */ + et2_dataview_controller.prototype.reset = function () { + // Throw away all internal mappings and reset the timestamp + this._indexMap = {}; + // Update selection manager, it uses this by reference + this._selectionMgr.setIndexMap(this._indexMap); + // Clear the grid + this._grid.clear(); + // Clear the row queue + this._queue = {}; + // Reset the request queue + this._request_queue = []; + // Update the data + this.update(); + }; + /** + * Loads the initial order. Do not call multiple times. + */ + et2_dataview_controller.prototype.loadInitialOrder = function (order) { + for (var i = 0; i < order.length; i++) { + this._getIndexEntry(i).uid = order[i]; + } + }; + /** + * Load initial data + * + * @param {string} uid_key Name of the unique row identifier field + * @param {Object} data Key / Value mapping of initial data. + */ + et2_dataview_controller.prototype.loadInitialData = function (uid_prefix, uid_key, data) { + var idx = 0; + for (var key in data) { + // Skip any extra keys + if (typeof data[key] != "object" || data[key] == null || typeof data[key][uid_key] == "undefined") + continue; + // Add to row / uid map + var entry = this._getIndexEntry(idx++); + entry.uid = data[key][uid_key] + ""; + if (entry.uid.indexOf(uid_prefix) < 0) { + entry.uid = uid_prefix + "::" + entry.uid; + } + // Add to data cache so grid will find it + egw.dataStoreUID(entry.uid, data[key]); + // Don't try to insert the rows, grid will do that automatically + } + if (idx == 0) { + // No rows, start with an empty + this._selectionMgr.clear(); + this._emptyRow(this._grid._total == 0); + } + }; + /** + * Returns the depth of the controller instance. + */ + et2_dataview_controller.prototype.getDepth = function () { + if (this._parentController) { + return this._parentController.getDepth() + 1; + } + return 0; + }; + /** + * Set the data cache prefix + * The default is to use appname, but if you need to set it explicitly to + * something else to avoid conflicts. Use the same prefix everywhere for + * each type of data. eg. infolog for infolog entries, even if accessed via addressbook + */ + et2_dataview_controller.prototype.setPrefix = function (prefix) { + this.dataStorePrefix = prefix; + }; + /** + * Returns the row information of the passed node, or null if not available + * + * @param {DOMNode} node + * @return {string|false} UID, or false if not found + */ + et2_dataview_controller.prototype.getRowByNode = function (node) { + // Whatever the node, find a TR + var row_node = jQuery(node).closest('tr'); + var row = null; + // Check index map - simple case + var indexed = this._getIndexEntry(row_node.index()); + if (indexed && indexed.row && indexed.row.getDOMNode() == row_node[0]) { + row = indexed; + } + else { + // Check whole index map + for (var index in this._indexMap) { + indexed = this._indexMap[index]; + if (indexed && indexed.row && indexed.row.getDOMNode() == row_node[0]) { + row = indexed; + break; + } + } + } + // Check children + for (var i = 0; !row && i < this._children.length; i++) { + var child_row = this._children[i].getRowByNode(node); + if (child_row !== false) + row = child_row; + } + if (row && !row.controller) { + row.controller = this; + } + return row; + }; + /* -- PRIVATE FUNCTIONS -- */ + et2_dataview_controller.prototype._getIndexEntry = function (_idx) { + // Create an entry in the index map if it does not exist yet + if (typeof this._indexMap[_idx] === "undefined") { + this._indexMap[_idx] = { + "row": null, + "uid": null + }; + } + // Always update the index of the entries before returning them. This is + // neccessary, as when we remove the uid from an entry without row, its + // index does not get updated any further + this._indexMap[_idx]["idx"] = _idx; + return this._indexMap[_idx]; + }; + /** + * Inserts a new data row into the grid. index and uid are derived from the + * given management entry. If the data for the given uid does not exist yet, + * a "loading" placeholder will be shown instead. The function will do + * nothing if there already is a row associated to the entry. This function + * will not re-insert a row if the entry already had a row. + * + * @param _entry is the management entry for the index the row will be + * displayed at. + * @param _update specifies whether the row should be updated if _entry.row + * already exists. + * @return true, if all data for the row has been available, false + * otherwise. + */ + et2_dataview_controller.prototype._insertDataRow = function (_entry, _update) { + // Abort if the entry already has a row but the _insert flag is not set + if (_entry.row && !_update) { + return true; + } + // Context used for the callback functions + var ctx = { "self": this, "entry": _entry }; + // Create a new row instance, if it does not exist yet + var createdRow = false; + if (!_entry.row) { + createdRow = true; + _entry.row = this._createRow(ctx); + _entry.row.setDestroyCallback(this._destroyCallback, ctx); + } + // Load the row data if we have a uid for the entry + this.hasData = false; // Gets updated by the _dataCallback + if (_entry.uid) { + // Register the callback / immediately load the data + this._dataProvider.dataRegisterUID(_entry.uid, this._dataCallback, ctx); + } + // Display the loading "row prototype" if we don't have data for the row + if (!this.hasData) { + // Get the average height, the "-5" derives from the td padding + var avg = Math.round(this._grid.getAverageHeight() - 5) + "px"; + var prototype = this._grid.getRowProvider().getPrototype("loading"); + jQuery("div", prototype).css("height", avg); + var node = _entry.row.getJNode(); + node.empty(); + node.append(prototype.children()); + } + // Insert the row into the table -- the same row must never be inserted + // twice into the grid, so this function only executes the following + // code only if it is a newly created row. + if (createdRow && _entry.row) { + this._grid.insertRow(_entry.idx, _entry.row); + } + return this.hasData; + }; + /** + * Create a new row. + * + * @param {type} ctx + * @returns {et2_dataview_container} + */ + et2_dataview_controller.prototype._createRow = function (ctx) { + return new et2_dataview_view_row_1.et2_dataview_row(this._grid); + }; + /** + * Function which gets called by the grid when data is requested. + * + * @param _idxStart is the index of the first row for which data is + * requested. + * @param _idxEnd is the index of the last requested row. + */ + et2_dataview_controller.prototype._gridCallback = function (_idxStart, _idxEnd) { + var needsData = false; + // Iterate over all elements the dataview requested and create a row + // which indicates that we are currently loading data + for (var i = _idxStart; i <= _idxEnd; i++) { + var entry = this._getIndexEntry(i); + // Insert the row for the entry -- do not update rows which are + // already existing, as we do not have new data for those. + if (!this._insertDataRow(entry, false) && needsData === false) { + needsData = i; + } + } + // Queue fetching that data range + if (needsData !== false) { + this._queueFetch(et2_bounds(needsData, _idxEnd + 1), needsData == _idxStart ? 0 : needsData > _idxStart ? 1 : -1, false); + } + }; + /** + * The _queueFetch function is used to queue a fetch request. + * TODO: Refresh is currently not used + */ + et2_dataview_controller.prototype._queueFetch = function (_range, _direction, _isUpdate) { + // Force immediate to be false + _isUpdate = _isUpdate ? _isUpdate : false; + // Push the requests onto the request queue + var start = Math.max(0, _range.top); + var end = Math.min(this._grid.getTotalCount(), _range.bottom); + for (var i = start; i < end; i++) { + if (typeof this._queue[i] === "undefined") { + this._queue[i] = _direction; // Stage 1 - queue for after current, -1 -- queue for before current + } + } + // Start the queue timer, if this has not already been done + if (this._queueTimer === null && !_isUpdate) { + var self = this; + egw.debug('log', 'Dataview queue: ', _range); + this._queueTimer = window.setTimeout(function () { + self._flushQueue(false); + }, ET2_DATAVIEW_FETCH_TIMEOUT); + } + if (_isUpdate) { + this._flushQueue(true); + } + }; + /** + * Flushes the queue. + */ + et2_dataview_controller.prototype._flushQueue = function (_isUpdate) { + // Clear any still existing timer + this._clearTimer(); + // Mark all elements in a radius of ET2_DATAVIEW_STEPSIZE + var marked = {}; + var r = _isUpdate ? 0 : Math.floor(ET2_DATAVIEW_STEPSIZE / 2); + var total = this._grid.getTotalCount(); + for (var key in this._queue) { + if (this._queue[key] > 1) + continue; + key = parseInt(key); + var b = Math.max(0, key - r + (r * this._queue[key])); + var t = Math.min(key + r + (r * this._queue[key]), total - 1); + var c = 0; + for (var i = b; i <= t && c < ET2_DATAVIEW_STEPSIZE; i++) { + if (typeof this._queue[i] == "undefined" + || this._queue[i] <= 1) { + this._queue[i] = 2; // Stage 2 -- pending or available + marked[i] = true; + c++; + } + } + } + // Create a list with start indices and counts + var fetchList = []; + var entry = null; + var last = 0; + // Get the int keys and sort the array numeric + var arr = et2_arrayIntKeys(marked).sort(function (a, b) { return a > b ? 1 : (a == b ? 0 : -1); }); + for (var i = 0; i < arr.length; i++) { + if (i == 0 || arr[i] - last > 1) { + if (entry) { + fetchList.push(entry); + } + entry = { + "start": arr[i], + "count": 1 + }; + } + else { + entry.count++; + } + last = arr[i]; + } + if (entry) { + fetchList.push(entry); + } + // Special case: If there are no entries in the fetch list and this is + // an update, create an dummy entry, so that we'll get the current count + if (fetchList.length === 0 && _isUpdate) { + fetchList.push({ + "start": 0, "count": 0 + }); + // Disable grid invalidate, or it might request again before we're done + this._grid.doInvalidate = false; + } + egw.debug("log", "Dataview flush", fetchList); + // Execute all queries + for (var i = 0; i < fetchList.length; i++) { + // Build the query + var query = { + "start": fetchList[i].start, + "num_rows": fetchList[i].count, + "refresh": false + }; + // Context used in the callback function + var ctx = { + "self": this, + "start": query.start, + "count": query.num_rows, + "lastModification": this._lastModification + }; + if (this.dataStorePrefix) { + ctx.prefix = this.dataStorePrefix; + } + this._queueRequest(query, ctx); + } + }; + /** + * Queue a request for data + * @param {Object} query + * @param {Object} ctx + */ + et2_dataview_controller.prototype._queueRequest = function (query, ctx) { + this._request_queue.push({ + query: query, + context: ctx, + // Start pending, set to 1 when request sent + status: 0 + }); + this._fetchQueuedRequest(); + }; + /** + * Fetch data for a queued request, subject to rate limit + */ + et2_dataview_controller.prototype._fetchQueuedRequest = function () { + // Check to see if there's room + var count = 0; + for (var i = 0; i < this._request_queue.length; i++) { + if (this._request_queue[i].status > 0) + count++; + } + // Too many requests, will try again after response is received + if (count >= et2_dataview_controller.CONCURRENT_REQUESTS || this._request_queue.length === 0) { + return; + } + // Keep at least 1 previous pending + var keep = 1; + // The most recent is the one the user's most interested in + var request = null; + for (var i = this._request_queue.length - 1; i >= 0; i--) { + // Only interested in pending requests (status 0) + if (this._request_queue[i].status != 0) { + continue; + } + if (request == null) { + request = this._request_queue[i]; + } + else if (keep > 0) { + keep--; + } + else if (keep <= 0) { + // Cancel pending, they've probably scrolled past. + this._request_queue.splice(i, 1); + } + } + if (request == null) + return; + // Request being sent + request.status = 1; + // Call the callback + this._dataProvider.dataFetch(request.query, this._fetchCallback, request.context); + }; + et2_dataview_controller.prototype._clearTimer = function () { + // Reset the queue timer upon destruction + if (this._queueTimer) { + window.clearTimeout(this._queueTimer); + this._queueTimer = null; + } + }; + /** + * Called by the data source when the data changes + * + * @param _data Object|null New data, or null. Null will remove the row. + */ + et2_dataview_controller.prototype._dataCallback = function (_data) { + // Set the "hasData" flag + this.self.hasData = true; + // Call the row callback with the new data -- the row callback then + // generates the row DOM nodes that will be inserted into the grid + if (this.self._rowCallback) { + // Remove everything from the current row + this.entry.row.clear(); + // If there's no data, stop + if (typeof _data == "undefined" || _data == null) { + this.self._destroyCallback.call(this, this.entry.row); + return; + } + // Fill the row DOM Node with data + this.self._rowCallback.call(this.self._context, _data, this.entry.row, this.entry.idx, this.entry); + // Attach the "subgrid" tag to the row, if the depth of this + // controller is larger than zero + var tr = this.entry.row.getDOMNode(); + var d = this.self.getDepth(); + if (d > 0) { + jQuery(tr).addClass("subentry"); + jQuery("td:first", tr).children("div").last().addClass("level_" + d + " indentation"); + if (this.entry.idx == 0) { + // Set the CSS for the level - required so columns line up + var indent = jQuery("").appendTo('body'); + egw.css(".subentry td div.innerContainer.level_" + d, "margin-right:" + (parseInt(indent.css("margin-right")) * d) + "px"); + indent.remove(); + } + } + var links = null; + // Look for a flag in the row to avoid actions. Use for sums or extra header rows. + if (!_data.no_actions) { + // Get the action links if the links callback is set + if (this.self._linkCallback) { + links = this.self._linkCallback.call(this.self._context, _data, this.entry.idx, this.entry.uid); + } + // Register the row in the selection manager + this.self._selectionMgr.registerRow(this.entry.uid, this.entry.idx, tr, links); + } + else { + // Remember that + this.entry.no_actions = true; + } + // Invalidate the current row entry + this.entry.row.invalidate(); + } + }; + /** + * + */ + et2_dataview_controller.prototype._destroyCallback = function (_row) { + // Unregister the row from the selection manager, if not selected + // If it is selected, leave it there - allows selecting rows and scrolling + var selection = this.self._selectionMgr._getRegisteredRowsEntry(this.entry.uid); + if (this.entry.row && selection && !egwBitIsSet(selection.state, EGW_AO_STATE_SELECTED)) { + var tr = this.entry.row.getDOMNode(); + this.self._selectionMgr._updateState(this.entry.uid, EGW_AO_STATE_NORMAL); + this.self._selectionMgr.unregisterRow(this.entry.uid, tr); + } + // There is no further row connected to the entry + this.entry.row = null; + // Unregister the data callback + this.self._dataProvider.dataUnregisterUID(this.entry.uid, this.self._dataCallback, this); + }; + /** + * Returns an array containing "_count" index mapping entries starting from + * the index given in "_start". + */ + et2_dataview_controller.prototype._getIndexMapping = function (_start, _count) { + var result = []; + for (var i = _start; i < _start + _count; i++) { + result.push(this._getIndexEntry(i)); + } + return result; + }; + /** + * Updates the grid according to the new order. The function simply does the + * following: It iterates along the new order (given in _order) and the old + * order given in _idxMap. Iteration variables used are + * a) i -- points to the current entry in _order + * b) idx -- points to the current grid row that will be effected by + * this operation. + * c) mapIdx -- points to the current entry in _indexMap + * The following cases may occur: + * a) The current entry in the old order has no uid or no row -- in that + * case the row at the current position is simply updated, + * the old pointer will be incremented. + * b) The two uids differ -- insert a new row with the new uid, do not + * increment the old pointer. + * c) The two uids are the same -- increment the old pointer. + * In a last step all rows that are left in the old order are deleted. All + * newly created index entries are returned. This function does not update + * the internal mapping in _idxMap. + */ + et2_dataview_controller.prototype._updateOrder = function (_start, _count, _idxMap, _order) { + // The result contains the newly created index map entries which have to + // be merged with the result + var result = []; + // Iterate over the new order + var mapIdx = 0; + var idx = _start; + for (var i = 0; i < _order.length; i++, idx++) { + var current = _idxMap[mapIdx]; + if (!current.row || !current.uid) { + // If there is no row yet at the current position or the uid + // of that entry is unknown, simply update the entry. + current.uid = _order[i]; + current.idx = idx; + // Only update the row, if it is displayed (e.g. has a "loading" + // row displayed) -- this is needed for prefetching + if (current.row) { + this._insertDataRow(current, true); + } + mapIdx++; + } + else if (current.uid !== _order[i]) { + // Insert a new row at the new position + var entry = { + "idx": idx, + "uid": _order[i], + "row": null + }; + this._insertDataRow(entry, true); + // Remember the new entry + result.push(entry); + } + else { + // Do nothing, the uids do not differ, just update the index of + // the element + current.idx = idx; + mapIdx++; + } + } + // Delete as many rows as we have left, invalidate the corresponding + // index entry + for (var i = mapIdx; i < _idxMap.length; i++) { + if (typeof _idxMap[i] != 'undefined') { + _idxMap[i].uid = null; + } + } + return result; + }; + et2_dataview_controller.prototype._mergeResult = function (_newEntries, _invalidStartIdx, _diff, _total) { + if (_newEntries.length > 0 || _diff > 0) { + // Create a new index map + var newMap = {}; + // Insert all new entries into the new index map + for (var i = 0; i < _newEntries.length; i++) { + newMap[_newEntries[i].idx] = _newEntries[i]; + } + // Merge the old map with all old entries + for (var key in this._indexMap) { + // Get the corresponding index entry + var entry = this._indexMap[key]; + // Calculate the new index -- if rows were deleted, we'll + // have to adjust the index + var newIdx = entry.idx >= _invalidStartIdx + ? entry.idx - _diff : entry.idx; + if (newIdx >= 0 && newIdx < _total + && typeof newMap[newIdx] === "undefined") { + entry.idx = newIdx; + newMap[newIdx] = entry; + } + else { + // Make sure the old entry gets invalidated + entry.idx = null; + entry.row = null; + } + } + // Make the new index map the current index map + this._indexMap = newMap; + this._selectionMgr.setIndexMap(newMap); + } + }; + et2_dataview_controller.prototype._fetchCallback = function (_response) { + // Remove answered request from queue + var request = null; + for (var i = 0; i < this.self._request_queue.length; i++) { + if (this.self._request_queue[i].context == this) { + request = this.self._request_queue[i]; + this.self._request_queue.splice(i, 1); + break; + } + } + this.self._lastModification = _response.lastModification; + // Do nothing if _response.order evaluates to false + if (!_response.order) { + return; + } + // Make sure _response.order.length is not longer than the requested + // count, if a specific count was requested + var order = this.count != 0 ? _response.order.splice(0, this.count) : _response.order; + // Remove from queue, or it will not be fetched again + if (_response.total < this.count) { + // Less rows than we expected + // Clear the queue, or the remnants will never be loaded again + this.self._queue = {}; + } + else { + for (var i = this.start; i < this.start + order.length; i++) + delete this.self._queue[i]; + } + // Get the current index map for the updated region + var idxMap = this.self._getIndexMapping(this.start, order.length); + // Update the grid using the new order. The _updateOrder function does + // not update the internal mapping while inserting and deleting rows, as + // this would move us to another asymptotic runtime level. + var res = this.self._updateOrder(this.start, this.count, idxMap, order); + // Merge the new indices, update all indices with rows that were not + // affected and invalidate all indices if there were changes + this.self._mergeResult(res, this.start + order.length, idxMap.length - order.length, _response.total); + if (_response.total == 0) { + this.self._emptyRow(true); + } + else { + var row = jQuery(".egwGridView_empty", this.self._grid.innerTbody).remove(); + this.self._selectionMgr.unregisterRow("", 0, row.get(0)); + } + // Now it's OK to invalidate, if it wasn't before + this.self._grid.doInvalidate = true; + // Update the total element count in the grid + this.self._grid.setTotalCount(_response.total); + this.self._selectionMgr.setTotalCount(_response.total); + // Schedule an invalidate, in case total is the same + this.self._grid.invalidate(); + // Check if requests are waiting + this.self._fetchQueuedRequest(); + }; + /** + * Insert an empty / placeholder row when there is no data to display + */ + et2_dataview_controller.prototype._emptyRow = function (_noRows) { + var noRows = !_noRows ? false : true; + jQuery(".egwGridView_empty", this._grid.innerTbody).remove(); + if (typeof this._grid._rowProvider != "undefined" && this._grid._rowProvider.getPrototype("empty")) { + var placeholder = this._grid._rowProvider.getPrototype("empty"); + if (jQuery("td", placeholder).length == 1) { + jQuery("td", placeholder).css("width", this._grid.outerCell.width() + "px"); + } + placeholder.appendTo(this._grid.innerTbody); + // Register placeholder action only if no rows + if (noRows) { + // Get the action links if the links callback is set + var links = null; + if (this._linkCallback) { + links = this._linkCallback.call(this._context, {}, 0, ""); + } + this._selectionMgr.registerRow("", 0, placeholder.get(0), links); + } + } + }; + /** + * Callback function used by the selection manager to translate the selected + * range to uids. + */ + et2_dataview_controller.prototype._selectionFetchRange = function (_range, _callback, _context) { + this._dataProvider.dataFetch({ "start": _range.top, "num_rows": _range.bottom - _range.top + 1, + "no_data": true }, function (_response) { + _callback.call(_context, _response.order); + }, _context); + }; + /** + * Tells the grid to make the given index visible. + */ + et2_dataview_controller.prototype._makeIndexVisible = function (_idx) { + this._grid.makeIndexVisible(_idx); + }; + // Maximum concurrent data requests. Additional ones are held in the queue. + et2_dataview_controller.CONCURRENT_REQUESTS = 5; + return et2_dataview_controller; +}()); +exports.et2_dataview_controller = et2_dataview_controller; +//# sourceMappingURL=et2_dataview_controller.js.map \ No newline at end of file diff --git a/api/js/etemplate/et2_dataview_controller.ts b/api/js/etemplate/et2_dataview_controller.ts new file mode 100644 index 0000000000..7de0a670c1 --- /dev/null +++ b/api/js/etemplate/et2_dataview_controller.ts @@ -0,0 +1,1128 @@ +/** + * EGroupware eTemplate2 + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package etemplate + * @subpackage dataview + * @link http://www.egroupware.org + * @author Andreas Stöckel + * @copyright Stylite 2011-2012 + +/*egw:uses + et2_core_common; + et2_core_inheritance; + + et2_dataview_interfaces; + et2_dataview_controller_selection; + et2_dataview_view_row; + et2_dataview_view_tile; + + egw_action.egw_action; +*/ + +import {et2_IDataProvider} from "./et2_dataview_interfaces"; +import {et2_dataview_selectionManager} from "./et2_dataview_controller_selection"; +import {et2_dataview_row} from "./et2_dataview_view_row"; + +/** + * The fetch timeout specifies the time during which the controller tries to + * consolidate requests for rows. + */ +var ET2_DATAVIEW_FETCH_TIMEOUT = 50; + +var ET2_DATAVIEW_STEPSIZE = 50; + +/** + * The et2_dataview_controller class is the intermediate layer between a grid + * instance and the corresponding data source. It manages updating the grid, + * as well as inserting and deleting rows. + */ +export class et2_dataview_controller +{ + // Maximum concurrent data requests. Additional ones are held in the queue. + public static readonly CONCURRENT_REQUESTS = 5; + + private _parentController: any; + private _grid: any; + private dataStorePrefix: any; + private _dataProvider: any; + private _rowCallback: any; + private _linkCallback: any; + private _context: any; + + private _children: any[]; + private _indexMap: any = {}; + + private _queueTimer: number; + private _lastModification: number; + private _queue: {}; + private _request_queue: any[]; + private _selectionMgr: et2_dataview_selectionManager; + + private _objectManager: any; + + /** + * Constructor of the et2_dataview_controller, connects to the grid + * callback. + * + * @param _grid is the grid the controller should controll. + * @param _rowCallback is the callback function that gets called when a row + * is requested. + * @param _linkCallback is the callback function that gets called for + * requesting action links for a row. The row data, the index of the row and + * the uid are passed as parameters to the function. + * uid is passed to the function. + * @param _actionObjectManager is the object that manages the action + * objects. + */ + constructor (_parentController, _grid) + { + // Copy the given arguments + this._parentController = _parentController; + this._grid = _grid; + + // Initialize list of child controllers + this._children = []; + + // Initialize the "index map" which contains all currently displayed + // containers hashed by the "index" + this._indexMap = {}; + + // Timer used for queing fetch requests + this._queueTimer = null; + + // Array which contains all currently queued row indices in the form of + // an associative array + this._queue = {}; + + // Current concurrent requests we have + this._request_queue = []; + + // Register the dataFetch callback + this._grid.setDataCallback(this._gridCallback, this); + + + // Record the child + if(this._parentController != null) + { + this._parentController._children.push(this); + } + } + + destroy( ) + { + + // Destroy the selection manager + this._selectionMgr.destroy(); + + // Clear the selection timeout + this._clearTimer(); + + // Remove the child from the child list + if(this._parentController != null) + { + var idx = this._parentController._children.indexOf(this); + + if (idx >= 0) + { + // This element is no longer parent of the child + this._parentController._children.splice(idx, 1); + this._parentController = null; + } + } + } + + /** + * @param value is an object implementing the et2_IDataProvider + * interface + */ + setDataProvider(value: et2_IDataProvider) + { + this._dataProvider = value; + } + + setRowCallback(value: any) { + this._rowCallback = value; + } + + setLinkCallback(value: any) { + this._linkCallback = value; + } + + /** + * @param value is the context in which the _rowCallback and the + * _linkCallback are called. + */ + setContext(value: any) { + this._context = value; + } + + setActionObjectManager(_actionObjectManager: any) + { + if(this._selectionMgr) + { + this._selectionMgr.destroy(); + } + + // Create the selection manager + this._selectionMgr = new et2_dataview_selectionManager( + this._parentController ? this._parentController._selectionMgr : null, + this._indexMap, + _actionObjectManager, + this._selectionFetchRange, + this._makeIndexVisible, + this + ); + } + + /** + * The update function queries the server for changes in the currently + * managed index range -- those changes are then merged into the current + * view without a complete rebuild of every row. + * + * @param {boolean} clear Skip the fancy stuff, dump everything and start again. + * Completely clears the grid and selection. + */ + update( clear? : boolean) + { + + // --------- + + // TODO: Actually stuff here should be done if the server responds that + // there at all were some changes (needs implementation of "refresh") + + // Tell the grid not to try and update itself while we do this + this._grid.doInvalidate = false; + + if(clear) + { + // Scroll to top + this._grid.makeIndexVisible(0); + this._grid.clear(); + + // Free selection manager + this._selectionMgr.clear(); + + // Clear object manager + this._objectManager.clear(); + + // Clear the map + this._indexMap = {}; + // Update selection manager, it uses this by reference + this._selectionMgr.setIndexMap(this._indexMap); + + // Clear the queue + this._queue = {}; + + // Invalidate the change detection, re-fetches any known rows + this._lastModification = 0; + } + // Remove all rows which are outside the view range + this._grid.cleanup(); + + // Get the currently visible range from the grid + var range = this._grid.getIndexRange(); + + // Force range.top and range.bottom to contain an integer + if (range.top === false) + { + range.top = range.bottom = 0; + } + this._request_queue = []; + + // Require that range from the server + this._queueFetch(et2_bounds(range.top, clear ? 0 : range.bottom + 1), 0, true); + } + + /** + * Rebuilds the complete grid. + */ + reset( ) + { + // Throw away all internal mappings and reset the timestamp + this._indexMap = {}; + // Update selection manager, it uses this by reference + this._selectionMgr.setIndexMap(this._indexMap); + + // Clear the grid + this._grid.clear(); + + // Clear the row queue + this._queue = {}; + + // Reset the request queue + this._request_queue = []; + + // Update the data + this.update(); + } + + /** + * Loads the initial order. Do not call multiple times. + */ + loadInitialOrder( order) + { + for (var i = 0; i < order.length; i++) + { + this._getIndexEntry(i).uid = order[i]; + } + } + + /** + * Load initial data + * + * @param {string} uid_key Name of the unique row identifier field + * @param {Object} data Key / Value mapping of initial data. + */ + loadInitialData( uid_prefix, uid_key, data) + { + var idx = 0; + for(var key in data) + { + // Skip any extra keys + if(typeof data[key] != "object" || data[key] == null || typeof data[key][uid_key] == "undefined") continue; + + // Add to row / uid map + var entry = this._getIndexEntry(idx++); + entry.uid = data[key][uid_key]+""; + if(entry.uid.indexOf(uid_prefix) < 0) + { + entry.uid = uid_prefix + "::" + entry.uid; + } + + // Add to data cache so grid will find it + egw.dataStoreUID(entry.uid, data[key]) + + // Don't try to insert the rows, grid will do that automatically + } + if(idx == 0) + { + // No rows, start with an empty + this._selectionMgr.clear(); + this._emptyRow(this._grid._total == 0); + } + } + + /** + * Returns the depth of the controller instance. + */ + getDepth( ) + { + + if (this._parentController) + { + return this._parentController.getDepth() + 1; + } + + return 0; + } + + /** + * Set the data cache prefix + * The default is to use appname, but if you need to set it explicitly to + * something else to avoid conflicts. Use the same prefix everywhere for + * each type of data. eg. infolog for infolog entries, even if accessed via addressbook + */ + setPrefix( prefix) + { + this.dataStorePrefix = prefix; + } + + /** + * Returns the row information of the passed node, or null if not available + * + * @param {DOMNode} node + * @return {string|false} UID, or false if not found + */ + getRowByNode( node) + { + // Whatever the node, find a TR + var row_node = jQuery(node).closest('tr'); + var row = null; + + // Check index map - simple case + var indexed = this._getIndexEntry(row_node.index()); + if(indexed && indexed.row && indexed.row.getDOMNode() == row_node[0]) + { + row = indexed; + } + else + { + // Check whole index map + for(var index in this._indexMap) + { + indexed = this._indexMap[index]; + if( indexed && indexed.row && indexed.row.getDOMNode() == row_node[0]) + { + row = indexed; + break; + } + } + } + + // Check children + for(var i = 0; !row && i < this._children.length; i++) + { + var child_row = this._children[i].getRowByNode(node); + if(child_row !== false) row = child_row; + } + if(row && !row.controller) + { + row.controller = this; + } + return row; + } + + /* -- PRIVATE FUNCTIONS -- */ + + + _getIndexEntry( _idx) + { + // Create an entry in the index map if it does not exist yet + if (typeof this._indexMap[_idx] === "undefined") + { + this._indexMap[_idx] = { + "row": null, + "uid": null + }; + } + + // Always update the index of the entries before returning them. This is + // neccessary, as when we remove the uid from an entry without row, its + // index does not get updated any further + this._indexMap[_idx]["idx"] = _idx; + + return this._indexMap[_idx]; + } + + /** + * Inserts a new data row into the grid. index and uid are derived from the + * given management entry. If the data for the given uid does not exist yet, + * a "loading" placeholder will be shown instead. The function will do + * nothing if there already is a row associated to the entry. This function + * will not re-insert a row if the entry already had a row. + * + * @param _entry is the management entry for the index the row will be + * displayed at. + * @param _update specifies whether the row should be updated if _entry.row + * already exists. + * @return true, if all data for the row has been available, false + * otherwise. + */ + _insertDataRow( _entry, _update) + { + // Abort if the entry already has a row but the _insert flag is not set + if (_entry.row && !_update) + { + return true; + } + + // Context used for the callback functions + var ctx = {"self": this, "entry": _entry}; + + // Create a new row instance, if it does not exist yet + var createdRow = false; + if (!_entry.row) + { + createdRow = true; + _entry.row = this._createRow(ctx); + _entry.row.setDestroyCallback(this._destroyCallback, ctx); + } + + // Load the row data if we have a uid for the entry + this.hasData = false; // Gets updated by the _dataCallback + if (_entry.uid) + { + // Register the callback / immediately load the data + this._dataProvider.dataRegisterUID(_entry.uid, this._dataCallback, + ctx); + } + + // Display the loading "row prototype" if we don't have data for the row + if (!this.hasData) + { + // Get the average height, the "-5" derives from the td padding + var avg = Math.round(this._grid.getAverageHeight() - 5) + "px"; + var prototype = this._grid.getRowProvider().getPrototype("loading"); + jQuery("div", prototype).css("height", avg); + var node = _entry.row.getJNode(); + node.empty(); + node.append(prototype.children()); + } + + // Insert the row into the table -- the same row must never be inserted + // twice into the grid, so this function only executes the following + // code only if it is a newly created row. + if (createdRow && _entry.row) + { + this._grid.insertRow(_entry.idx, _entry.row); + } + + return this.hasData; + } + + + /** + * Create a new row. + * + * @param {type} ctx + * @returns {et2_dataview_container} + */ + _createRow( ctx) + { + return new et2_dataview_row(this._grid); + } + + /** + * Function which gets called by the grid when data is requested. + * + * @param _idxStart is the index of the first row for which data is + * requested. + * @param _idxEnd is the index of the last requested row. + */ + _gridCallback( _idxStart, _idxEnd) + { + + var needsData = false; + + // Iterate over all elements the dataview requested and create a row + // which indicates that we are currently loading data + for (var i = _idxStart; i <= _idxEnd; i++) + { + var entry = this._getIndexEntry(i); + + // Insert the row for the entry -- do not update rows which are + // already existing, as we do not have new data for those. + if (!this._insertDataRow(entry, false) && needsData === false) + { + needsData = i; + } + } + + // Queue fetching that data range + if (needsData !== false) + { + this._queueFetch(et2_bounds(needsData, _idxEnd + 1), needsData == _idxStart ? 0 : needsData > _idxStart ? 1 : -1, false); + } + } + + /** + * The _queueFetch function is used to queue a fetch request. + * TODO: Refresh is currently not used + */ + _queueFetch( _range, _direction, _isUpdate) + { + + // Force immediate to be false + _isUpdate = _isUpdate ? _isUpdate : false; + + // Push the requests onto the request queue + var start = Math.max(0, _range.top); + var end = Math.min(this._grid.getTotalCount(), _range.bottom); + for (var i = start; i < end; i++) + { + if (typeof this._queue[i] === "undefined") + { + this._queue[i] = _direction; // Stage 1 - queue for after current, -1 -- queue for before current + } + } + + // Start the queue timer, if this has not already been done + if (this._queueTimer === null && !_isUpdate) + { + var self = this; + egw.debug('log', 'Dataview queue: ', _range); + this._queueTimer = window.setTimeout(function () { + self._flushQueue(false); + }, ET2_DATAVIEW_FETCH_TIMEOUT); + } + + if (_isUpdate) + { + this._flushQueue(true); + } + } + + /** + * Flushes the queue. + */ + _flushQueue( _isUpdate) + { + + // Clear any still existing timer + this._clearTimer(); + + // Mark all elements in a radius of ET2_DATAVIEW_STEPSIZE + var marked = {}; + var r = _isUpdate ? 0 : Math.floor(ET2_DATAVIEW_STEPSIZE / 2); + var total = this._grid.getTotalCount(); + for (var key in this._queue) + { + if (this._queue[key] > 1) + continue; + + key = parseInt(key); + + var b = Math.max(0, key - r + (r * this._queue[key])); + var t = Math.min(key + r + (r * this._queue[key]), total - 1); + var c = 0; + for (var i = b; i <= t && c < ET2_DATAVIEW_STEPSIZE; i ++) + { + if (typeof this._queue[i] == "undefined" + || this._queue[i] <= 1) + { + this._queue[i] = 2; // Stage 2 -- pending or available + marked[i] = true; + c++; + } + } + } + + // Create a list with start indices and counts + var fetchList = []; + var entry = null; + var last = 0; + + // Get the int keys and sort the array numeric + var arr = et2_arrayIntKeys(marked).sort( + function(a,b){return a > b ? 1 : (a == b ? 0 : -1)}); + + for (var i = 0; i < arr.length; i++) + { + if (i == 0 || arr[i] - last > 1) + { + if (entry) + { + fetchList.push(entry); + } + entry = { + "start": arr[i], + "count": 1 + }; + } + else + { + entry.count++; + } + + last = arr[i]; + } + + if (entry) + { + fetchList.push(entry); + } + + // Special case: If there are no entries in the fetch list and this is + // an update, create an dummy entry, so that we'll get the current count + if (fetchList.length === 0 && _isUpdate) + { + fetchList.push({ + "start": 0, "count": 0 + }); + + // Disable grid invalidate, or it might request again before we're done + this._grid.doInvalidate = false; + } + + egw.debug("log", "Dataview flush", fetchList); + // Execute all queries + for (var i = 0; i < fetchList.length; i++) + { + // Build the query + var query = { + "start": fetchList[i].start, + "num_rows": fetchList[i].count, + "refresh": false + }; + + // Context used in the callback function + var ctx = { + "self": this, + "start": query.start, + "count": query.num_rows, + "lastModification": this._lastModification + }; + if(this.dataStorePrefix) + { + ctx.prefix = this.dataStorePrefix; + } + + this._queueRequest(query, ctx); + } + } + + /** + * Queue a request for data + * @param {Object} query + * @param {Object} ctx + */ + _queueRequest(query, ctx) + { + this._request_queue.push({ + query: query, + context: ctx, + // Start pending, set to 1 when request sent + status: 0 + }); + + this._fetchQueuedRequest(); + } + + /** + * Fetch data for a queued request, subject to rate limit + */ + _fetchQueuedRequest() + { + // Check to see if there's room + var count = 0; + for (var i = 0; i < this._request_queue.length; i++) + { + if(this._request_queue[i].status > 0) count++; + } + // Too many requests, will try again after response is received + if(count >= et2_dataview_controller.CONCURRENT_REQUESTS || this._request_queue.length === 0) + { + return; + } + + // Keep at least 1 previous pending + var keep = 1; + + // The most recent is the one the user's most interested in + var request = null; + for(var i = this._request_queue.length - 1; i >= 0; i--) + { + // Only interested in pending requests (status 0) + if(this._request_queue[i].status != 0) + { + continue; + } + if(request == null) + { + request = this._request_queue[i]; + } + else if (keep > 0) + { + keep--; + } + else if (keep <= 0) + { + // Cancel pending, they've probably scrolled past. + this._request_queue.splice(i,1); + } + } + if(request == null) return; + + // Request being sent + request.status = 1; + + // Call the callback + this._dataProvider.dataFetch(request.query, this._fetchCallback, request.context); + } + + _clearTimer( ) + { + + // Reset the queue timer upon destruction + if (this._queueTimer) + { + window.clearTimeout(this._queueTimer); + this._queueTimer = null; + } + + } + + /** + * Called by the data source when the data changes + * + * @param _data Object|null New data, or null. Null will remove the row. + */ + _dataCallback( _data) + { + // Set the "hasData" flag + this.self.hasData = true; + + // Call the row callback with the new data -- the row callback then + // generates the row DOM nodes that will be inserted into the grid + if (this.self._rowCallback) + { + // Remove everything from the current row + this.entry.row.clear(); + + // If there's no data, stop + if(typeof _data == "undefined" || _data == null) + { + this.self._destroyCallback.call( + this, + this.entry.row + ); + return; + } + + // Fill the row DOM Node with data + this.self._rowCallback.call( + this.self._context, + _data, + this.entry.row, + this.entry.idx, + this.entry + ); + + // Attach the "subgrid" tag to the row, if the depth of this + // controller is larger than zero + var tr = this.entry.row.getDOMNode(); + var d = this.self.getDepth(); + if (d > 0) + { + jQuery(tr).addClass("subentry"); + jQuery("td:first",tr).children("div").last().addClass("level_" + d + " indentation"); + + if(this.entry.idx == 0) + { + // Set the CSS for the level - required so columns line up + var indent = jQuery("").appendTo('body'); + egw.css(".subentry td div.innerContainer.level_"+d, + "margin-right:" + (parseInt(indent.css("margin-right")) * d) + "px" + ); + indent.remove(); + } + } + + var links = null; + + // Look for a flag in the row to avoid actions. Use for sums or extra header rows. + if(!_data.no_actions) + { + // Get the action links if the links callback is set + if (this.self._linkCallback) + { + links = this.self._linkCallback.call( + this.self._context, + _data, + this.entry.idx, + this.entry.uid + ); + } + + // Register the row in the selection manager + this.self._selectionMgr.registerRow(this.entry.uid, this.entry.idx, + tr, links); + } + else + { + // Remember that + this.entry.no_actions = true; + } + + // Invalidate the current row entry + this.entry.row.invalidate(); + } + } + + /** + * + */ + _destroyCallback( _row) + { + + // Unregister the row from the selection manager, if not selected + // If it is selected, leave it there - allows selecting rows and scrolling + var selection = this.self._selectionMgr._getRegisteredRowsEntry(this.entry.uid); + if (this.entry.row && selection && !egwBitIsSet(selection.state, EGW_AO_STATE_SELECTED)) + { + var tr = this.entry.row.getDOMNode(); + this.self._selectionMgr._updateState(this.entry.uid, EGW_AO_STATE_NORMAL); + this.self._selectionMgr.unregisterRow(this.entry.uid, tr); + } + + // There is no further row connected to the entry + this.entry.row = null; + + // Unregister the data callback + this.self._dataProvider.dataUnregisterUID(this.entry.uid, + this.self._dataCallback, this); + } + + /** + * Returns an array containing "_count" index mapping entries starting from + * the index given in "_start". + */ + _getIndexMapping( _start, _count) + { + var result = []; + + for (var i = _start; i < _start + _count; i++) + { + result.push(this._getIndexEntry(i)); + } + + return result; + } + + /** + * Updates the grid according to the new order. The function simply does the + * following: It iterates along the new order (given in _order) and the old + * order given in _idxMap. Iteration variables used are + * a) i -- points to the current entry in _order + * b) idx -- points to the current grid row that will be effected by + * this operation. + * c) mapIdx -- points to the current entry in _indexMap + * The following cases may occur: + * a) The current entry in the old order has no uid or no row -- in that + * case the row at the current position is simply updated, + * the old pointer will be incremented. + * b) The two uids differ -- insert a new row with the new uid, do not + * increment the old pointer. + * c) The two uids are the same -- increment the old pointer. + * In a last step all rows that are left in the old order are deleted. All + * newly created index entries are returned. This function does not update + * the internal mapping in _idxMap. + */ + _updateOrder( _start, _count, _idxMap, _order) + { + // The result contains the newly created index map entries which have to + // be merged with the result + var result = []; + + // Iterate over the new order + var mapIdx = 0; + var idx = _start; + for (var i = 0; i < _order.length; i++, idx++) + { + var current = _idxMap[mapIdx]; + + if (!current.row || !current.uid) + { + // If there is no row yet at the current position or the uid + // of that entry is unknown, simply update the entry. + current.uid = _order[i]; + current.idx = idx; + + // Only update the row, if it is displayed (e.g. has a "loading" + // row displayed) -- this is needed for prefetching + if (current.row) + { + this._insertDataRow(current, true); + } + + mapIdx++; + } + else if (current.uid !== _order[i]) + { + // Insert a new row at the new position + var entry = { + "idx": idx, + "uid": _order[i], + "row": null + }; + + this._insertDataRow(entry, true); + + // Remember the new entry + result.push(entry); + } + else + { + // Do nothing, the uids do not differ, just update the index of + // the element + current.idx = idx; + mapIdx++; + } + } + + // Delete as many rows as we have left, invalidate the corresponding + // index entry + for (var i = mapIdx; i < _idxMap.length; i++) + { + if(typeof _idxMap[i] != 'undefined') + { + _idxMap[i].uid = null; + } + } + + return result; + } + + _mergeResult( _newEntries, _invalidStartIdx, _diff, _total) + { + + if (_newEntries.length > 0 || _diff > 0) + { + // Create a new index map + var newMap = {}; + + // Insert all new entries into the new index map + for (var i = 0; i < _newEntries.length; i++) + { + newMap[_newEntries[i].idx] = _newEntries[i]; + } + + // Merge the old map with all old entries + for (var key in this._indexMap) + { + // Get the corresponding index entry + var entry = this._indexMap[key]; + + // Calculate the new index -- if rows were deleted, we'll + // have to adjust the index + var newIdx = entry.idx >= _invalidStartIdx + ? entry.idx - _diff : entry.idx; + if (newIdx >= 0 && newIdx < _total + && typeof newMap[newIdx] === "undefined") + { + entry.idx = newIdx; + newMap[newIdx] = entry; + } + else + { + // Make sure the old entry gets invalidated + entry.idx = null; + entry.row = null; + } + } + + // Make the new index map the current index map + this._indexMap = newMap; + this._selectionMgr.setIndexMap(newMap); + } + + } + + _fetchCallback( _response) + { + // Remove answered request from queue + var request = null; + for(var i = 0; i < this.self._request_queue.length; i++) + { + if(this.self._request_queue[i].context == this) + { + request = this.self._request_queue[i]; + this.self._request_queue.splice(i,1); + break; + } + } + + this.self._lastModification = _response.lastModification; + + // Do nothing if _response.order evaluates to false + if (!_response.order) + { + return; + } + + // Make sure _response.order.length is not longer than the requested + // count, if a specific count was requested + var order = this.count != 0 ? _response.order.splice(0, this.count) : _response.order; + + // Remove from queue, or it will not be fetched again + if(_response.total < this.count) + { + // Less rows than we expected + // Clear the queue, or the remnants will never be loaded again + this.self._queue = {}; + } + else + { + for(var i = this.start; i < this.start + order.length; i++) + delete this.self._queue[i]; + } + + // Get the current index map for the updated region + var idxMap = this.self._getIndexMapping(this.start, order.length); + + // Update the grid using the new order. The _updateOrder function does + // not update the internal mapping while inserting and deleting rows, as + // this would move us to another asymptotic runtime level. + var res = this.self._updateOrder(this.start, this.count, idxMap, order); + + // Merge the new indices, update all indices with rows that were not + // affected and invalidate all indices if there were changes + this.self._mergeResult(res, this.start + order.length, + idxMap.length - order.length, _response.total); + + if(_response.total == 0) + { + this.self._emptyRow(true); + } + else + { + var row = jQuery(".egwGridView_empty",this.self._grid.innerTbody).remove(); + this.self._selectionMgr.unregisterRow("",0,row.get(0)); + } + + // Now it's OK to invalidate, if it wasn't before + this.self._grid.doInvalidate = true; + + // Update the total element count in the grid + this.self._grid.setTotalCount(_response.total); + this.self._selectionMgr.setTotalCount(_response.total); + + // Schedule an invalidate, in case total is the same + this.self._grid.invalidate(); + + // Check if requests are waiting + this.self._fetchQueuedRequest(); + } + + /** + * Insert an empty / placeholder row when there is no data to display + */ + _emptyRow(_noRows) + { + var noRows = !_noRows ? false : true; + jQuery(".egwGridView_empty",this._grid.innerTbody).remove(); + if(typeof this._grid._rowProvider != "undefined" && this._grid._rowProvider.getPrototype("empty")) + { + var placeholder = this._grid._rowProvider.getPrototype("empty"); + if(jQuery("td",placeholder).length == 1) + { + jQuery("td",placeholder).css("width",this._grid.outerCell.width() + "px") + } + placeholder.appendTo(this._grid.innerTbody); + + // Register placeholder action only if no rows + if (noRows) + { + // Get the action links if the links callback is set + var links = null; + if (this._linkCallback) + { + links = this._linkCallback.call( + this._context, + {}, + 0, + "" + ); + } + this._selectionMgr.registerRow("",0,placeholder.get(0), links); + } + } + } + + /** + * Callback function used by the selection manager to translate the selected + * range to uids. + */ + _selectionFetchRange( _range, _callback, _context) + { + this._dataProvider.dataFetch( + { "start": _range.top, "num_rows": _range.bottom - _range.top + 1, + "no_data": true }, + function (_response) { + _callback.call(_context, _response.order); + }, + _context + ); + } + + /** + * Tells the grid to make the given index visible. + */ + _makeIndexVisible( _idx) + { + this._grid.makeIndexVisible(_idx); + } + +} + diff --git a/api/js/etemplate/et2_dataview_controller_selection.js b/api/js/etemplate/et2_dataview_controller_selection.js index 0ada4b5b67..ef7c801a01 100644 --- a/api/js/etemplate/et2_dataview_controller_selection.js +++ b/api/js/etemplate/et2_dataview_controller_selection.js @@ -1,3 +1,4 @@ +"use strict"; /** * EGroupware eTemplate2 * @@ -7,15 +8,14 @@ * @link http://www.egroupware.org * @author Andreas Stöckel * @copyright Stylite 2011-2012 - * @version $Id$ - */ + * /*egw:uses - et2_dataview_view_aoi; + et2_dataview_view_aoi; - egw_action.egw_keymanager; + egw_action.egw_keymanager; */ - +Object.defineProperty(exports, "__esModule", { value: true }); /** * The selectioManager is internally used by the et2_dataview_controller class * to manage the row selection. @@ -27,659 +27,492 @@ * * @augments Class */ -var et2_dataview_selectionManager = (function(){ "use strict"; return Class.extend( -{ - - // Maximum number of rows we can safely fetch for selection - // Actual selection may have more rows if we already have some - MAX_SELECTION: 1000, - - /** - * Constructor - * - * @param _parent - * @param _indexMap - * @param _actionObjectManager - * @param _queryRangeCallback - * @param _makeVisibleCallback - * @param _context - * @memberOf et2_dataview_selectionManager - */ - init: function (_parent, _indexMap, _actionObjectManager, - _queryRangeCallback, _makeVisibleCallback, _context) { - - // Copy the arguments - this._parent = _parent; - this._indexMap = _indexMap; - this._actionObjectManager = _actionObjectManager; - this._queryRangeCallback = _queryRangeCallback; - this._makeVisibleCallback = _makeVisibleCallback; - this._context = _context; - - // Attach this manager to the parent manager if one is given - if (this._parent) - { - this._parent._children.push(this); - } - - // Use our selection instead of object manager's to handle not-loaded rows - if(_actionObjectManager) - { - this._actionObjectManager.getAllSelected = jQuery.proxy( - this.getAllSelected, this - ); - } - - // Internal map which contains all curently selected uids and their - // state - this._registeredRows = {}; - this._focusedEntry = null; - this._invertSelection = false; - this._selectAll = false; - this._inUpdate = false; - this._total = 0; - this._children = []; - - // Callback for when the selection changes - this.select_callback = null; - }, - - destroy: function () { - - // If we have a parent, unregister from that - if (this._parent) - { - var idx = this._parent._children.indexOf(this); - this._parent._children.splice(idx, 1); - } - - // Destroy all children - for (var i = this._children.length - 1; i >= 0; i--) - { - this._children[i].free(); - } - - // Delete all still registered rows - for (var key in this._registeredRows) - { - this.unregisterRow(key, this._registeredRows[key].tr); - } - this.select_callback = null; - }, - - clear: function() { - for (var key in this._registeredRows) - { - this.unregisterRow(key, this._registeredRows[key].tr); - delete this._registeredRows[key]; - } - if(this._actionObjectManager) - { - this._actionObjectManager.clear(); - } - for (key in this._indexMap) { - delete this._indexMap[key]; - } - this._total = 0; - this._focusedEntry = null; - this._invertSelection = false; - this._selectAll = false; - this._inUpdate = false; - }, - - setIndexMap: function (_indexMap) { - this._indexMap = _indexMap; - }, - - setTotalCount: function (_total) { - this._total = _total; - }, - - registerRow: function (_uid, _idx, _tr, _links) { - - // Get the corresponding entry from the registered rows array - var entry = this._getRegisteredRowsEntry(_uid); - - // If the row has changed unregister the old one and do not delete - // entry from the entry map - if (entry.tr && entry.tr !== _tr) { - this.unregisterRow(_uid, entry.tr, true); - } - - // Create the AOI for the tr - if (!entry.tr && _links) - { - this._attachActionObjectInterface(entry, _tr, _uid); - this._attachActionObject(entry, _tr, _uid, _links, _idx); - } - - // Update the entry - if(entry.ao) entry.ao._index; - entry.idx = _idx; - entry.tr = _tr; - - // Update the visible state of the _tr - this._updateEntryState(entry, entry.state); - }, - - unregisterRow: function (_uid, _tr, _noDelete) { - - // _noDelete defaults to false - _noDelete = _noDelete ? true : false; - - if (typeof this._registeredRows[_uid] !== "undefined" - && this._registeredRows[_uid].tr === _tr) - { - this._inUpdate = true; - - this._registeredRows[_uid].tr = null; - this._registeredRows[_uid].aoi = null; - - // Remove the action object from its container - if (this._registeredRows[_uid].ao) - { - this._registeredRows[_uid].ao.remove(); - this._registeredRows[_uid].ao = null; - } - - if (!_noDelete - && this._registeredRows[_uid].state === EGW_AO_STATE_NORMAL) - { - delete this._registeredRows[_uid]; - } - - this._inUpdate = false; - } - }, - - resetSelection: function () { - this._invertSelection = false; - this._selectAll = false; - this._actionObjectManager.setAllSelected(false); - - for (var key in this._registeredRows) - { - this.setSelected(key, false); - } - for(var i = 0; i < this._children.length; i++) - { - this._children[i].resetSelection(); - } - }, - - setSelected: function (_uid, _selected) { - this._selectAll = false; - var entry = this._getRegisteredRowsEntry(_uid); - this._updateEntryState(entry, - egwSetBit(entry.state, EGW_AO_STATE_SELECTED, _selected)); - }, - - getAllSelected: function() - { - var selected = this.getSelected(); - return selected.all || (selected.ids.length === this._total); - }, - - setFocused: function (_uid, _focused) { - // Reset the state of the currently focused entry - if (this._focusedEntry) - { - this._updateEntryState(this._focusedEntry, - egwSetBit(this._focusedEntry.state, EGW_AO_STATE_FOCUSED, - false)); - this._focusedEntry = null; - } - // Mark the new given uid as focused - if (_focused) - { - //console.log('et2_dataview_controller_selection::setFocused -> UID:'+_uid+' is focused by:'+this._actionObjectManager.name); - var entry = this._focusedEntry = this._getRegisteredRowsEntry(_uid); - this._updateEntryState(entry, - egwSetBit(entry.state, EGW_AO_STATE_FOCUSED, true)); - } - }, - - selectAll: function () { - // Reset the selection - this.resetSelection(); - - this._selectAll = true; - - // Run as a range if there's less then the max - if(egw.dataKnownUIDs(this._context._dataProvider.dataStorePrefix).length !== this._total && - this._total <= this.MAX_SELECTION - ) - { - this._selectRange(0, this._total); - } - // Tell action manager to do all - this._actionObjectManager.setAllSelected(true); - - // Update the selection - for (var key in this._registeredRows) - { - var entry = this._registeredRows[key]; - this._updateEntryState(entry, entry.state); - } - - this._selectAll = true; - }, - - getSelected: function () { - // Collect all currently selected ids - var ids = []; - for (var key in this._registeredRows) - { - if (egwBitIsSet(this._registeredRows[key].state, EGW_AO_STATE_SELECTED)) - { - ids.push(key); - } - } - - // Push all events of the child managers onto the list - for (var i = 0; i < this._children.length; i++) - { - ids = ids.concat(this._children[i].getSelected().ids); - } - - // Return an array containing those ids - // RB: we are currently NOT using "inverted" - return { - //"inverted": this._invertSelection, - "all": this._selectAll, - "ids": ids - }; - }, - - - /** -- PRIVATE FUNCTIONS -- **/ - - - _attachActionObjectInterface: function (_entry, _tr, _uid) { - // Create the AOI which is used internally in the selection manager - // this AOI is not connected to the AO, as the selection manager - // cares about selection etc. - _entry.aoi = new et2_dataview_rowAOI(_tr); - _entry.aoi.setStateChangeCallback( - function (_newState, _changedBit, _shiftState) { - if (_changedBit === EGW_AO_STATE_SELECTED) - { - // Call the select handler - this._handleSelect( - _uid, - _entry, - egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_BLOCK), - egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_MULTI) - ); - } - }, this); - }, - - _getDummyAOI: function (_entry, _tr, _uid, _idx) { - // Create AOI - var dummyAOI = new egwActionObjectInterface(); - var self = this; - - // Handling for Action Implementations updating the state - dummyAOI.doSetState = function (_state) { - if (!self._inUpdate) - { - // Update the "focused" flag - self.setFocused(_uid, egwBitIsSet(_state, EGW_AO_STATE_FOCUSED)); - - // Generally update the state - self._updateState(_uid, _state); - } - }; - - // Handle the "make visible" event, pass the request to the parent - // controller - dummyAOI.doMakeVisible = function () { - self._makeVisibleCallback.call(self._context, _idx); - }; - - // Connect the the two AOIs - dummyAOI.doTriggerEvent = _entry.aoi.doTriggerEvent; - - // Implementation of the getDOMNode function, so that the event - // handlers can be properly bound - dummyAOI.getDOMNode = function () { return _tr; }; - - return dummyAOI; - }, - - _attachActionObject: function (_entry, _tr, _uid, _links, _idx) { - - // Get the dummyAOI which connects the action object to the tr but - // does no selection handling - var dummyAOI = this._getDummyAOI(_entry, _tr, _uid, _idx); - - // Create an action object for the tr and connect it to a dummy AOI - if(this._actionObjectManager) - { - if(this._actionObjectManager.getObjectById(_uid)) - { - var state = _entry.state; - this._actionObjectManager.getObjectById(_uid).remove(); - _entry.state = state; - } - _entry.ao = this._actionObjectManager.addObject(_uid, dummyAOI); - } - - // Force context (actual widget) in here, it's the last place it's available - _entry.ao._context = this._context; - _entry.ao.updateActionLinks(_links); - _entry.ao._index = _idx; - - // Overwrite some functions like "traversePath", "getNext" and - // "getPrevious" - var self = this; - - function getIndexAO (_idx) { - // Check whether the index is in the index map - if (typeof self._indexMap[_idx] !== "undefined" - && self._indexMap[_idx].uid) - { - return self._getRegisteredRowsEntry(self._indexMap[_idx].uid).ao; - } - - return null; - } - - function getElementRelatively (_step) { - var total = self._total || Object.keys(self._indexMap).length; - var max_index = Math.max.apply(Math,Object.keys(self._indexMap)); - // Get a reasonable number of iterations - not all - var count = Math.max(1,Math.min(self._total,50)); - var element = null; - var idx = _entry.idx; - while(element == null && count > 0 && max_index > 0) - { - count--; - element = getIndexAO(Math.max(0, - Math.min(max_index, idx += _step))); - } - return element; - }; - - _entry.ao.getPrevious = function (_step) { - return getElementRelatively(-_step); - }; - - _entry.ao.getNext = function (_step) { - return getElementRelatively(_step); - }; - - _entry.ao.traversePath = function (_obj) { - // Get the start and the stop index - var s = Math.min(this._index, _obj._index); - var e = Math.max(this._index, _obj._index); - - var result = []; - - for (var i = s; i < e; i++) - { - var ao = getIndexAO(i); - if (ao) - { - result.push(ao); - } - } - - return result; - }; - }, - - _updateState: function (_uid, _state) { - var entry = this._getRegisteredRowsEntry(_uid); - - this._updateEntryState(entry, _state); - - return entry; - }, - - _updateEntryState: function (_entry, _state) { - - if (this._selectAll) - { - _state |= EGW_AO_STATE_SELECTED; - } - else if (this._invertSelection) - { - _state ^= EGW_AO_STATE_SELECTED; - } - - // Attach ao if not there, happens for rows loaded for selection, but - // not displayed yet - if(!_entry.ao && _entry.uid && this._actionObjectManager) - { - var _links = []; - for (var key in this._registeredRows) - { - if(this._registeredRows[key].ao && this._registeredRows[key].ao.actionLinks) - { - _links = this._registeredRows[key].ao.actionLinks; - break; - } - } - if(_links) - { - this._attachActionObjectInterface(_entry, null, _entry.uid); - this._attachActionObject(_entry, null, _entry.uid, _links, _entry.idx); - } - } - - // Update the state if it has changed - if ((_entry.aoi && _entry.aoi.getState() !== _state) || _entry.state != _state) - { - this._inUpdate = true; // Recursion prevention - - // Update the state of the action object - if (_entry.ao) - { - _entry.ao.setSelected(egwBitIsSet(_state, EGW_AO_STATE_SELECTED)); - _entry.ao.setFocused(egwBitIsSet(_state, EGW_AO_STATE_FOCUSED)); - } - - this._inUpdate = false; - - // Delete the element if state was set to "NORMAL" and there is - // no tr - if (_state === EGW_AO_STATE_NORMAL && !_entry.tr) - { - delete this._registeredRows[_entry.uid]; - } - } - - // Update the visual state - if (_entry.aoi && _entry.aoi.doSetState) - { - _entry.aoi.doSetState(_state); - } - - // Update the state of the entry - _entry.state = _state; - }, - - _getRegisteredRowsEntry: function (_uid) { - if (typeof this._registeredRows[_uid] === "undefined") - { - this._registeredRows[_uid] = { - "uid": _uid, - "idx": null, - "state": EGW_AO_STATE_NORMAL, - "tr": null, - "aoi": null, - "ao": null - }; - } - - return this._registeredRows[_uid]; - }, - - _handleSelect: function (_uid, _entry, _shift, _ctrl) { - // If not "_ctrl" is set, reset the selection - if (!_ctrl) - { - var top = this; - while(top._parent !== null) - { - top = top._parent; - } - top.resetSelection(); - this._actionObjectManager.setAllSelected(false); // needed for hirachical stuff - } - - // Mark the element that was clicked as selected - var entry = this._getRegisteredRowsEntry(_uid); - this.setSelected(_uid, - !_ctrl || !egwBitIsSet(entry.state, EGW_AO_STATE_SELECTED)); - - // Focus the element if shift is not pressed - if (!_shift) - { - this.setFocused(_uid, true); - } - else if (this._focusedEntry) - { - this._selectRange(this._focusedEntry.idx, _entry.idx); - } - - if(this.select_callback && typeof this.select_callback == "function") - { - this.select_callback.apply(this._context, arguments); - } - }, - - _selectRange: function (_start, _stop) { - // Contains ranges that are not currently in the index map and that have - // to be queried - var queryRanges = []; - - // Iterate over the given range and select the elements in the range - // from _start to _stop - var naStart = false; - var s = Math.min(_start, _stop); - var e = Math.max(_stop, _start); - var RANGE_MAX = 50; - var range_break = s + RANGE_MAX; - for (var i = s; i <= e; i++) - { - if (typeof this._indexMap[i] !== "undefined" && - this._indexMap[i].uid && egw.dataGetUIDdata(this._indexMap[i].uid)) - { - // Add the range to the "queryRanges" - if (naStart !== false) - { - queryRanges.push(et2_bounds(naStart, i - 1)); - naStart = false; - range_break += RANGE_MAX; - } - - // Select the element, unless flagged for exclusion - // Check for no_actions flag via data - var data = egw.dataGetUIDdata(this._indexMap[i].uid); - if(data && data.data && !data.data.no_actions) - { - this.setSelected(this._indexMap[i].uid, true); - } - } - else if (naStart === false) - { - naStart = i; - range_break = naStart + RANGE_MAX; - } - else if(i >= range_break) - { - queryRanges.push(et2_bounds(naStart ? naStart : s, i - 1)); - naStart = i; - range_break += RANGE_MAX; - } - } - - // Add the last range to the "queryRanges" - if (naStart !== false) - { - queryRanges.push(et2_bounds(naStart, i - 1)); - naStart = false; - } - - // Query all unknown ranges from the server - if(queryRanges.length > 0) - { - this._query_ranges(queryRanges); - } - }, - - _query_ranges: function _query_ranges(queryRanges) - { - var that = this; - var record_count = 0; - var range_index = 0; - var range_count = queryRanges.length; - var cont = true; - var fetchPromise = new Promise(function (resolve) { - resolve(); - }); - // Found after dialog loads - var progressbar; - - var parent = et2_dialog._create_parent(); - var dialog = et2_createWidget("dialog", { - callback: - // Abort the long task if they canceled the data load - function() {cont = false}, - template: egw.webserverUrl+'/api/templates/default/long_task.xet', - message: egw.lang('Loading'), - title: egw.lang('please wait...'), - buttons: [{"button_id": et2_dialog.CANCEL_BUTTON,"text": egw.lang('cancel'),id: 'dialog[cancel]',image: 'cancel'}], - width: 300 - }, parent); - jQuery(dialog.template.DOMContainer).on('load', function() { - // Get access to template widgets - progressbar = dialog.template.widgetContainer.getWidgetById('progressbar'); - }); - - for (var i = 0; i < queryRanges.length; i++) - { - if(record_count + (queryRanges[i].bottom - queryRanges[i].top+1) > that.MAX_SELECTION) - { - egw.message(egw.lang('Too many rows selected.
Select all, or less than %1 rows', that.MAX_SELECTION)); - break; - } - else - { - record_count += (queryRanges[i].bottom - queryRanges[i].top+1); - fetchPromise = fetchPromise.then((function () - { - // Check for abort - if(!cont) return; - - return new Promise(function(resolve) { - that._queryRangeCallback.call(that._context, this, - function (_order) { - for (var j = 0; j < _order.length; j++) - { - // Check for no_actions flag via data since entry isn't there/available - var data = egw.dataGetUIDdata(_order[j]); - if(!data || data && data.data && !data.data.no_actions) - { - var entry = this._getRegisteredRowsEntry(_order[j]); - this._updateEntryState(entry, - egwSetBit(entry.state, EGW_AO_STATE_SELECTED, true)); - } - } - progressbar.set_value(100*(++range_index/range_count)); - resolve(); - }, that); - }.bind(this)); - }).bind(queryRanges[i])); - } - } - fetchPromise.finally(function() { - dialog.destroy(); - }); - } - -});}).call(this); - +var et2_dataview_selectionManager = /** @class */ (function () { + /** + * Constructor + * + * @param _parent + * @param _indexMap + * @param _actionObjectManager + * @param _queryRangeCallback + * @param _makeVisibleCallback + * @param _context + * @memberOf et2_dataview_selectionManager + */ + function et2_dataview_selectionManager(_parent, _indexMap, _actionObjectManager, _queryRangeCallback, _makeVisibleCallback, _context) { + // Copy the arguments + this._parent = _parent; + this._indexMap = _indexMap; + this._actionObjectManager = _actionObjectManager; + this._queryRangeCallback = _queryRangeCallback; + this._makeVisibleCallback = _makeVisibleCallback; + this._context = _context; + // Attach this manager to the parent manager if one is given + if (this._parent) { + this._parent._children.push(this); + } + // Use our selection instead of object manager's to handle not-loaded rows + if (_actionObjectManager) { + this._actionObjectManager.getAllSelected = jQuery.proxy(this.getAllSelected, this); + } + // Internal map which contains all curently selected uids and their + // state + this._registeredRows = {}; + this._focusedEntry = null; + this._invertSelection = false; + this._selectAll = false; + this._inUpdate = false; + this._total = 0; + this._children = []; + // Callback for when the selection changes + this.select_callback = null; + } + et2_dataview_selectionManager.prototype.destroy = function () { + // If we have a parent, unregister from that + if (this._parent) { + var idx = this._parent._children.indexOf(this); + this._parent._children.splice(idx, 1); + } + // Destroy all children + for (var i = this._children.length - 1; i >= 0; i--) { + this._children[i].destroy(); + } + // Delete all still registered rows + for (var key in this._registeredRows) { + this.unregisterRow(key, this._registeredRows[key].tr); + } + this.select_callback = null; + }; + et2_dataview_selectionManager.prototype.clear = function () { + for (var key in this._registeredRows) { + this.unregisterRow(key, this._registeredRows[key].tr); + delete this._registeredRows[key]; + } + if (this._actionObjectManager) { + this._actionObjectManager.clear(); + } + for (key in this._indexMap) { + delete this._indexMap[key]; + } + this._total = 0; + this._focusedEntry = null; + this._invertSelection = false; + this._selectAll = false; + this._inUpdate = false; + }; + et2_dataview_selectionManager.prototype.setIndexMap = function (_indexMap) { + this._indexMap = _indexMap; + }; + et2_dataview_selectionManager.prototype.setTotalCount = function (_total) { + this._total = _total; + }; + et2_dataview_selectionManager.prototype.registerRow = function (_uid, _idx, _tr, _links) { + // Get the corresponding entry from the registered rows array + var entry = this._getRegisteredRowsEntry(_uid); + // If the row has changed unregister the old one and do not delete + // entry from the entry map + if (entry.tr && entry.tr !== _tr) { + this.unregisterRow(_uid, entry.tr, true); + } + // Create the AOI for the tr + if (!entry.tr && _links) { + this._attachActionObjectInterface(entry, _tr, _uid); + this._attachActionObject(entry, _tr, _uid, _links, _idx); + } + // Update the entry + if (entry.ao) + entry.ao._index; + entry.idx = _idx; + entry.tr = _tr; + // Update the visible state of the _tr + this._updateEntryState(entry, entry.state); + }; + et2_dataview_selectionManager.prototype.unregisterRow = function (_uid, _tr, _noDelete) { + // _noDelete defaults to false + _noDelete = _noDelete ? true : false; + if (typeof this._registeredRows[_uid] !== "undefined" + && this._registeredRows[_uid].tr === _tr) { + this._inUpdate = true; + this._registeredRows[_uid].tr = null; + this._registeredRows[_uid].aoi = null; + // Remove the action object from its container + if (this._registeredRows[_uid].ao) { + this._registeredRows[_uid].ao.remove(); + this._registeredRows[_uid].ao = null; + } + if (!_noDelete + && this._registeredRows[_uid].state === EGW_AO_STATE_NORMAL) { + delete this._registeredRows[_uid]; + } + this._inUpdate = false; + } + }; + et2_dataview_selectionManager.prototype.resetSelection = function () { + this._invertSelection = false; + this._selectAll = false; + this._actionObjectManager.setAllSelected(false); + for (var key in this._registeredRows) { + this.setSelected(key, false); + } + for (var i = 0; i < this._children.length; i++) { + this._children[i].resetSelection(); + } + }; + et2_dataview_selectionManager.prototype.setSelected = function (_uid, _selected) { + this._selectAll = false; + var entry = this._getRegisteredRowsEntry(_uid); + this._updateEntryState(entry, egwSetBit(entry.state, EGW_AO_STATE_SELECTED, _selected)); + }; + et2_dataview_selectionManager.prototype.getAllSelected = function () { + var selected = this.getSelected(); + return selected.all || (selected.ids.length === this._total); + }; + et2_dataview_selectionManager.prototype.setFocused = function (_uid, _focused) { + // Reset the state of the currently focused entry + if (this._focusedEntry) { + this._updateEntryState(this._focusedEntry, egwSetBit(this._focusedEntry.state, EGW_AO_STATE_FOCUSED, false)); + this._focusedEntry = null; + } + // Mark the new given uid as focused + if (_focused) { + //console.log('et2_dataview_controller_selection::setFocused -> UID:'+_uid+' is focused by:'+this._actionObjectManager.name); + var entry = this._focusedEntry = this._getRegisteredRowsEntry(_uid); + this._updateEntryState(entry, egwSetBit(entry.state, EGW_AO_STATE_FOCUSED, true)); + } + }; + et2_dataview_selectionManager.prototype.selectAll = function () { + // Reset the selection + this.resetSelection(); + this._selectAll = true; + // Run as a range if there's less then the max + if (egw.dataKnownUIDs(this._context._dataProvider.dataStorePrefix).length !== this._total && + this._total <= et2_dataview_selectionManager.MAX_SELECTION) { + this._selectRange(0, this._total); + } + // Tell action manager to do all + this._actionObjectManager.setAllSelected(true); + // Update the selection + for (var key in this._registeredRows) { + var entry = this._registeredRows[key]; + this._updateEntryState(entry, entry.state); + } + this._selectAll = true; + }; + et2_dataview_selectionManager.prototype.getSelected = function () { + // Collect all currently selected ids + var ids = []; + for (var key in this._registeredRows) { + if (egwBitIsSet(this._registeredRows[key].state, EGW_AO_STATE_SELECTED)) { + ids.push(key); + } + } + // Push all events of the child managers onto the list + for (var i = 0; i < this._children.length; i++) { + ids = ids.concat(this._children[i].getSelected().ids); + } + // Return an array containing those ids + // RB: we are currently NOT using "inverted" + return { + //"inverted": this._invertSelection, + "all": this._selectAll, + "ids": ids + }; + }; + /** -- PRIVATE FUNCTIONS -- **/ + et2_dataview_selectionManager.prototype._attachActionObjectInterface = function (_entry, _tr, _uid) { + // Create the AOI which is used internally in the selection manager + // this AOI is not connected to the AO, as the selection manager + // cares about selection etc. + _entry.aoi = new et2_dataview_rowAOI(_tr); + _entry.aoi.setStateChangeCallback(function (_newState, _changedBit, _shiftState) { + if (_changedBit === EGW_AO_STATE_SELECTED) { + // Call the select handler + this._handleSelect(_uid, _entry, egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_BLOCK), egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_MULTI)); + } + }, this); + }; + et2_dataview_selectionManager.prototype._getDummyAOI = function (_entry, _tr, _uid, _idx) { + // Create AOI + var dummyAOI = new egwActionObjectInterface(); + var self = this; + // Handling for Action Implementations updating the state + dummyAOI.doSetState = function (_state) { + if (!self._inUpdate) { + // Update the "focused" flag + self.setFocused(_uid, egwBitIsSet(_state, EGW_AO_STATE_FOCUSED)); + // Generally update the state + self._updateState(_uid, _state); + } + }; + // Handle the "make visible" event, pass the request to the parent + // controller + dummyAOI.doMakeVisible = function () { + self._makeVisibleCallback.call(self._context, _idx); + }; + // Connect the the two AOIs + dummyAOI.doTriggerEvent = _entry.aoi.doTriggerEvent; + // Implementation of the getDOMNode function, so that the event + // handlers can be properly bound + dummyAOI.getDOMNode = function () { return _tr; }; + return dummyAOI; + }; + et2_dataview_selectionManager.prototype._attachActionObject = function (_entry, _tr, _uid, _links, _idx) { + // Get the dummyAOI which connects the action object to the tr but + // does no selection handling + var dummyAOI = this._getDummyAOI(_entry, _tr, _uid, _idx); + // Create an action object for the tr and connect it to a dummy AOI + if (this._actionObjectManager) { + if (this._actionObjectManager.getObjectById(_uid)) { + var state = _entry.state; + this._actionObjectManager.getObjectById(_uid).remove(); + _entry.state = state; + } + _entry.ao = this._actionObjectManager.addObject(_uid, dummyAOI); + } + // Force context (actual widget) in here, it's the last place it's available + _entry.ao._context = this._context; + _entry.ao.updateActionLinks(_links); + _entry.ao._index = _idx; + // Overwrite some functions like "traversePath", "getNext" and + // "getPrevious" + var self = this; + function getIndexAO(_idx) { + // Check whether the index is in the index map + if (typeof self._indexMap[_idx] !== "undefined" + && self._indexMap[_idx].uid) { + return self._getRegisteredRowsEntry(self._indexMap[_idx].uid).ao; + } + return null; + } + function getElementRelatively(_step) { + var total = self._total || Object.keys(self._indexMap).length; + var max_index = Math.max.apply(Math, Object.keys(self._indexMap)); + // Get a reasonable number of iterations - not all + var count = Math.max(1, Math.min(self._total, 50)); + var element = null; + var idx = _entry.idx; + while (element == null && count > 0 && max_index > 0) { + count--; + element = getIndexAO(Math.max(0, Math.min(max_index, idx += _step))); + } + return element; + } + + _entry.ao.getPrevious = function (_step) { + return getElementRelatively(-_step); + }; + _entry.ao.getNext = function (_step) { + return getElementRelatively(_step); + }; + _entry.ao.traversePath = function (_obj) { + // Get the start and the stop index + var s = Math.min(this._index, _obj._index); + var e = Math.max(this._index, _obj._index); + var result = []; + for (var i = s; i < e; i++) { + var ao = getIndexAO(i); + if (ao) { + result.push(ao); + } + } + return result; + }; + }; + et2_dataview_selectionManager.prototype._updateState = function (_uid, _state) { + var entry = this._getRegisteredRowsEntry(_uid); + this._updateEntryState(entry, _state); + return entry; + }; + et2_dataview_selectionManager.prototype._updateEntryState = function (_entry, _state) { + if (this._selectAll) { + _state |= EGW_AO_STATE_SELECTED; + } + else if (this._invertSelection) { + _state ^= EGW_AO_STATE_SELECTED; + } + // Attach ao if not there, happens for rows loaded for selection, but + // not displayed yet + if (!_entry.ao && _entry.uid && this._actionObjectManager) { + var _links = []; + for (var key in this._registeredRows) { + if (this._registeredRows[key].ao && this._registeredRows[key].ao.actionLinks) { + _links = this._registeredRows[key].ao.actionLinks; + break; + } + } + if (_links) { + this._attachActionObjectInterface(_entry, null, _entry.uid); + this._attachActionObject(_entry, null, _entry.uid, _links, _entry.idx); + } + } + // Update the state if it has changed + if ((_entry.aoi && _entry.aoi.getState() !== _state) || _entry.state != _state) { + this._inUpdate = true; // Recursion prevention + // Update the state of the action object + if (_entry.ao) { + _entry.ao.setSelected(egwBitIsSet(_state, EGW_AO_STATE_SELECTED)); + _entry.ao.setFocused(egwBitIsSet(_state, EGW_AO_STATE_FOCUSED)); + } + this._inUpdate = false; + // Delete the element if state was set to "NORMAL" and there is + // no tr + if (_state === EGW_AO_STATE_NORMAL && !_entry.tr) { + delete this._registeredRows[_entry.uid]; + } + } + // Update the visual state + if (_entry.aoi && _entry.aoi.doSetState) { + _entry.aoi.doSetState(_state); + } + // Update the state of the entry + _entry.state = _state; + }; + et2_dataview_selectionManager.prototype._getRegisteredRowsEntry = function (_uid) { + if (typeof this._registeredRows[_uid] === "undefined") { + this._registeredRows[_uid] = { + "uid": _uid, + "idx": null, + "state": EGW_AO_STATE_NORMAL, + "tr": null, + "aoi": null, + "ao": null + }; + } + return this._registeredRows[_uid]; + }; + et2_dataview_selectionManager.prototype._handleSelect = function (_uid, _entry, _shift, _ctrl) { + // If not "_ctrl" is set, reset the selection + if (!_ctrl) { + var top = this; + while (top._parent !== null) { + top = top._parent; + } + top.resetSelection(); + this._actionObjectManager.setAllSelected(false); // needed for hirachical stuff + } + // Mark the element that was clicked as selected + var entry = this._getRegisteredRowsEntry(_uid); + this.setSelected(_uid, !_ctrl || !egwBitIsSet(entry.state, EGW_AO_STATE_SELECTED)); + // Focus the element if shift is not pressed + if (!_shift) { + this.setFocused(_uid, true); + } + else if (this._focusedEntry) { + this._selectRange(this._focusedEntry.idx, _entry.idx); + } + if (this.select_callback && typeof this.select_callback == "function") { + this.select_callback.apply(this._context, arguments); + } + }; + et2_dataview_selectionManager.prototype._selectRange = function (_start, _stop) { + // Contains ranges that are not currently in the index map and that have + // to be queried + var queryRanges = []; + // Iterate over the given range and select the elements in the range + // from _start to _stop + var naStart = false; + var s = Math.min(_start, _stop); + var e = Math.max(_stop, _start); + var RANGE_MAX = 50; + var range_break = s + RANGE_MAX; + for (var i = s; i <= e; i++) { + if (typeof this._indexMap[i] !== "undefined" && + this._indexMap[i].uid && egw.dataGetUIDdata(this._indexMap[i].uid)) { + // Add the range to the "queryRanges" + if (naStart !== false) { + queryRanges.push(et2_bounds(naStart, i - 1)); + naStart = false; + range_break += RANGE_MAX; + } + // Select the element, unless flagged for exclusion + // Check for no_actions flag via data + var data = egw.dataGetUIDdata(this._indexMap[i].uid); + if (data && data.data && !data.data.no_actions) { + this.setSelected(this._indexMap[i].uid, true); + } + } + else if (naStart === false) { + naStart = i; + range_break = naStart + RANGE_MAX; + } + else if (i >= range_break) { + queryRanges.push(et2_bounds(naStart ? naStart : s, i - 1)); + naStart = i; + range_break += RANGE_MAX; + } + } + // Add the last range to the "queryRanges" + if (naStart !== false) { + queryRanges.push(et2_bounds(naStart, i - 1)); + naStart = false; + } + // Query all unknown ranges from the server + if (queryRanges.length > 0) { + this._query_ranges(queryRanges); + } + }; + et2_dataview_selectionManager.prototype._query_ranges = function (queryRanges) { + var that = this; + var record_count = 0; + var range_index = 0; + var range_count = queryRanges.length; + var cont = true; + var fetchPromise = new Promise(function (resolve) { + resolve(); + }); + // Found after dialog loads + var progressbar; + var parent = et2_dialog._create_parent(); + var dialog = et2_createWidget("dialog", { + callback: + // Abort the long task if they canceled the data load + function () { cont = false; }, + template: egw.webserverUrl + '/api/templates/default/long_task.xet', + message: egw.lang('Loading'), + title: egw.lang('please wait...'), + buttons: [{ "button_id": et2_dialog.CANCEL_BUTTON, "text": egw.lang('cancel'), id: 'dialog[cancel]', image: 'cancel' }], + width: 300 + }, parent); + jQuery(dialog.template.DOMContainer).on('load', function () { + // Get access to template widgets + progressbar = dialog.template.widgetContainer.getWidgetById('progressbar'); + }); + for (var i = 0; i < queryRanges.length; i++) { + if (record_count + (queryRanges[i].bottom - queryRanges[i].top + 1) > that.MAX_SELECTION) { + egw.message(egw.lang('Too many rows selected.
Select all, or less than %1 rows', that.MAX_SELECTION)); + break; + } + else { + record_count += (queryRanges[i].bottom - queryRanges[i].top + 1); + fetchPromise = fetchPromise.then((function () { + // Check for abort + if (!cont) + return; + return new Promise(function (resolve) { + that._queryRangeCallback.call(that._context, this, function (_order) { + for (var j = 0; j < _order.length; j++) { + // Check for no_actions flag via data since entry isn't there/available + var data = egw.dataGetUIDdata(_order[j]); + if (!data || data && data.data && !data.data.no_actions) { + var entry = this._getRegisteredRowsEntry(_order[j]); + this._updateEntryState(entry, egwSetBit(entry.state, EGW_AO_STATE_SELECTED, true)); + } + } + progressbar.set_value(100 * (++range_index / range_count)); + resolve(); + }, that); + }.bind(this)); + }).bind(queryRanges[i])); + } + } + fetchPromise.finally(function () { + dialog.destroy(); + }); + }; + // Maximum number of rows we can safely fetch for selection + // Actual selection may have more rows if we already have some + et2_dataview_selectionManager.MAX_SELECTION = 1000; + return et2_dataview_selectionManager; +}()); +exports.et2_dataview_selectionManager = et2_dataview_selectionManager; +//# sourceMappingURL=et2_dataview_controller_selection.js.map \ No newline at end of file diff --git a/api/js/etemplate/et2_dataview_controller_selection.ts b/api/js/etemplate/et2_dataview_controller_selection.ts new file mode 100644 index 0000000000..e9907da391 --- /dev/null +++ b/api/js/etemplate/et2_dataview_controller_selection.ts @@ -0,0 +1,720 @@ +/** + * EGroupware eTemplate2 + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package etemplate + * @subpackage dataview + * @link http://www.egroupware.org + * @author Andreas Stöckel + * @copyright Stylite 2011-2012 + * + +/*egw:uses + et2_dataview_view_aoi; + + egw_action.egw_keymanager; +*/ + +/** + * The selectioManager is internally used by the et2_dataview_controller class + * to manage the row selection. + * As the action system does not allow selection of entries which are currently + * not in the dom tree, we have to manage this in this class. The idea is to + * manage an external action object interface for each visible row and proxy all + * state changes between an dummy action object, that does no selection handling, + * and the external action object interface. + * + * @augments Class + */ +export class et2_dataview_selectionManager +{ + + // Maximum number of rows we can safely fetch for selection + // Actual selection may have more rows if we already have some + public static readonly MAX_SELECTION = 1000; + + private _parent: any; + private _indexMap: any; + private _actionObjectManager: any; + private _makeVisibleCallback: any; + private _queryRangeCallback: any; + private select_callback: null; + + private _context: any; + private _registeredRows: {}; + private _focusedEntry: null; + private _invertSelection: boolean; + private _selectAll: boolean; + private _inUpdate: boolean; + private _total: number; + private _children: any[]; + + /** + * Constructor + * + * @param _parent + * @param _indexMap + * @param _actionObjectManager + * @param _queryRangeCallback + * @param _makeVisibleCallback + * @param _context + * @memberOf et2_dataview_selectionManager + */ + constructor(_parent, _indexMap, _actionObjectManager, + _queryRangeCallback, _makeVisibleCallback, _context) + { + + // Copy the arguments + this._parent = _parent; + this._indexMap = _indexMap; + this._actionObjectManager = _actionObjectManager; + this._queryRangeCallback = _queryRangeCallback; + this._makeVisibleCallback = _makeVisibleCallback; + this._context = _context; + + // Attach this manager to the parent manager if one is given + if (this._parent) + { + this._parent._children.push(this); + } + + // Use our selection instead of object manager's to handle not-loaded rows + if(_actionObjectManager) + { + this._actionObjectManager.getAllSelected = jQuery.proxy( + this.getAllSelected, this + ); + } + + // Internal map which contains all curently selected uids and their + // state + this._registeredRows = {}; + this._focusedEntry = null; + this._invertSelection = false; + this._selectAll = false; + this._inUpdate = false; + this._total = 0; + this._children = []; + + // Callback for when the selection changes + this.select_callback = null; + } + + destroy( ) + { + + // If we have a parent, unregister from that + if (this._parent) + { + var idx = this._parent._children.indexOf(this); + this._parent._children.splice(idx, 1); + } + + // Destroy all children + for (var i = this._children.length - 1; i >= 0; i--) + { + this._children[i].destroy(); + } + + // Delete all still registered rows + for (var key in this._registeredRows) + { + this.unregisterRow(key, this._registeredRows[key].tr); + } + this.select_callback = null; + } + + clear( ) + { + for (var key in this._registeredRows) + { + this.unregisterRow(key, this._registeredRows[key].tr); + delete this._registeredRows[key]; + } + if(this._actionObjectManager) + { + this._actionObjectManager.clear(); + } + for (key in this._indexMap) { + delete this._indexMap[key]; + } + this._total = 0; + this._focusedEntry = null; + this._invertSelection = false; + this._selectAll = false; + this._inUpdate = false; + } + + setIndexMap( _indexMap) + { + this._indexMap = _indexMap; + } + + setTotalCount( _total) + { + this._total = _total; + } + + registerRow( _uid, _idx, _tr, _links) + { + + // Get the corresponding entry from the registered rows array + var entry = this._getRegisteredRowsEntry(_uid); + + // If the row has changed unregister the old one and do not delete + // entry from the entry map + if (entry.tr && entry.tr !== _tr) { + this.unregisterRow(_uid, entry.tr, true); + } + + // Create the AOI for the tr + if (!entry.tr && _links) + { + this._attachActionObjectInterface(entry, _tr, _uid); + this._attachActionObject(entry, _tr, _uid, _links, _idx); + } + + // Update the entry + if(entry.ao) entry.ao._index; + entry.idx = _idx; + entry.tr = _tr; + + // Update the visible state of the _tr + this._updateEntryState(entry, entry.state); + } + + unregisterRow( _uid, _tr, _noDelete? : boolean) + { + + // _noDelete defaults to false + _noDelete = _noDelete ? true : false; + + if (typeof this._registeredRows[_uid] !== "undefined" + && this._registeredRows[_uid].tr === _tr) + { + this._inUpdate = true; + + this._registeredRows[_uid].tr = null; + this._registeredRows[_uid].aoi = null; + + // Remove the action object from its container + if (this._registeredRows[_uid].ao) + { + this._registeredRows[_uid].ao.remove(); + this._registeredRows[_uid].ao = null; + } + + if (!_noDelete + && this._registeredRows[_uid].state === EGW_AO_STATE_NORMAL) + { + delete this._registeredRows[_uid]; + } + + this._inUpdate = false; + } + } + + resetSelection( ) + { + this._invertSelection = false; + this._selectAll = false; + this._actionObjectManager.setAllSelected(false); + + for (var key in this._registeredRows) + { + this.setSelected(key, false); + } + for(var i = 0; i < this._children.length; i++) + { + this._children[i].resetSelection(); + } + } + + setSelected( _uid, _selected) + { + this._selectAll = false; + var entry = this._getRegisteredRowsEntry(_uid); + this._updateEntryState(entry, + egwSetBit(entry.state, EGW_AO_STATE_SELECTED, _selected)); + } + + getAllSelected() + { + var selected = this.getSelected(); + return selected.all || (selected.ids.length === this._total); + } + + setFocused( _uid, _focused) + { + // Reset the state of the currently focused entry + if (this._focusedEntry) + { + this._updateEntryState(this._focusedEntry, + egwSetBit(this._focusedEntry.state, EGW_AO_STATE_FOCUSED, + false)); + this._focusedEntry = null; + } + // Mark the new given uid as focused + if (_focused) + { + //console.log('et2_dataview_controller_selection::setFocused -> UID:'+_uid+' is focused by:'+this._actionObjectManager.name); + var entry = this._focusedEntry = this._getRegisteredRowsEntry(_uid); + this._updateEntryState(entry, + egwSetBit(entry.state, EGW_AO_STATE_FOCUSED, true)); + } + } + + selectAll( ) + { + // Reset the selection + this.resetSelection(); + + this._selectAll = true; + + // Run as a range if there's less then the max + if(egw.dataKnownUIDs(this._context._dataProvider.dataStorePrefix).length !== this._total && + this._total <= et2_dataview_selectionManager.MAX_SELECTION + ) + { + this._selectRange(0, this._total); + } + // Tell action manager to do all + this._actionObjectManager.setAllSelected(true); + + // Update the selection + for (var key in this._registeredRows) + { + var entry = this._registeredRows[key]; + this._updateEntryState(entry, entry.state); + } + + this._selectAll = true; + } + + getSelected( ) + { + // Collect all currently selected ids + var ids = []; + for (var key in this._registeredRows) + { + if (egwBitIsSet(this._registeredRows[key].state, EGW_AO_STATE_SELECTED)) + { + ids.push(key); + } + } + + // Push all events of the child managers onto the list + for (var i = 0; i < this._children.length; i++) + { + ids = ids.concat(this._children[i].getSelected().ids); + } + + // Return an array containing those ids + // RB: we are currently NOT using "inverted" + return { + //"inverted": this._invertSelection, + "all": this._selectAll, + "ids": ids + }; + } + + + /** -- PRIVATE FUNCTIONS -- **/ + + + _attachActionObjectInterface( _entry, _tr, _uid) + { + // Create the AOI which is used internally in the selection manager + // this AOI is not connected to the AO, as the selection manager + // cares about selection etc. + _entry.aoi = new et2_dataview_rowAOI(_tr); + _entry.aoi.setStateChangeCallback( + function (_newState, _changedBit, _shiftState) { + if (_changedBit === EGW_AO_STATE_SELECTED) + { + // Call the select handler + this._handleSelect( + _uid, + _entry, + egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_BLOCK), + egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_MULTI) + ); + } + }, this); + } + + _getDummyAOI( _entry, _tr, _uid, _idx) + { + // Create AOI + var dummyAOI = new egwActionObjectInterface(); + var self = this; + + // Handling for Action Implementations updating the state + dummyAOI.doSetState = function (_state) { + if (!self._inUpdate) + { + // Update the "focused" flag + self.setFocused(_uid, egwBitIsSet(_state, EGW_AO_STATE_FOCUSED)); + + // Generally update the state + self._updateState(_uid, _state); + } + }; + + // Handle the "make visible" event, pass the request to the parent + // controller + dummyAOI.doMakeVisible = function () { + self._makeVisibleCallback.call(self._context, _idx); + }; + + // Connect the the two AOIs + dummyAOI.doTriggerEvent = _entry.aoi.doTriggerEvent; + + // Implementation of the getDOMNode function, so that the event + // handlers can be properly bound + dummyAOI.getDOMNode = function () { return _tr; }; + + return dummyAOI; + } + + _attachActionObject( _entry, _tr, _uid, _links, _idx) + { + + // Get the dummyAOI which connects the action object to the tr but + // does no selection handling + var dummyAOI = this._getDummyAOI(_entry, _tr, _uid, _idx); + + // Create an action object for the tr and connect it to a dummy AOI + if(this._actionObjectManager) + { + if(this._actionObjectManager.getObjectById(_uid)) + { + var state = _entry.state; + this._actionObjectManager.getObjectById(_uid).remove(); + _entry.state = state; + } + _entry.ao = this._actionObjectManager.addObject(_uid, dummyAOI); + } + + // Force context (actual widget) in here, it's the last place it's available + _entry.ao._context = this._context; + _entry.ao.updateActionLinks(_links); + _entry.ao._index = _idx; + + // Overwrite some functions like "traversePath", "getNext" and + // "getPrevious" + var self = this; + + function getIndexAO (_idx) { + // Check whether the index is in the index map + if (typeof self._indexMap[_idx] !== "undefined" + && self._indexMap[_idx].uid) + { + return self._getRegisteredRowsEntry(self._indexMap[_idx].uid).ao; + } + + return null; + } + + function getElementRelatively (_step) { + var total = self._total || Object.keys(self._indexMap).length; + var max_index = Math.max.apply(Math,Object.keys(self._indexMap)); + // Get a reasonable number of iterations - not all + var count = Math.max(1,Math.min(self._total,50)); + var element = null; + var idx = _entry.idx; + while(element == null && count > 0 && max_index > 0) + { + count--; + element = getIndexAO(Math.max(0, + Math.min(max_index, idx += _step))); + } + return element; + } + + _entry.ao.getPrevious = function (_step) { + return getElementRelatively(-_step); + }; + + _entry.ao.getNext = function (_step) { + return getElementRelatively(_step); + }; + + _entry.ao.traversePath = function (_obj) { + // Get the start and the stop index + var s = Math.min(this._index, _obj._index); + var e = Math.max(this._index, _obj._index); + + var result = []; + + for (var i = s; i < e; i++) + { + var ao = getIndexAO(i); + if (ao) + { + result.push(ao); + } + } + + return result; + }; + } + + _updateState( _uid, _state) + { + var entry = this._getRegisteredRowsEntry(_uid); + + this._updateEntryState(entry, _state); + + return entry; + } + + _updateEntryState( _entry, _state) + { + + if (this._selectAll) + { + _state |= EGW_AO_STATE_SELECTED; + } + else if (this._invertSelection) + { + _state ^= EGW_AO_STATE_SELECTED; + } + + // Attach ao if not there, happens for rows loaded for selection, but + // not displayed yet + if(!_entry.ao && _entry.uid && this._actionObjectManager) + { + var _links = []; + for (var key in this._registeredRows) + { + if(this._registeredRows[key].ao && this._registeredRows[key].ao.actionLinks) + { + _links = this._registeredRows[key].ao.actionLinks; + break; + } + } + if(_links) + { + this._attachActionObjectInterface(_entry, null, _entry.uid); + this._attachActionObject(_entry, null, _entry.uid, _links, _entry.idx); + } + } + + // Update the state if it has changed + if ((_entry.aoi && _entry.aoi.getState() !== _state) || _entry.state != _state) + { + this._inUpdate = true; // Recursion prevention + + // Update the state of the action object + if (_entry.ao) + { + _entry.ao.setSelected(egwBitIsSet(_state, EGW_AO_STATE_SELECTED)); + _entry.ao.setFocused(egwBitIsSet(_state, EGW_AO_STATE_FOCUSED)); + } + + this._inUpdate = false; + + // Delete the element if state was set to "NORMAL" and there is + // no tr + if (_state === EGW_AO_STATE_NORMAL && !_entry.tr) + { + delete this._registeredRows[_entry.uid]; + } + } + + // Update the visual state + if (_entry.aoi && _entry.aoi.doSetState) + { + _entry.aoi.doSetState(_state); + } + + // Update the state of the entry + _entry.state = _state; + } + + _getRegisteredRowsEntry( _uid) + { + if (typeof this._registeredRows[_uid] === "undefined") + { + this._registeredRows[_uid] = { + "uid": _uid, + "idx": null, + "state": EGW_AO_STATE_NORMAL, + "tr": null, + "aoi": null, + "ao": null + }; + } + + return this._registeredRows[_uid]; + } + + _handleSelect( _uid, _entry, _shift, _ctrl) + { + // If not "_ctrl" is set, reset the selection + if (!_ctrl) + { + var top = this; + while(top._parent !== null) + { + top = top._parent; + } + top.resetSelection(); + this._actionObjectManager.setAllSelected(false); // needed for hirachical stuff + } + + // Mark the element that was clicked as selected + var entry = this._getRegisteredRowsEntry(_uid); + this.setSelected(_uid, + !_ctrl || !egwBitIsSet(entry.state, EGW_AO_STATE_SELECTED)); + + // Focus the element if shift is not pressed + if (!_shift) + { + this.setFocused(_uid, true); + } + else if (this._focusedEntry) + { + this._selectRange(this._focusedEntry.idx, _entry.idx); + } + + if(this.select_callback && typeof this.select_callback == "function") + { + this.select_callback.apply(this._context, arguments); + } + } + + _selectRange( _start, _stop) + { + // Contains ranges that are not currently in the index map and that have + // to be queried + var queryRanges = []; + + // Iterate over the given range and select the elements in the range + // from _start to _stop + var naStart = false; + var s = Math.min(_start, _stop); + var e = Math.max(_stop, _start); + var RANGE_MAX = 50; + var range_break = s + RANGE_MAX; + for (var i = s; i <= e; i++) + { + if (typeof this._indexMap[i] !== "undefined" && + this._indexMap[i].uid && egw.dataGetUIDdata(this._indexMap[i].uid)) + { + // Add the range to the "queryRanges" + if (naStart !== false) + { + queryRanges.push(et2_bounds(naStart, i - 1)); + naStart = false; + range_break += RANGE_MAX; + } + + // Select the element, unless flagged for exclusion + // Check for no_actions flag via data + var data = egw.dataGetUIDdata(this._indexMap[i].uid); + if(data && data.data && !data.data.no_actions) + { + this.setSelected(this._indexMap[i].uid, true); + } + } + else if (naStart === false) + { + naStart = i; + range_break = naStart + RANGE_MAX; + } + else if(i >= range_break) + { + queryRanges.push(et2_bounds(naStart ? naStart : s, i - 1)); + naStart = i; + range_break += RANGE_MAX; + } + } + + // Add the last range to the "queryRanges" + if (naStart !== false) + { + queryRanges.push(et2_bounds(naStart, i - 1)); + naStart = false; + } + + // Query all unknown ranges from the server + if(queryRanges.length > 0) + { + this._query_ranges(queryRanges); + } + } + + _query_ranges(queryRanges) + { + var that = this; + var record_count = 0; + var range_index = 0; + var range_count = queryRanges.length; + var cont = true; + var fetchPromise = new Promise(function (resolve) { + resolve(); + }); + // Found after dialog loads + var progressbar; + + var parent = et2_dialog._create_parent(); + var dialog = et2_createWidget("dialog", { + callback: + // Abort the long task if they canceled the data load + function() {cont = false}, + template: egw.webserverUrl+'/api/templates/default/long_task.xet', + message: egw.lang('Loading'), + title: egw.lang('please wait...'), + buttons: [{"button_id": et2_dialog.CANCEL_BUTTON,"text": egw.lang('cancel'),id: 'dialog[cancel]',image: 'cancel'}], + width: 300 + }, parent); + jQuery(dialog.template.DOMContainer).on('load', function() { + // Get access to template widgets + progressbar = dialog.template.widgetContainer.getWidgetById('progressbar'); + }); + + for (var i = 0; i < queryRanges.length; i++) + { + if(record_count + (queryRanges[i].bottom - queryRanges[i].top+1) > that.MAX_SELECTION) + { + egw.message(egw.lang('Too many rows selected.
Select all, or less than %1 rows', that.MAX_SELECTION)); + break; + } + else + { + record_count += (queryRanges[i].bottom - queryRanges[i].top+1); + fetchPromise = fetchPromise.then((function () + { + // Check for abort + if(!cont) return; + + return new Promise(function(resolve) { + that._queryRangeCallback.call(that._context, this, + function (_order) { + for (var j = 0; j < _order.length; j++) + { + // Check for no_actions flag via data since entry isn't there/available + var data = egw.dataGetUIDdata(_order[j]); + if(!data || data && data.data && !data.data.no_actions) + { + var entry = this._getRegisteredRowsEntry(_order[j]); + this._updateEntryState(entry, + egwSetBit(entry.state, EGW_AO_STATE_SELECTED, true)); + } + } + progressbar.set_value(100*(++range_index/range_count)); + resolve(); + }, that); + }.bind(this)); + }).bind(queryRanges[i])); + } + } + fetchPromise.finally(function() { + dialog.destroy(); + }); + } + +} + diff --git a/api/js/etemplate/et2_dataview_interfaces.js b/api/js/etemplate/et2_dataview_interfaces.js index 940fafeb1e..2ab93036d3 100644 --- a/api/js/etemplate/et2_dataview_interfaces.js +++ b/api/js/etemplate/et2_dataview_interfaces.js @@ -11,4 +11,16 @@ * @version $Id$ */ Object.defineProperty(exports, "__esModule", { value: true }); +var et2_dataviewIInvalidatable = "et2_dataview_IInvalidatable"; +function implements_et2_dataview_IInvalidatable(obj) { + return implements_methods(obj, ["invalidate"]); +} +var et2_dataview_IViewRange = "et2_dataview_IViewRange"; +function implements_et2_dataview_IViewRange(obj) { + return implements_methods(obj, ["setViewRange"]); +} +var et2_IDataProvider = "et2_IDataProvider"; +function implements_et2_IDataProvider(obj) { + return implements_methods(obj, ["dataFetch", "dataRegisterUID", "dataUnregisterUID"]); +} //# sourceMappingURL=et2_dataview_interfaces.js.map \ No newline at end of file diff --git a/api/js/etemplate/et2_dataview_interfaces.ts b/api/js/etemplate/et2_dataview_interfaces.ts index 4a8b0475a8..216e00a38a 100644 --- a/api/js/etemplate/et2_dataview_interfaces.ts +++ b/api/js/etemplate/et2_dataview_interfaces.ts @@ -16,16 +16,21 @@ export interface et2_dataview_IInvalidatable { - invalidate() - } - +var et2_dataviewIInvalidatable = "et2_dataview_IInvalidatable"; +function implements_et2_dataview_IInvalidatable(obj : et2_widget) +{ + return implements_methods(obj, ["invalidate"]); +} export interface et2_dataview_IViewRange { - setViewRange(_range) - +} +var et2_dataview_IViewRange = "et2_dataview_IViewRange"; +function implements_et2_dataview_IViewRange(obj : et2_widget) +{ + return implements_methods(obj, ["setViewRange"]); } /** @@ -96,5 +101,10 @@ export interface et2_IDataProvider dataUnregisterUID (_uid : string, _callback : Function, _context : object) } +var et2_IDataProvider = "et2_IDataProvider"; +function implements_et2_IDataProvider(obj : et2_widget) +{ + return implements_methods(obj, ["dataFetch", "dataRegisterUID", "dataUnregisterUID"]); +} diff --git a/api/js/etemplate/et2_dataview_model_columns.js b/api/js/etemplate/et2_dataview_model_columns.js index 42ddad6482..83a7c10757 100755 --- a/api/js/etemplate/et2_dataview_model_columns.js +++ b/api/js/etemplate/et2_dataview_model_columns.js @@ -15,13 +15,6 @@ Object.defineProperty(exports, "__esModule", { value: true }); et2_core_inheritance; et2_inheritance; */ -var ET2_COL_TYPE_DEFAULT = 0; -var ET2_COL_TYPE_NAME_ICON_FIXED = 1; -var ET2_COL_VISIBILITY_ALWAYS = 0; -var ET2_COL_VISIBILITY_VISIBLE = 1; -var ET2_COL_VISIBILITY_INVISIBLE = 2; -var ET2_COL_VISIBILITY_ALWAYS_NOSELECT = 3; -var ET2_COL_VISIBILITY_DISABLED = 4; /** * Class which stores the data of a single column. * @@ -35,14 +28,14 @@ var et2_dataview_column = /** @class */ (function () { /** * Defines the visibility state of this column. */ - this.visibility = ET2_COL_VISIBILITY_VISIBLE; + this.visibility = et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE; this.caption = ''; /** * Column type - Type of the column * * One of ET2_COL_TYPE_DEFAULT or ET2_COL_TYPE_NAME_ICON_FIXED */ - this.type = ET2_COL_TYPE_DEFAULT; + this.type = et2_dataview_column.ET2_COL_TYPE_DEFAULT; /** * Width of the column */ @@ -55,6 +48,23 @@ var et2_dataview_column = /** @class */ (function () { * Minimum width of the column, in pixels. Values below this are rejected. */ this.minWidth = 20; + this.id = _attrs.id; + if (typeof _attrs.visibility !== "undefined") { + this.visibility = _attrs.visibility; + } + this.caption = _attrs.caption; + if (typeof _attrs.type !== "undefined") { + this.type = _attrs.type; + } + if (typeof _attrs.width !== "undefined") { + this.set_width(_attrs.width); + } + if (typeof _attrs.maxWidth !== "undefined") { + this.maxWidth = _attrs.maxWidth; + } + if (typeof _attrs.minWidth !== "undefined") { + this.minWidth = _attrs.minWidth; + } } /** * Set the column width @@ -91,13 +101,13 @@ var et2_dataview_column = /** @class */ (function () { }; et2_dataview_column.prototype.set_visibility = function (_value) { // If visibility is always, don't turn it off - if (this.visibility == ET2_COL_VISIBILITY_ALWAYS || this.visibility == ET2_COL_VISIBILITY_ALWAYS_NOSELECT) + if (this.visibility == et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS || this.visibility == et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS_NOSELECT) return; if (_value === true) { - this.visibility = ET2_COL_VISIBILITY_VISIBLE; + this.visibility = et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE; } else if (_value === false) { - this.visibility = ET2_COL_VISIBILITY_INVISIBLE; + this.visibility = et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE; } else if (typeof _value == "number") { this.visibility = _value; @@ -106,6 +116,13 @@ var et2_dataview_column = /** @class */ (function () { egw().debug("warn", "Invalid visibility option for column: ", _value); } }; + et2_dataview_column.ET2_COL_TYPE_DEFAULT = 0; + et2_dataview_column.ET2_COL_TYPE_NAME_ICON_FIXED = 1; + et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS = 0; + et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE = 1; + et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE = 2; + et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS_NOSELECT = 3; + et2_dataview_column.ET2_COL_VISIBILITY_DISABLED = 4; et2_dataview_column._attributes = { "id": { "name": "ID", @@ -116,7 +133,7 @@ var et2_dataview_column = /** @class */ (function () { "visibility": { "name": "Visibility", "type": "integer", - "default": ET2_COL_VISIBILITY_VISIBLE, + "default": et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE, "description": "Defines the visibility state of this column." }, "caption": { @@ -128,7 +145,7 @@ var et2_dataview_column = /** @class */ (function () { "type": { "name": "Column type", "type": "integer", - "default": ET2_COL_TYPE_DEFAULT, + "default": et2_dataview_column.ET2_COL_TYPE_DEFAULT, "description": "Type of the column" }, "width": { @@ -162,14 +179,14 @@ var et2_dataview_columns = /** @class */ (function () { function et2_dataview_columns(_columnData) { // Initialize some variables this._totalWidth = 0; - this.totalFixed = 0; + this._totalFixed = 0; this.columnWidths = []; // Create the columns object this.columns = new Array(_columnData.length); for (var i = 0; i < _columnData.length; i++) { this.columns[i] = new et2_dataview_column(_columnData[i]); } - this.updated = true; + this._updated = true; } et2_dataview_columns.prototype.destroy = function () { // Free all column objects @@ -177,6 +194,12 @@ var et2_dataview_columns = /** @class */ (function () { this.columns[i] = null; } }; + et2_dataview_columns.prototype.updated = function () { + this._updated = true; + }; + et2_dataview_columns.prototype.columnCount = function () { + return this.columns.length; + }; Object.defineProperty(et2_dataview_columns.prototype, "totalWidth", { get: function () { return this._totalWidth; @@ -184,6 +207,13 @@ var et2_dataview_columns = /** @class */ (function () { enumerable: true, configurable: true }); + Object.defineProperty(et2_dataview_columns.prototype, "totalFixed", { + get: function () { + return this._totalFixed ? this._totalFixed : 0; + }, + enumerable: true, + configurable: true + }); /** * Set the total width of the header row * @@ -192,7 +222,7 @@ var et2_dataview_columns = /** @class */ (function () { et2_dataview_columns.prototype.setTotalWidth = function (_width) { if (_width != this._totalWidth && _width > 0) { this._totalWidth = _width; - this.updated = true; + this._updated = true; } }; /** @@ -225,9 +255,9 @@ var et2_dataview_columns = /** @class */ (function () { et2_dataview_columns.prototype.getColumnWidth = function (_idx) { if (this._totalWidth > 0 && _idx >= 0 && _idx < this.columns.length) { // Recalculate the column widths if something has changed. - if (this.updated) { + if (this._updated) { this._calculateWidths(); - this.updated = false; + this._updated = false; } // Return the calculated width for the column with the given index. return this.columnWidths[_idx]; @@ -244,8 +274,8 @@ var et2_dataview_columns = /** @class */ (function () { result.push({ "id": this.columns[i].id, "width": this.getColumnWidth(i), - "visible": this.columns[i].visibility !== ET2_COL_VISIBILITY_INVISIBLE && - this.columns[i].visibility !== ET2_COL_VISIBILITY_DISABLED + "visible": this.columns[i].visibility !== et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE && + this.columns[i].visibility !== et2_dataview_column.ET2_COL_VISIBILITY_DISABLED }); } return result; @@ -257,13 +287,13 @@ var et2_dataview_columns = /** @class */ (function () { et2_dataview_columns.prototype.getColumnVisibilitySet = function () { var result = {}; for (var i = 0; i < this.columns.length; i++) { - if (this.columns[i].visibility != ET2_COL_VISIBILITY_ALWAYS_NOSELECT) { + if (this.columns[i].visibility != et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS_NOSELECT) { result[this.columns[i].id] = { "caption": this.columns[i].caption, - "enabled": (this.columns[i].visibility != ET2_COL_VISIBILITY_ALWAYS) && - (this.columns[i].visibility != ET2_COL_VISIBILITY_DISABLED) && - (this.columns[i].type != ET2_COL_TYPE_NAME_ICON_FIXED), - "visible": this.columns[i].visibility != ET2_COL_VISIBILITY_INVISIBLE + "enabled": (this.columns[i].visibility != et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS) && + (this.columns[i].visibility != et2_dataview_column.ET2_COL_VISIBILITY_DISABLED) && + (this.columns[i].type != et2_dataview_column.ET2_COL_TYPE_NAME_ICON_FIXED), + "visible": this.columns[i].visibility != et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE }; } } @@ -278,11 +308,11 @@ var et2_dataview_columns = /** @class */ (function () { for (var k in _set) { var col = this.getColumnById(k); if (col) { - col.set_visibility(_set[k].visible ? ET2_COL_VISIBILITY_VISIBLE : - ET2_COL_VISIBILITY_INVISIBLE); + col.set_visibility(_set[k].visible ? et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE : + et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE); } } - this.updated = true; + this._updated = true; }; /* ---- PRIVATE FUNCTIONS ---- */ /** @@ -302,11 +332,11 @@ var et2_dataview_columns = /** @class */ (function () { // relative or fixed width var totalRelative = 0; var fixedCount = 0; - this.totalFixed = 0; + this._totalFixed = 0; for (var i = 0; i < this.columns.length; i++) { var col = this.columns[i]; - if (col.visibility !== ET2_COL_VISIBILITY_INVISIBLE && - col.visibility !== ET2_COL_VISIBILITY_DISABLED) { + if (col.visibility !== et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE && + col.visibility !== et2_dataview_column.ET2_COL_VISIBILITY_DISABLED) { // Some bounds sanity checking if (col.fixedWidth > tw || col.fixedWidth < 0) { col.fixedWidth = false; @@ -318,7 +348,7 @@ var et2_dataview_columns = /** @class */ (function () { totalRelative += col.relativeWidth; } else if (col.fixedWidth) { - this.totalFixed += col.fixedWidth; + this._totalFixed += col.fixedWidth; fixedCount++; } } @@ -329,8 +359,8 @@ var et2_dataview_columns = /** @class */ (function () { for (var i = 0; i < this.columns.length; i++) { var w = 0; var col = this.columns[i]; - if (col.visibility != ET2_COL_VISIBILITY_INVISIBLE && - col.visibility !== ET2_COL_VISIBILITY_DISABLED) { + if (col.visibility != et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE && + col.visibility !== et2_dataview_column.ET2_COL_VISIBILITY_DISABLED) { if (_larger[i]) { w = col.maxWidth; } @@ -341,7 +371,7 @@ var et2_dataview_columns = /** @class */ (function () { // Reset relative to an actual percentage (of 1.00) or // resizing eventually sends them to 0 col.relativeWidth = col.relativeWidth / totalRelative; - w = Math.round((tw - this.totalFixed) * col.relativeWidth); + w = Math.round((tw - this._totalFixed) * col.relativeWidth); } if (w > tw || (col.maxWidth && w > col.maxWidth)) { w = Math.min(tw - usedTotal, col.maxWidth); @@ -359,8 +389,8 @@ var et2_dataview_columns = /** @class */ (function () { var remaining_width = (usedTotal - tw); // Pick the first relative column and use it for (columnIndex = 0; columnIndex < this.columns.length; columnIndex++) { - if (this.columns[columnIndex].visibility === ET2_COL_VISIBILITY_INVISIBLE || - this.columns[columnIndex].visibility === ET2_COL_VISIBILITY_DISABLED || + if (this.columns[columnIndex].visibility === et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE || + this.columns[columnIndex].visibility === et2_dataview_column.ET2_COL_VISIBILITY_DISABLED || this.columnWidths[columnIndex] <= 0 || remaining_width > 0 && this.columnWidths[columnIndex] <= this.columns[columnIndex].minWidth) { continue; diff --git a/api/js/etemplate/et2_dataview_model_columns.ts b/api/js/etemplate/et2_dataview_model_columns.ts index 7828ea0072..6484f47fb8 100755 --- a/api/js/etemplate/et2_dataview_model_columns.ts +++ b/api/js/etemplate/et2_dataview_model_columns.ts @@ -16,15 +16,6 @@ */ -var ET2_COL_TYPE_DEFAULT = 0; -var ET2_COL_TYPE_NAME_ICON_FIXED = 1; - -var ET2_COL_VISIBILITY_ALWAYS = 0; -var ET2_COL_VISIBILITY_VISIBLE = 1; -var ET2_COL_VISIBILITY_INVISIBLE = 2; -var ET2_COL_VISIBILITY_ALWAYS_NOSELECT = 3; -var ET2_COL_VISIBILITY_DISABLED = 4; - /** * Class which stores the data of a single column. * @@ -33,6 +24,15 @@ var ET2_COL_VISIBILITY_DISABLED = 4; export class et2_dataview_column { + public static readonly ET2_COL_TYPE_DEFAULT = 0; + public static readonly ET2_COL_TYPE_NAME_ICON_FIXED = 1; + + public static readonly ET2_COL_VISIBILITY_ALWAYS = 0; + public static readonly ET2_COL_VISIBILITY_VISIBLE = 1; + public static readonly ET2_COL_VISIBILITY_INVISIBLE = 2; + public static readonly ET2_COL_VISIBILITY_ALWAYS_NOSELECT = 3; + public static readonly ET2_COL_VISIBILITY_DISABLED = 4; + static readonly _attributes: any = { "id": { "name": "ID", @@ -43,7 +43,7 @@ export class et2_dataview_column "visibility": { "name": "Visibility", "type": "integer", - "default": ET2_COL_VISIBILITY_VISIBLE, + "default": et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE, "description": "Defines the visibility state of this column." }, "caption": { @@ -55,7 +55,7 @@ export class et2_dataview_column "type": { "name": "Column type", "type": "integer", - "default": ET2_COL_TYPE_DEFAULT, + "default": et2_dataview_column.ET2_COL_TYPE_DEFAULT, "description": "Type of the column" }, "width": { @@ -89,7 +89,7 @@ export class et2_dataview_column /** * Defines the visibility state of this column. */ - public visibility: number = ET2_COL_VISIBILITY_VISIBLE; + public visibility: number = et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE; public caption: string = ''; @@ -98,7 +98,7 @@ export class et2_dataview_column * * One of ET2_COL_TYPE_DEFAULT or ET2_COL_TYPE_NAME_ICON_FIXED */ - public type: number = ET2_COL_TYPE_DEFAULT; + public type: number = et2_dataview_column.ET2_COL_TYPE_DEFAULT; /** * Width of the column @@ -120,6 +120,29 @@ export class et2_dataview_column */ constructor(_attrs) { + this.id = _attrs.id; + + if(typeof _attrs.visibility !== "undefined") + { + this.visibility = _attrs.visibility; + } + this.caption = _attrs.caption; + if(typeof _attrs.type !== "undefined") + { + this.type = _attrs.type; + } + if(typeof _attrs.width !== "undefined") + { + this.set_width( _attrs.width ); + } + if(typeof _attrs.maxWidth !== "undefined") + { + this.maxWidth = _attrs.maxWidth; + } + if(typeof _attrs.minWidth !== "undefined") + { + this.minWidth = _attrs.minWidth; + } } /** @@ -167,15 +190,15 @@ export class et2_dataview_column set_visibility(_value) { // If visibility is always, don't turn it off - if(this.visibility == ET2_COL_VISIBILITY_ALWAYS || this.visibility == ET2_COL_VISIBILITY_ALWAYS_NOSELECT) return; + if(this.visibility == et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS || this.visibility == et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS_NOSELECT) return; if(_value === true) { - this.visibility = ET2_COL_VISIBILITY_VISIBLE; + this.visibility = et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE; } else if (_value === false) { - this.visibility = ET2_COL_VISIBILITY_INVISIBLE; + this.visibility = et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE; } else if (typeof _value == "number") { @@ -197,16 +220,16 @@ export class et2_dataview_column export class et2_dataview_columns { private _totalWidth: number; - private totalFixed: number | boolean; + private _totalFixed: number | boolean; private columnWidths: any[]; private columns: et2_dataview_column[]; - private updated: boolean; + private _updated: boolean; constructor(_columnData) { // Initialize some variables this._totalWidth = 0; - this.totalFixed = 0; + this._totalFixed = 0; this.columnWidths = []; // Create the columns object @@ -216,7 +239,7 @@ export class et2_dataview_columns this.columns[i] = new et2_dataview_column(_columnData[i]); } - this.updated = true; + this._updated = true; } destroy() { @@ -227,9 +250,25 @@ export class et2_dataview_columns } } - get totalWidth(): number { + public updated() + { + this._updated = true; + } + + columnCount() : number + { + return this.columns.length; + } + + get totalWidth(): number + { return this._totalWidth; } + + get totalFixed(): number { + return this._totalFixed ? this._totalFixed : 0; + } + /** * Set the total width of the header row * @@ -240,7 +279,7 @@ export class et2_dataview_columns if (_width != this._totalWidth && _width > 0) { this._totalWidth = _width; - this.updated = true; + this._updated = true; } } @@ -282,10 +321,10 @@ export class et2_dataview_columns if (this._totalWidth > 0 && _idx >= 0 && _idx < this.columns.length) { // Recalculate the column widths if something has changed. - if (this.updated) + if (this._updated) { this._calculateWidths(); - this.updated = false; + this._updated = false; } // Return the calculated width for the column with the given index. @@ -308,8 +347,8 @@ export class et2_dataview_columns result.push({ "id": this.columns[i].id, "width": this.getColumnWidth(i), - "visible": this.columns[i].visibility !== ET2_COL_VISIBILITY_INVISIBLE && - this.columns[i].visibility !== ET2_COL_VISIBILITY_DISABLED + "visible": this.columns[i].visibility !== et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE && + this.columns[i].visibility !== et2_dataview_column.ET2_COL_VISIBILITY_DISABLED }); } @@ -327,14 +366,14 @@ export class et2_dataview_columns for (var i = 0; i < this.columns.length; i++) { - if (this.columns[i].visibility != ET2_COL_VISIBILITY_ALWAYS_NOSELECT) + if (this.columns[i].visibility != et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS_NOSELECT) { result[this.columns[i].id] = { "caption": this.columns[i].caption, - "enabled": (this.columns[i].visibility != ET2_COL_VISIBILITY_ALWAYS) && - (this.columns[i].visibility != ET2_COL_VISIBILITY_DISABLED) && - (this.columns[i].type != ET2_COL_TYPE_NAME_ICON_FIXED), - "visible": this.columns[i].visibility != ET2_COL_VISIBILITY_INVISIBLE + "enabled": (this.columns[i].visibility != et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS) && + (this.columns[i].visibility != et2_dataview_column.ET2_COL_VISIBILITY_DISABLED) && + (this.columns[i].type != et2_dataview_column.ET2_COL_TYPE_NAME_ICON_FIXED), + "visible": this.columns[i].visibility != et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE }; } } @@ -354,12 +393,12 @@ export class et2_dataview_columns var col = this.getColumnById(k); if (col) { - col.set_visibility(_set[k].visible ? ET2_COL_VISIBILITY_VISIBLE : - ET2_COL_VISIBILITY_INVISIBLE); + col.set_visibility(_set[k].visible ? et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE : + et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE); } } - this.updated = true; + this._updated = true; } /* ---- PRIVATE FUNCTIONS ---- */ @@ -385,13 +424,13 @@ export class et2_dataview_columns // relative or fixed width var totalRelative: number = 0; var fixedCount = 0; - this.totalFixed = 0; + this._totalFixed = 0; for (var i = 0; i < this.columns.length; i++) { var col = this.columns[i]; - if (col.visibility !== ET2_COL_VISIBILITY_INVISIBLE && - col.visibility !== ET2_COL_VISIBILITY_DISABLED + if (col.visibility !== et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE && + col.visibility !== et2_dataview_column.ET2_COL_VISIBILITY_DISABLED ) { // Some bounds sanity checking @@ -409,7 +448,7 @@ export class et2_dataview_columns } else if (col.fixedWidth) { - this.totalFixed += col.fixedWidth; + this._totalFixed += col.fixedWidth; fixedCount++; } } @@ -422,8 +461,8 @@ export class et2_dataview_columns { var w = 0; var col = this.columns[i]; - if (col.visibility != ET2_COL_VISIBILITY_INVISIBLE && - col.visibility !== ET2_COL_VISIBILITY_DISABLED + if (col.visibility != et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE && + col.visibility !== et2_dataview_column.ET2_COL_VISIBILITY_DISABLED ) { if (_larger[i]) @@ -439,7 +478,7 @@ export class et2_dataview_columns // Reset relative to an actual percentage (of 1.00) or // resizing eventually sends them to 0 col.relativeWidth = col.relativeWidth / totalRelative; - w = Math.round((tw-this.totalFixed) * col.relativeWidth); + w = Math.round((tw-this._totalFixed) * col.relativeWidth); } if (w > tw || (col.maxWidth && w > col.maxWidth)) { @@ -463,8 +502,8 @@ export class et2_dataview_columns // Pick the first relative column and use it for(columnIndex = 0; columnIndex < this.columns.length; columnIndex++) { - if(this.columns[columnIndex].visibility === ET2_COL_VISIBILITY_INVISIBLE || - this.columns[columnIndex].visibility === ET2_COL_VISIBILITY_DISABLED || + if(this.columns[columnIndex].visibility === et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE || + this.columns[columnIndex].visibility === et2_dataview_column.ET2_COL_VISIBILITY_DISABLED || this.columnWidths[columnIndex] <= 0 || remaining_width > 0 && this.columnWidths[columnIndex] <= this.columns[columnIndex].minWidth) { diff --git a/api/js/etemplate/et2_dataview_view_grid.js b/api/js/etemplate/et2_dataview_view_grid.js index 50af7cd4aa..9f5457b3d0 100644 --- a/api/js/etemplate/et2_dataview_view_grid.js +++ b/api/js/etemplate/et2_dataview_view_grid.js @@ -9,7 +9,16 @@ * @author Andreas Stöckel * @copyright Stylite 2011 * @version $Id$ - */ + * + +/*egw:uses + /vendor/bower-asset/jquery/dist/jquery.js; + et2_core_common; + + et2_dataview_interfaces; + et2_dataview_view_container; + et2_dataview_view_spacer; +*/ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || @@ -25,6 +34,7 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var et2_dataview_view_container_1 = require("./et2_dataview_view_container"); +var et2_dataview_view_spacer_1 = require("./et2_dataview_view_spacer"); var et2_dataview_grid = /** @class */ (function (_super_1) { __extends(et2_dataview_grid, _super_1); /** @@ -236,7 +246,7 @@ var et2_dataview_grid = /** @class */ (function (_super_1) { */ et2_dataview_grid.prototype.getVisibleIndexRange = function (_viewRange) { function getElemIdx(_elem, _px) { - if (_elem instanceof et2_dataview_spacer) { + if (_elem instanceof et2_dataview_view_spacer_1.et2_dataview_spacer) { return _elem.getIndex() + Math.floor((_px - _elem.getTop()) / this.getAverageHeight()); } @@ -281,7 +291,7 @@ var et2_dataview_grid = /** @class */ (function (_super_1) { var idxTop = false; var idxBottom = false; for (var i = 0; i < this._map.length; i++) { - if (!(this._map[i] instanceof et2_dataview_spacer)) { + if (!(this._map[i] instanceof et2_dataview_view_spacer_1.et2_dataview_spacer)) { var idx = this._map[i].getIndex(); if (idxTop === false) { idxTop = idx; @@ -439,7 +449,7 @@ var et2_dataview_grid = /** @class */ (function (_super_1) { // Get the map element var elem = this._map[mapIdx]; // Get the element range - if (elem instanceof et2_dataview_spacer) { + if (elem instanceof et2_dataview_view_spacer_1.et2_dataview_spacer) { var avg = this.getAverageHeight(); return et2_range(elem.getTop() + avg * (elem.getIndex() - _idx), avg); } @@ -557,9 +567,9 @@ var et2_dataview_grid = /** @class */ (function (_super_1) { var _loop_1 = function (i) { var container = this_1._map[i]; // Check which type the container object has - var isSpacer = container instanceof et2_dataview_spacer; + var isSpacer = container instanceof et2_dataview_view_spacer_1.et2_dataview_spacer; var hasIViewRange = !isSpacer - && container.implements(et2_dataview_IViewRange); + && implements_et2_dataview_IViewRange(container); // If the container has one of those special types, calculate the // view range and use that to update the view range of the element // or to request new elements for the spacer @@ -699,7 +709,7 @@ var et2_dataview_grid = /** @class */ (function (_super_1) { // Add the new element after the old container _container.insertIntoTree(_mapElem.getLastNode()); // Create a new spacer and add it after the newly inserted container - var newSpacer = new et2_dataview_spacer(this, this._rowProvider); + var newSpacer = new et2_dataview_view_spacer_1.et2_dataview_spacer(this, this._rowProvider); newSpacer.setCount(cntBottom, _avg); newSpacer.setIndex(_index + 1); newSpacer.insertIntoTree(_container.getLastNode()); @@ -726,7 +736,7 @@ var et2_dataview_grid = /** @class */ (function (_super_1) { // Append the new container to the current container and then // destroy the old container _container.insertIntoTree(_mapElem.getLastNode()); - _mapElem.free(); + _mapElem.destroy(); this._map.splice(_mapIndex, 1, _container); } }; @@ -743,7 +753,7 @@ var et2_dataview_grid = /** @class */ (function (_super_1) { // Update the index of the element this._map[i].setIndex(_newIndex++); // We've found a spacer -- decrement its element count and abort - if (this._map[i] instanceof et2_dataview_spacer) { + if (this._map[i] instanceof et2_dataview_view_spacer_1.et2_dataview_spacer) { this._decrementSpacerCount(i, _avg); return; } @@ -751,7 +761,7 @@ var et2_dataview_grid = /** @class */ (function (_super_1) { // We've found no spacer so far, remove the last element from the map in // order to obtain the "totalCount" (especially the last element is no // spacer, so the following code cannot remove a spacer) - this._map.pop().free(); + this._map.pop().destroy(); }; /** * Inserts the given container at the given index. @@ -760,7 +770,7 @@ var et2_dataview_grid = /** @class */ (function (_super_1) { // Check whether the given element at the map index is a spacer. If // yes, we have to split the spacer at that position. var mapElem = this._map[_mapIndex]; - if (mapElem instanceof et2_dataview_spacer) { + if (mapElem instanceof et2_dataview_view_spacer_1.et2_dataview_spacer) { this._insertContainerAtSpacer(_index, _mapIndex, mapElem, _container, _avg); } else { @@ -786,16 +796,16 @@ var et2_dataview_grid = /** @class */ (function (_super_1) { var spacerAbove = null; var spacerBelow = null; if (_mapIndex > 0 - && this._map[_mapIndex - 1] instanceof et2_dataview_spacer) { + && this._map[_mapIndex - 1] instanceof et2_dataview_view_spacer_1.et2_dataview_spacer) { spacerAbove = this._map[_mapIndex - 1]; } if (_mapIndex < this._map.length - 1 - && this._map[_mapIndex + 1] instanceof et2_dataview_spacer) { + && this._map[_mapIndex + 1] instanceof et2_dataview_view_spacer_1.et2_dataview_spacer) { spacerBelow = this._map[_mapIndex + 1]; } if (!spacerAbove && !spacerBelow) { // No spacer can be extended -- simply create a new one - spacer = new et2_dataview_spacer(this, this._rowProvider); + spacer = new et2_dataview_view_spacer_1.et2_dataview_spacer(this, this._rowProvider); spacer.setIndex(_mapElem.getIndex()); spacer.addAvgHeight(_mapElem.getHeight()); spacer.setCount(1, _mapElem.getHeight()); @@ -818,7 +828,7 @@ var et2_dataview_grid = /** @class */ (function (_super_1) { spacerAbove.addAvgHeight(_mapElem.getHeight()); spacerAbove.setCount(totalCount, newAvg); // Delete the lower spacer and remove it from the mapping - spacerBelow.free(); + spacerBelow.destroy(); this._map.splice(_mapIndex + 1, 1); } else { @@ -840,8 +850,8 @@ var et2_dataview_grid = /** @class */ (function (_super_1) { */ et2_dataview_grid.prototype._consolidateSpacers = function (_mapIndex) { if (_mapIndex < this._map.length - 1 - && this._map[_mapIndex] instanceof et2_dataview_spacer - && this._map[_mapIndex + 1] instanceof et2_dataview_spacer) { + && this._map[_mapIndex] instanceof et2_dataview_view_spacer_1.et2_dataview_spacer + && this._map[_mapIndex + 1] instanceof et2_dataview_view_spacer_1.et2_dataview_spacer) { var spacerAbove = this._map[_mapIndex]; var spacerBelow = this._map[_mapIndex + 1]; // Calculate the new height/count of both containers @@ -851,7 +861,7 @@ var et2_dataview_grid = /** @class */ (function (_super_1) { // Extend the new spacer spacerAbove.setCount(totalCount, newAvg); // Delete the old spacer - spacerBelow.free(); + spacerBelow.destroy(); this._map.splice(_mapIndex + 1, 1); } }; @@ -874,7 +884,7 @@ var et2_dataview_grid = /** @class */ (function (_super_1) { this._map[_mapIndex].setCount(cnt, _avg); } else { - this._map[_mapIndex].free(); + this._map[_mapIndex].destroy(); this._map.splice(_mapIndex, 1); } }; @@ -891,7 +901,7 @@ var et2_dataview_grid = /** @class */ (function (_super_1) { var removedElement = false; // Check whether the map element is a spacer -- if yes, we have to do // some special treatment - if (mapElem instanceof et2_dataview_spacer) { + if (mapElem instanceof et2_dataview_view_spacer_1.et2_dataview_spacer) { // Do nothing if the "_replaceWithSpacer" flag is true as the // element already is a spacer if (!_replaceWithSpacer) { @@ -907,7 +917,7 @@ var et2_dataview_grid = /** @class */ (function (_super_1) { removedElement = true; } // Remove the complete (current) container, decrement the _mapIndex - this._map[_mapIndex].free(); + this._map[_mapIndex].destroy(); this._map.splice(_mapIndex, 1); _mapIndex--; // The delete operation may have created two joining spacers -- this @@ -940,9 +950,9 @@ var et2_dataview_grid = /** @class */ (function (_super_1) { var spacer = null; var lastIndex = this._map.length - 1; if (this._map.length === 0 || - !(this._map[lastIndex] instanceof et2_dataview_spacer)) { + !(this._map[lastIndex] instanceof et2_dataview_view_spacer_1.et2_dataview_spacer)) { // Create a new spacer - spacer = new et2_dataview_spacer(this, this._rowProvider); + spacer = new et2_dataview_view_spacer_1.et2_dataview_spacer(this, this._rowProvider); // Insert the spacer -- we have a special case if there currently is // no element inside the mapping if (this._map.length === 0) { @@ -985,7 +995,7 @@ var et2_dataview_grid = /** @class */ (function (_super_1) { while (_delta > 0 && this._map.length > 0) { var cont = this._map[this._map.length - 1]; // Remove as many containers as possible from spacers - if (cont instanceof et2_dataview_spacer) { + if (cont instanceof et2_dataview_view_spacer_1.et2_dataview_spacer) { var diff = cont.getCount() - _delta; if (diff > 0) { // We're done as the spacer still has entries left @@ -1003,7 +1013,7 @@ var et2_dataview_grid = /** @class */ (function (_super_1) { _delta -= 1; } // Destroy the container if there are no rows left - cont.free(); + cont.destroy(); this._map.pop(); } // Check whether _delta is really zero diff --git a/api/js/etemplate/et2_dataview_view_grid.ts b/api/js/etemplate/et2_dataview_view_grid.ts index 5e39b42bfb..efb22c6149 100644 --- a/api/js/etemplate/et2_dataview_view_grid.ts +++ b/api/js/etemplate/et2_dataview_view_grid.ts @@ -8,7 +8,7 @@ * @author Andreas Stöckel * @copyright Stylite 2011 * @version $Id$ - */ + * /*egw:uses /vendor/bower-asset/jquery/dist/jquery.js; @@ -21,10 +21,11 @@ import {et2_dataview_IViewRange} from "./et2_dataview_interfaces"; import {et2_dataview_container} from "./et2_dataview_view_container"; +import {et2_dataview_spacer} from "./et2_dataview_view_spacer"; +import {et2_dataview_rowProvider} from "./et2_dataview_view_rowProvider"; export class et2_dataview_grid extends et2_dataview_container implements et2_dataview_IViewRange { - /** * Determines how many pixels the view range of the gridview is extended inside * the scroll callback. @@ -794,7 +795,7 @@ export class et2_dataview_grid extends et2_dataview_container implements et2_dat // Check which type the container object has const isSpacer = container instanceof et2_dataview_spacer; const hasIViewRange = !isSpacer - && container.implements(et2_dataview_IViewRange); + && implements_et2_dataview_IViewRange(container); // If the container has one of those special types, calculate the // view range and use that to update the view range of the element @@ -1020,7 +1021,7 @@ export class et2_dataview_grid extends et2_dataview_container implements et2_dat // Append the new container to the current container and then // destroy the old container _container.insertIntoTree(_mapElem.getLastNode()); - _mapElem.free(); + _mapElem.destroy(); this._map.splice(_mapIndex, 1, _container); } @@ -1053,7 +1054,7 @@ export class et2_dataview_grid extends et2_dataview_container implements et2_dat // We've found no spacer so far, remove the last element from the map in // order to obtain the "totalCount" (especially the last element is no // spacer, so the following code cannot remove a spacer) - this._map.pop().free(); + this._map.pop().destroy(); } /** @@ -1139,7 +1140,7 @@ export class et2_dataview_grid extends et2_dataview_container implements et2_dat spacerAbove.setCount(totalCount, newAvg); // Delete the lower spacer and remove it from the mapping - spacerBelow.free(); + spacerBelow.destroy(); this._map.splice(_mapIndex + 1, 1); } else @@ -1181,7 +1182,7 @@ export class et2_dataview_grid extends et2_dataview_container implements et2_dat spacerAbove.setCount(totalCount, newAvg); // Delete the old spacer - spacerBelow.free(); + spacerBelow.destroy(); this._map.splice(_mapIndex + 1, 1); } } @@ -1208,7 +1209,7 @@ export class et2_dataview_grid extends et2_dataview_container implements et2_dat } else { - this._map[_mapIndex].free(); + this._map[_mapIndex].destroy(); this._map.splice(_mapIndex, 1); } } @@ -1252,7 +1253,7 @@ export class et2_dataview_grid extends et2_dataview_container implements et2_dat } // Remove the complete (current) container, decrement the _mapIndex - this._map[_mapIndex].free(); + this._map[_mapIndex].destroy(); this._map.splice(_mapIndex, 1); _mapIndex--; @@ -1374,7 +1375,7 @@ export class et2_dataview_grid extends et2_dataview_container implements et2_dat } // Destroy the container if there are no rows left - cont.free(); + cont.destroy(); this._map.pop(); } diff --git a/api/js/etemplate/et2_dataview_view_row.js b/api/js/etemplate/et2_dataview_view_row.js index adfb69f670..d669dfed2c 100644 --- a/api/js/etemplate/et2_dataview_view_row.js +++ b/api/js/etemplate/et2_dataview_view_row.js @@ -1,3 +1,4 @@ +"use strict"; /** * EGroupware eTemplate2 - dataview * @@ -8,188 +9,152 @@ * @author Andreas Stöckel * @copyright Stylite 2011-2012 * @version $Id$ - */ + * /*egw:uses - egw_action.egw_action; + egw_action.egw_action; - et2_dataview_view_container; + et2_dataview_view_container; */ - -/** - * @augments et2_dataview_container - */ -var et2_dataview_row = (function(){ "use strict"; return et2_dataview_container.extend(et2_dataview_IViewRange, -{ - /** - * Creates the row container. Use the "setRow" function to load the actual - * row content. - * - * @param _parent is the row parent container. - * @memberOf et2_dataview_row - */ - init: function(_parent) { - // Call the inherited constructor - this._super(_parent); - - // Create the outer "tr" tag and append it to the container - this.tr = jQuery(document.createElement("tr")); - this.appendNode(this.tr); - - // Grid row which gets expanded when clicking on the corresponding - // button - this.expansionContainer = null; - this.expansionVisible = false; - - // Toggle button which is used to show and hide the expansionContainer - this.expansionButton = null; - }, - - destroy: function () { - - if (this.expansionContainer != null) - { - this.expansionContainer.free(); - } - - this._super(); - }, - - clear: function() { - this.tr.empty(); - }, - - makeExpandable: function (_expandable, _callback, _context) { - if (_expandable) - { - // Create the tr and the button if this has not been done yet - if (!this.expansionButton) - { - this.expansionButton = jQuery(document.createElement("span")); - this.expansionButton.addClass("arrow closed"); - } - - // Update context - var self = this; - this.expansionButton.off("click").on("click", function (e) { - self._handleExpansionButtonClick(_callback, _context); - e.stopImmediatePropagation(); - }); - - jQuery("td:first", this.tr).prepend(this.expansionButton); - } - else - { - // If the row is made non-expandable, remove the created DOM-Nodes - if (this.expansionButton) - { - this.expansionButton.remove(); - } - - if (this.expansionContainer) - { - this.expansionContainer.free(); - } - - this.expansionButton = null; - this.expansionContainer = null; - } - }, - - removeFromTree: function () { - if (this.expansionContainer) - { - this.expansionContainer.removeFromTree(); - } - - this.expansionContainer = null; - this.expansionButton = null; - - this._super(); - }, - - getDOMNode: function () { - return this.tr[0]; - }, - - getJNode: function () { - return this.tr; - }, - - getHeight: function () { - var h = this._super(); - - if (this.expansionContainer && this.expansionVisible) - { - h += this.expansionContainer.getHeight(); - } - - return h; - }, - - getAvgHeightData: function() { - // Only take the height of the own tr into account - //var oldVisible = this.expansionVisible; - this.expansionVisible = false; - - var res = { - "avgHeight": this.getHeight(), - "avgCount": 1 - }; - - this.expansionVisible = true; - - return res; - }, - - - /** -- PRIVATE FUNCTIONS -- **/ - - - _handleExpansionButtonClick: function (_callback, _context) { - // Create the "expansionContainer" if it does not exist yet - if (!this.expansionContainer) - { - this.expansionContainer = _callback.call(_context); - this.expansionContainer.insertIntoTree(this.tr); - this.expansionVisible = false; - } - - // Toggle the visibility of the expansion tr - this.expansionVisible = !this.expansionVisible; - jQuery(this.expansionContainer._nodes[0]).toggle(this.expansionVisible); - - // Set the class of the arrow - if (this.expansionVisible) - { - this.expansionButton.addClass("opened"); - this.expansionButton.removeClass("closed"); - } - else - { - this.expansionButton.addClass("closed"); - this.expansionButton.removeClass("opened"); - } - - this.invalidate(); - }, - - - /** -- Implementation of et2_dataview_IViewRange -- **/ - - - setViewRange: function (_range) { - if (this.expansionContainer && this.expansionVisible - && this.expansionContainer.implements(et2_dataview_IViewRange)) - { - // Substract the height of the own row from the container - var oh = jQuery(this._nodes[0]).height(); - _range.top -= oh; - - // Proxy the setViewRange call to the expansion container - this.expansionContainer.setViewRange(_range); - } - } - -});}).call(this); - +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var et2_dataview_row = /** @class */ (function (_super) { + __extends(et2_dataview_row, _super); + /** + * Creates the row container. Use the "setRow" function to load the actual + * row content. + * + * @param _parent is the row parent container. + */ + function et2_dataview_row(_parent) { + var _this = + // Call the inherited constructor + _super.call(this, _parent) || this; + // Create the outer "tr" tag and append it to the container + _this.tr = jQuery(document.createElement("tr")); + _this.appendNode(_this.tr); + // Grid row which gets expanded when clicking on the corresponding + // button + _this.expansionContainer = null; + _this.expansionVisible = false; + // Toggle button which is used to show and hide the expansionContainer + _this.expansionButton = null; + return _this; + } + et2_dataview_row.prototype.destroy = function () { + if (this.expansionContainer != null) { + this.expansionContainer.destroy(); + } + _super.prototype.destroy.call(this); + }; + et2_dataview_row.prototype.clear = function () { + this.tr.empty(); + }; + et2_dataview_row.prototype.makeExpandable = function (_expandable, _callback, _context) { + if (_expandable) { + // Create the tr and the button if this has not been done yet + if (!this.expansionButton) { + this.expansionButton = jQuery(document.createElement("span")); + this.expansionButton.addClass("arrow closed"); + } + // Update context + var self = this; + this.expansionButton.off("click").on("click", function (e) { + self._handleExpansionButtonClick(_callback, _context); + e.stopImmediatePropagation(); + }); + jQuery("td:first", this.tr).prepend(this.expansionButton); + } + else { + // If the row is made non-expandable, remove the created DOM-Nodes + if (this.expansionButton) { + this.expansionButton.remove(); + } + if (this.expansionContainer) { + this.expansionContainer.destroy(); + } + this.expansionButton = null; + this.expansionContainer = null; + } + }; + et2_dataview_row.prototype.removeFromTree = function () { + if (this.expansionContainer) { + this.expansionContainer.removeFromTree(); + } + this.expansionContainer = null; + this.expansionButton = null; + _super.prototype.removeFromTree.call(this); + }; + et2_dataview_row.prototype.getDOMNode = function () { + return this.tr[0]; + }; + et2_dataview_row.prototype.getJNode = function () { + return this.tr; + }; + et2_dataview_row.prototype.getHeight = function () { + var h = _super.prototype.getHeight.call(this); + if (this.expansionContainer && this.expansionVisible) { + h += this.expansionContainer.getHeight(); + } + return h; + }; + et2_dataview_row.prototype.getAvgHeightData = function () { + // Only take the height of the own tr into account + //var oldVisible = this.expansionVisible; + this.expansionVisible = false; + var res = { + "avgHeight": this.getHeight(), + "avgCount": 1 + }; + this.expansionVisible = true; + return res; + }; + /** -- PRIVATE FUNCTIONS -- **/ + et2_dataview_row.prototype._handleExpansionButtonClick = function (_callback, _context) { + // Create the "expansionContainer" if it does not exist yet + if (!this.expansionContainer) { + this.expansionContainer = _callback.call(_context); + this.expansionContainer.insertIntoTree(this.tr); + this.expansionVisible = false; + } + // Toggle the visibility of the expansion tr + this.expansionVisible = !this.expansionVisible; + jQuery(this.expansionContainer._nodes[0]).toggle(this.expansionVisible); + // Set the class of the arrow + if (this.expansionVisible) { + this.expansionButton.addClass("opened"); + this.expansionButton.removeClass("closed"); + } + else { + this.expansionButton.addClass("closed"); + this.expansionButton.removeClass("opened"); + } + this.invalidate(); + }; + /** -- Implementation of et2_dataview_IViewRange -- **/ + et2_dataview_row.prototype.setViewRange = function (_range) { + if (this.expansionContainer && this.expansionVisible + && this.expansionContainer.implements(et2_dataview_IViewRange)) { + // Substract the height of the own row from the container + var oh = jQuery(this._nodes[0]).height(); + _range.top -= oh; + // Proxy the setViewRange call to the expansion container + this.expansionContainer.setViewRange(_range); + } + }; + return et2_dataview_row; +}(et2_dataview_container)); +exports.et2_dataview_row = et2_dataview_row; +//# sourceMappingURL=et2_dataview_view_row.js.map \ No newline at end of file diff --git a/api/js/etemplate/et2_dataview_view_row.ts b/api/js/etemplate/et2_dataview_view_row.ts new file mode 100644 index 0000000000..185457f047 --- /dev/null +++ b/api/js/etemplate/et2_dataview_view_row.ts @@ -0,0 +1,203 @@ +/** + * EGroupware eTemplate2 - dataview + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package etemplate + * @subpackage dataview + * @link http://www.egroupware.org + * @author Andreas Stöckel + * @copyright Stylite 2011-2012 + * @version $Id$ + * + +/*egw:uses + egw_action.egw_action; + + et2_dataview_view_container; +*/ + +import {et2_dataview_IViewRange} from "./et2_dataview_interfaces"; + +export class et2_dataview_row extends et2_dataview_container implements et2_dataview_IViewRange +{ + /** + * Creates the row container. Use the "setRow" function to load the actual + * row content. + * + * @param _parent is the row parent container. + */ + constructor( _parent) + { + // Call the inherited constructor + super(_parent); + + // Create the outer "tr" tag and append it to the container + this.tr = jQuery(document.createElement("tr")); + this.appendNode(this.tr); + + // Grid row which gets expanded when clicking on the corresponding + // button + this.expansionContainer = null; + this.expansionVisible = false; + + // Toggle button which is used to show and hide the expansionContainer + this.expansionButton = null; + } + + destroy( ) + { + + if (this.expansionContainer != null) + { + this.expansionContainer.destroy(); + } + + super.destroy(); + } + + clear( ) + { + this.tr.empty(); + } + + makeExpandable( _expandable, _callback, _context) + { + if (_expandable) + { + // Create the tr and the button if this has not been done yet + if (!this.expansionButton) + { + this.expansionButton = jQuery(document.createElement("span")); + this.expansionButton.addClass("arrow closed"); + } + + // Update context + var self = this; + this.expansionButton.off("click").on("click", function (e) { + self._handleExpansionButtonClick(_callback, _context); + e.stopImmediatePropagation(); + }); + + jQuery("td:first", this.tr).prepend(this.expansionButton); + } + else + { + // If the row is made non-expandable, remove the created DOM-Nodes + if (this.expansionButton) + { + this.expansionButton.remove(); + } + + if (this.expansionContainer) + { + this.expansionContainer.destroy(); + } + + this.expansionButton = null; + this.expansionContainer = null; + } + } + + removeFromTree( ) + { + if (this.expansionContainer) + { + this.expansionContainer.removeFromTree(); + } + + this.expansionContainer = null; + this.expansionButton = null; + + super.removeFromTree(); + } + + getDOMNode( ) + { + return this.tr[0]; + } + + getJNode( ) + { + return this.tr; + } + + getHeight( ) + { + var h = super.getHeight(); + + if (this.expansionContainer && this.expansionVisible) + { + h += this.expansionContainer.getHeight(); + } + + return h; + } + + getAvgHeightData( ) + { + // Only take the height of the own tr into account + //var oldVisible = this.expansionVisible; + this.expansionVisible = false; + + var res = { + "avgHeight": this.getHeight(), + "avgCount": 1 + }; + + this.expansionVisible = true; + + return res; + } + + + /** -- PRIVATE FUNCTIONS -- **/ + + + _handleExpansionButtonClick( _callback, _context) + { + // Create the "expansionContainer" if it does not exist yet + if (!this.expansionContainer) + { + this.expansionContainer = _callback.call(_context); + this.expansionContainer.insertIntoTree(this.tr); + this.expansionVisible = false; + } + + // Toggle the visibility of the expansion tr + this.expansionVisible = !this.expansionVisible; + jQuery(this.expansionContainer._nodes[0]).toggle(this.expansionVisible); + + // Set the class of the arrow + if (this.expansionVisible) + { + this.expansionButton.addClass("opened"); + this.expansionButton.removeClass("closed"); + } + else + { + this.expansionButton.addClass("closed"); + this.expansionButton.removeClass("opened"); + } + + this.invalidate(); + } + + + /** -- Implementation of et2_dataview_IViewRange -- **/ + + + setViewRange( _range) + { + if (this.expansionContainer && this.expansionVisible + && this.expansionContainer.implements(et2_dataview_IViewRange)) + { + // Substract the height of the own row from the container + var oh = jQuery(this._nodes[0]).height(); + _range.top -= oh; + + // Proxy the setViewRange call to the expansion container + this.expansionContainer.setViewRange(_range); + } + } + +} \ No newline at end of file diff --git a/api/js/etemplate/et2_dataview_view_rowProvider.js b/api/js/etemplate/et2_dataview_view_rowProvider.js index e50e1336b7..d4d369717a 100644 --- a/api/js/etemplate/et2_dataview_view_rowProvider.js +++ b/api/js/etemplate/et2_dataview_view_rowProvider.js @@ -1,3 +1,4 @@ +"use strict"; /** * EGroupware eTemplate2 - Class which contains a factory method for rows * @@ -9,121 +10,96 @@ * @copyright Stylite 2011 * @version $Id$ */ - +Object.defineProperty(exports, "__esModule", { value: true }); /*egw:uses - /vendor/bower-asset/jquery/dist/jquery.js; - et2_core_inheritance; - et2_core_interfaces; - et2_core_arrayMgr; - et2_core_widget; + /vendor/bower-asset/jquery/dist/jquery.js; + et2_core_inheritance; + et2_core_interfaces; + et2_core_arrayMgr; + et2_core_widget; */ - /** * The row provider contains prototypes (full clonable dom-trees) * for all registered row types. - * - * @augments Class */ -var et2_dataview_rowProvider = (function(){ "use strict"; return Class.extend( -{ - /** - * - * @param _outerId - * @param _columnIds - * @memberOf et2_dataview_rowProvider - */ - init: function(_outerId, _columnIds) { - // Copy the given parameters - this._outerId = _outerId; - this._columnIds = _columnIds; - this._prototypes = {}; - - this._template = null; - this._mgrs = null; - this._rootWidget = null; - - // Create the default row "prototypes" - this._createFullRowPrototype(); - this._createDefaultPrototype(); - this._createEmptyPrototype(); - this._createLoadingPrototype(); - }, - - getColumnCount: function() { - return this._columnIds.length; - }, - - /** - * Returns a clone of the prototype with the given name. If the generator - * callback function is given, this function is called if the prototype - * does not yet registered. - * - * @param {string} _name - * @param {function} _generator - * @param {object} _context - */ - getPrototype: function(_name, _generator, _context) { - if (typeof this._prototypes[_name] == "undefined") - { - if (typeof _generator != "undefined") - { - this._prototypes[_name] = _generator.call(_context, this._outerId, - this._columnIds); - } - else - { - return null; - } - } - - return this._prototypes[_name].clone(); - }, - - - /* ---- PRIVATE FUNCTIONS ---- */ - - - _createFullRowPrototype: function() { - var tr = jQuery(document.createElement("tr")); - var td = jQuery(document.createElement("td")) - .addClass(this._outerId + "_td_fullRow") - .attr("colspan", this._columnIds.length) - .appendTo(tr); - var div = jQuery(document.createElement("div")) - .addClass(this._outerId + "_div_fullRow") - .appendTo(td); - - this._prototypes["fullRow"] = tr; - }, - - _createDefaultPrototype: function() { - var tr = jQuery(document.createElement("tr")); - - // Append a td for each column - for (var i = 0; i < this._columnIds.length; i++) - { - var td = jQuery(document.createElement("td")) - .addClass(this._outerId + "_td_" + this._columnIds[i]) - .appendTo(tr); - var div = jQuery(document.createElement("div")) - .addClass(this._outerId + "_div_" + this._columnIds[i]) - .addClass("innerContainer") - .appendTo(td); - } - - this._prototypes["default"] = tr; - }, - - _createEmptyPrototype: function() { - this._prototypes["empty"] = jQuery(document.createElement("tr")); - }, - - _createLoadingPrototype: function() { - var fullRow = this.getPrototype("fullRow"); - jQuery("div", fullRow).addClass("loading"); - - this._prototypes["loading"] = fullRow; - } - -});}).call(this); - +var et2_dataview_rowProvider = /** @class */ (function () { + /** + * + * @param _outerId + * @param _columnIds + */ + function et2_dataview_rowProvider(_outerId, _columnIds) { + // Copy the given parameters + this._outerId = _outerId; + this._columnIds = _columnIds; + this._prototypes = {}; + this._template = null; + this._mgrs = null; + this._rootWidget = null; + // Create the default row "prototypes" + this._createFullRowPrototype(); + this._createDefaultPrototype(); + this._createEmptyPrototype(); + this._createLoadingPrototype(); + } + et2_dataview_rowProvider.prototype.getColumnCount = function () { + return this._columnIds.length; + }; + /** + * Returns a clone of the prototype with the given name. If the generator + * callback function is given, this function is called if the prototype + * does not yet registered. + * + * @param {string} _name + * @param {function} _generator + * @param {object} _context + */ + et2_dataview_rowProvider.prototype.getPrototype = function (_name, _generator, _context) { + if (typeof this._prototypes[_name] == "undefined") { + if (typeof _generator != "undefined") { + this._prototypes[_name] = _generator.call(_context, this._outerId, this._columnIds); + } + else { + return null; + } + } + return this._prototypes[_name].clone(); + }; + /* ---- PRIVATE FUNCTIONS ---- */ + et2_dataview_rowProvider.prototype._createFullRowPrototype = function () { + var tr = jQuery(document.createElement("tr")); + var td = jQuery(document.createElement("td")) + .addClass(this._outerId + "_td_fullRow") + .attr("colspan", this._columnIds.length) + .appendTo(tr); + var div = jQuery(document.createElement("div")) + .addClass(this._outerId + "_div_fullRow") + .appendTo(td); + this._prototypes["fullRow"] = tr; + }; + et2_dataview_rowProvider.prototype._createDefaultPrototype = function () { + var tr = jQuery(document.createElement("tr")); + // Append a td for each column + for (var i = 0; i < this._columnIds.length; i++) { + var td = jQuery(document.createElement("td")) + .addClass(this._outerId + "_td_" + this._columnIds[i]) + .appendTo(tr); + var div = jQuery(document.createElement("div")) + .addClass(this._outerId + "_div_" + this._columnIds[i]) + .addClass("innerContainer") + .appendTo(td); + } + this._prototypes["default"] = tr; + }; + et2_dataview_rowProvider.prototype._createEmptyPrototype = function () { + this._prototypes["empty"] = jQuery(document.createElement("tr")); + }; + et2_dataview_rowProvider.prototype._createLoadingPrototype = function () { + var fullRow = this.getPrototype("fullRow"); + jQuery("div", fullRow).addClass("loading"); + this._prototypes["loading"] = fullRow; + }; + return et2_dataview_rowProvider; +}()); +exports.et2_dataview_rowProvider = et2_dataview_rowProvider; +//# sourceMappingURL=et2_dataview_view_rowProvider.js.map \ No newline at end of file diff --git a/api/js/etemplate/et2_dataview_view_rowProvider.ts b/api/js/etemplate/et2_dataview_view_rowProvider.ts new file mode 100644 index 0000000000..0e8160189e --- /dev/null +++ b/api/js/etemplate/et2_dataview_view_rowProvider.ts @@ -0,0 +1,139 @@ +/** + * EGroupware eTemplate2 - Class which contains a factory method for rows + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package etemplate + * @subpackage dataview + * @link http://www.egroupware.org + * @author Andreas Stöckel + * @copyright Stylite 2011 + * @version $Id$ + */ + +/*egw:uses + /vendor/bower-asset/jquery/dist/jquery.js; + et2_core_inheritance; + et2_core_interfaces; + et2_core_arrayMgr; + et2_core_widget; +*/ + +/** + * The row provider contains prototypes (full clonable dom-trees) + * for all registered row types. + */ +export class et2_dataview_rowProvider +{ + private _outerId: any; + private _columnIds: any; + private _prototypes: {}; + private _template: null; + private _mgrs: null; + private _rootWidget: null; + /** + * + * @param _outerId + * @param _columnIds + */ + constructor( _outerId, _columnIds) + { + // Copy the given parameters + this._outerId = _outerId; + this._columnIds = _columnIds; + this._prototypes = {}; + + this._template = null; + this._mgrs = null; + this._rootWidget = null; + + // Create the default row "prototypes" + this._createFullRowPrototype(); + this._createDefaultPrototype(); + this._createEmptyPrototype(); + this._createLoadingPrototype(); + } + + public getColumnCount() + { + return this._columnIds.length; + } + + /** + * Returns a clone of the prototype with the given name. If the generator + * callback function is given, this function is called if the prototype + * does not yet registered. + * + * @param {string} _name + * @param {function} _generator + * @param {object} _context + */ + getPrototype( _name : string, _generator? : Function, _context? : any) + { + if (typeof this._prototypes[_name] == "undefined") + { + if (typeof _generator != "undefined") + { + this._prototypes[_name] = _generator.call(_context, this._outerId, + this._columnIds); + } + else + { + return null; + } + } + + return this._prototypes[_name].clone(); + } + + + /* ---- PRIVATE FUNCTIONS ---- */ + + + _createFullRowPrototype( ) + { + var tr = jQuery(document.createElement("tr")); + var td = jQuery(document.createElement("td")) + .addClass(this._outerId + "_td_fullRow") + .attr("colspan", this._columnIds.length) + .appendTo(tr); + var div = jQuery(document.createElement("div")) + .addClass(this._outerId + "_div_fullRow") + .appendTo(td); + + this._prototypes["fullRow"] = tr; + } + + _createDefaultPrototype( ) + { + var tr = jQuery(document.createElement("tr")); + + // Append a td for each column + for (var i = 0; i < this._columnIds.length; i++) + { + var td = jQuery(document.createElement("td")) + .addClass(this._outerId + "_td_" + this._columnIds[i]) + .appendTo(tr); + var div = jQuery(document.createElement("div")) + .addClass(this._outerId + "_div_" + this._columnIds[i]) + .addClass("innerContainer") + .appendTo(td); + } + + this._prototypes["default"] = tr; + } + + _createEmptyPrototype( ) + { + this._prototypes["empty"] = jQuery(document.createElement("tr")); + } + + _createLoadingPrototype( ) + { + var fullRow = this.getPrototype("fullRow"); + jQuery("div", fullRow).addClass("loading"); + + this._prototypes["loading"] = fullRow; + } + +} + diff --git a/api/js/etemplate/et2_dataview_view_spacer.js b/api/js/etemplate/et2_dataview_view_spacer.js index 2dc1cdf588..f92d344cb6 100644 --- a/api/js/etemplate/et2_dataview_view_spacer.js +++ b/api/js/etemplate/et2_dataview_view_spacer.js @@ -1,3 +1,4 @@ +"use strict"; /** * EGroupware eTemplate2 - Class which contains the spacer container * @@ -9,97 +10,94 @@ * @copyright Stylite 2011 * @version $Id$ */ - +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); /*egw:uses - /vendor/bower-asset/jquery/dist/jquery.js; - et2_dataview_view_container; + /vendor/bower-asset/jquery/dist/jquery.js; + et2_dataview_view_container; */ - /** * @augments et2_dataview_container */ -var et2_dataview_spacer = (function(){ "use strict"; return et2_dataview_container.extend( -{ - /** - * Constructor - * - * @param _parent - * @param _rowProvider - * @memberOf et2_dataview_spacer - */ - init: function (_parent, _rowProvider) { - // Call the inherited container constructor - this._super(_parent); - - // Initialize the row count and the row height - this._count = 0; - this._rowHeight = 19; - this._avgSum = 0; - this._avgCount = 0; - - // Get the spacer row and append it to the container - this.spacerNode = _rowProvider.getPrototype("spacer", - this._createSpacerPrototype, this); - this._phDiv = jQuery("td", this.spacerNode); - this.appendNode(this.spacerNode); - }, - - setCount: function (_count, _rowHeight) { - // Set the new count and _rowHeight if given - this._count = _count; - if (typeof _rowHeight !== "undefined") - { - this._rowHeight = _rowHeight; - } - - // Update the element height - this._phDiv.height(this._count * this._rowHeight); - - // Call the invalidate function - this.invalidate(); - }, - - getCount: function () { - return this._count; - }, - - getHeight: function () { - // Set the calculated height, so that "invalidate" will work correctly - this._height = this._count * this._rowHeight; - - return this._height; - }, - - getAvgHeightData: function () { - if (this._avgCount > 0) - { - return { - "avgHeight": this._avgSum / this._avgCount, - "avgCount": this._avgCount - }; - } - - return null; - }, - - addAvgHeight: function (_height) { - this._avgSum += _height; - this._avgCount++; - }, - - /* ---- PRIVATE FUNCTIONS ---- */ - - _createSpacerPrototype: function (_outerId, _columnIds) { - var tr = jQuery(document.createElement("tr")); - - var td = jQuery(document.createElement("td")) - .addClass("egwGridView_spacer") - .addClass(_outerId + "_spacer_fullRow") - .attr("colspan", _columnIds.length) - .appendTo(tr); - - return tr; - } - -});}).call(this); - +var et2_dataview_spacer = /** @class */ (function (_super) { + __extends(et2_dataview_spacer, _super); + /** + * Constructor + * + * @param _parent + * @param _rowProvider + * @memberOf et2_dataview_spacer + */ + function et2_dataview_spacer(_parent, _rowProvider) { + var _this = + // Call the inherited container constructor + _super.call(this, _parent) || this; + // Initialize the row count and the row height + _this._count = 0; + _this._rowHeight = 19; + _this._avgSum = 0; + _this._avgCount = 0; + // Get the spacer row and append it to the container + _this.spacerNode = _rowProvider.getPrototype("spacer", _this._createSpacerPrototype, _this); + _this._phDiv = jQuery("td", _this.spacerNode); + _this.appendNode(_this.spacerNode); + return _this; + } + et2_dataview_spacer.prototype.setCount = function (_count, _rowHeight) { + // Set the new count and _rowHeight if given + this._count = _count; + if (typeof _rowHeight !== "undefined") { + this._rowHeight = _rowHeight; + } + // Update the element height + this._phDiv.height(this._count * this._rowHeight); + // Call the invalidate function + this.invalidate(); + }; + et2_dataview_spacer.prototype.getCount = function () { + return this._count; + }; + et2_dataview_spacer.prototype.getHeight = function () { + // Set the calculated height, so that "invalidate" will work correctly + this._height = this._count * this._rowHeight; + return this._height; + }; + et2_dataview_spacer.prototype.getAvgHeightData = function () { + if (this._avgCount > 0) { + return { + "avgHeight": this._avgSum / this._avgCount, + "avgCount": this._avgCount + }; + } + return null; + }; + et2_dataview_spacer.prototype.addAvgHeight = function (_height) { + this._avgSum += _height; + this._avgCount++; + }; + /* ---- PRIVATE FUNCTIONS ---- */ + et2_dataview_spacer.prototype._createSpacerPrototype = function (_outerId, _columnIds) { + var tr = jQuery(document.createElement("tr")); + var td = jQuery(document.createElement("td")) + .addClass("egwGridView_spacer") + .addClass(_outerId + "_spacer_fullRow") + .attr("colspan", _columnIds.length) + .appendTo(tr); + return tr; + }; + return et2_dataview_spacer; +}(et2_dataview_container)); +exports.et2_dataview_spacer = et2_dataview_spacer; +//# sourceMappingURL=et2_dataview_view_spacer.js.map \ No newline at end of file diff --git a/api/js/etemplate/et2_dataview_view_spacer.ts b/api/js/etemplate/et2_dataview_view_spacer.ts new file mode 100644 index 0000000000..a08b043fea --- /dev/null +++ b/api/js/etemplate/et2_dataview_view_spacer.ts @@ -0,0 +1,112 @@ +/** + * EGroupware eTemplate2 - Class which contains the spacer container + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package etemplate + * @subpackage dataview + * @link http://www.egroupware.org + * @author Andreas Stöckel + * @copyright Stylite 2011 + * @version $Id$ + */ + +/*egw:uses + /vendor/bower-asset/jquery/dist/jquery.js; + et2_dataview_view_container; +*/ + +/** + * @augments et2_dataview_container + */ +export class et2_dataview_spacer extends et2_dataview_container +{ + /** + * Constructor + * + * @param _parent + * @param _rowProvider + * @memberOf et2_dataview_spacer + */ + constructor( _parent, _rowProvider) + { + // Call the inherited container constructor + super(_parent); + + // Initialize the row count and the row height + this._count = 0; + this._rowHeight = 19; + this._avgSum = 0; + this._avgCount = 0; + + // Get the spacer row and append it to the container + this.spacerNode = _rowProvider.getPrototype("spacer", + this._createSpacerPrototype, this); + this._phDiv = jQuery("td", this.spacerNode); + this.appendNode(this.spacerNode); + } + + setCount( _count, _rowHeight) + { + // Set the new count and _rowHeight if given + this._count = _count; + if (typeof _rowHeight !== "undefined") + { + this._rowHeight = _rowHeight; + } + + // Update the element height + this._phDiv.height(this._count * this._rowHeight); + + // Call the invalidate function + this.invalidate(); + } + + getCount( ) + { + return this._count; + } + + getHeight( ) + { + // Set the calculated height, so that "invalidate" will work correctly + this._height = this._count * this._rowHeight; + + return this._height; + } + + getAvgHeightData( ) + { + if (this._avgCount > 0) + { + return { + "avgHeight": this._avgSum / this._avgCount, + "avgCount": this._avgCount + }; + } + + return null; + } + + addAvgHeight( _height) + { + this._avgSum += _height; + this._avgCount++; + } + + /* ---- PRIVATE FUNCTIONS ---- */ + + _createSpacerPrototype( _outerId, _columnIds) + { + var tr = jQuery(document.createElement("tr")); + + var td = jQuery(document.createElement("td")) + .addClass("egwGridView_spacer") + .addClass(_outerId + "_spacer_fullRow") + .attr("colspan", _columnIds.length) + .appendTo(tr); + + return tr; + } + +} + diff --git a/api/js/etemplate/et2_dataview_view_tile.js b/api/js/etemplate/et2_dataview_view_tile.js index 2360759e06..41154e3160 100644 --- a/api/js/etemplate/et2_dataview_view_tile.js +++ b/api/js/etemplate/et2_dataview_view_tile.js @@ -1,3 +1,4 @@ +"use strict"; /** * EGroupware eTemplate2 - dataview code * @@ -9,96 +10,99 @@ * @copyright Nathan Gray 2014 * @version $Id: et2_dataview_view_container_1.js 46338 2014-03-20 09:40:37Z ralfbecker $ */ - +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); /*egw:uses - /vendor/bower-asset/jquery/dist/jquery.js; - et2_dataview_interfaces; + /vendor/bower-asset/jquery/dist/jquery.js; + et2_dataview_interfaces; */ - /** * Displays tiles or thumbnails (squares) instead of full rows. * * It's important that the template specifies a fixed width and height (via CSS) * so that the rows and columns work out properly. * - * @augments et2_dataview_container */ -var et2_dataview_tile = (function(){ "use strict"; return et2_dataview_row.extend([], -{ - columns: 4, - - /** - * Creates the row container. Use the "setRow" function to load the actual - * row content. - * - * @param _parent is the row parent container. - * @memberOf et2_dataview_row - */ - init: function(_parent) { - // Call the inherited constructor - this._super(_parent); - - // Make sure the needed class is there to get the CSS - this.tr.addClass('tile'); - }, - - makeExpandable: function (_expandable, _callback, _context) { - // Nope. It mostly works, it's just weird. - }, - - getAvgHeightData: function() { - var res = { - "avgHeight": this.getHeight() / this.columns, - "avgCount": this.columns - }; - return res; - }, - - /** - * Returns the height for the tile. - * - * This is where we do the magic. If a new row should start, we return the proper - * height. If this should be another tile in the same row, we say it has 0 height. - * @returns {Number} - */ - getHeight: function() { - if(this._index % this.columns == 0) - { - return this._super(); - } - else - { - return 0; - } - }, - - /** - * Broadcasts an invalidation through the container tree. Marks the own - * height as invalid. - */ - invalidate: function() { - if(this._inTree && this.tr) - { - var template_width = jQuery('.innerContainer',this.tr).children().outerWidth(true); - if(template_width) - { - - this.tr.css('width', template_width + (this.tr.outerWidth(true) - this.tr.width())); - } - } - this._recalculate_columns(); - this._super(); - }, - - /** - * Recalculate how many columns we can fit in a row. - * While browser takes care of the actual layout, we need this for proper - * pagination. - */ - _recalculate_columns: function() { - if(this._inTree && this.tr && this.tr.parent()) - { - this.columns = Math.max(1,parseInt(this.tr.parent().innerWidth() / this.tr.outerWidth(true))); - } - } -});}).call(this); +var et2_dataview_tile = /** @class */ (function (_super) { + __extends(et2_dataview_tile, _super); + /** + * Creates the row container. Use the "setRow" function to load the actual + * row content. + * + * @param _parent is the row parent container. + * @memberOf et2_dataview_row + */ + function et2_dataview_tile(_parent) { + var _this = + // Call the inherited constructor + _super.call(this, _parent) || this; + _this.columns = 4; + // Make sure the needed class is there to get the CSS + _this.tr.addClass('tile'); + return _this; + } + et2_dataview_tile.prototype.makeExpandable = function (_expandable, _callback, _context) { + // Nope. It mostly works, it's just weird. + }; + et2_dataview_tile.prototype.getAvgHeightData = function () { + var res = { + "avgHeight": this.getHeight() / this.columns, + "avgCount": this.columns + }; + return res; + }; + /** + * Returns the height for the tile. + * + * This is where we do the magic. If a new row should start, we return the proper + * height. If this should be another tile in the same row, we say it has 0 height. + * @returns {Number} + */ + et2_dataview_tile.prototype.getHeight = function () { + if (this._index % this.columns == 0) { + return _super.prototype.getHeight.call(this); + } + else { + return 0; + } + }; + /** + * Broadcasts an invalidation through the container tree. Marks the own + * height as invalid. + */ + et2_dataview_tile.prototype.invalidate = function () { + if (this._inTree && this.tr) { + var template_width = jQuery('.innerContainer', this.tr).children().outerWidth(true); + if (template_width) { + this.tr.css('width', template_width + (this.tr.outerWidth(true) - this.tr.width())); + } + } + this._recalculate_columns(); + _super.prototype.invalidate.call(this); + }; + /** + * Recalculate how many columns we can fit in a row. + * While browser takes care of the actual layout, we need this for proper + * pagination. + */ + et2_dataview_tile.prototype._recalculate_columns = function () { + if (this._inTree && this.tr && this.tr.parent()) { + this.columns = Math.max(1, parseInt(this.tr.parent().innerWidth() / this.tr.outerWidth(true))); + } + }; + return et2_dataview_tile; +}(et2_dataview_row)); +exports.et2_dataview_tile = et2_dataview_tile; +//# sourceMappingURL=et2_dataview_view_tile.js.map \ No newline at end of file diff --git a/api/js/etemplate/et2_dataview_view_tile.ts b/api/js/etemplate/et2_dataview_view_tile.ts new file mode 100644 index 0000000000..32d25a40fc --- /dev/null +++ b/api/js/etemplate/et2_dataview_view_tile.ts @@ -0,0 +1,107 @@ +/** + * EGroupware eTemplate2 - dataview code + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package etemplate + * @subpackage dataview + * @link http://www.egroupware.org + * @author Nathan Gray + * @copyright Nathan Gray 2014 + * @version $Id: et2_dataview_view_container_1.js 46338 2014-03-20 09:40:37Z ralfbecker $ + */ + +/*egw:uses + /vendor/bower-asset/jquery/dist/jquery.js; + et2_dataview_interfaces; +*/ + +/** + * Displays tiles or thumbnails (squares) instead of full rows. + * + * It's important that the template specifies a fixed width and height (via CSS) + * so that the rows and columns work out properly. + * + */ +export class et2_dataview_tile extends et2_dataview_row { + columns: number = 4; + + /** + * Creates the row container. Use the "setRow" function to load the actual + * row content. + * + * @param _parent is the row parent container. + * @memberOf et2_dataview_row + */ + constructor(_parent) + { + // Call the inherited constructor + super(_parent); + + // Make sure the needed class is there to get the CSS + this.tr.addClass('tile'); + } + + makeExpandable(_expandable, _callback, _context) + { + // Nope. It mostly works, it's just weird. + } + + getAvgHeightData() + { + var res = { + "avgHeight": this.getHeight() / this.columns, + "avgCount": this.columns + }; + return res; + } + + /** + * Returns the height for the tile. + * + * This is where we do the magic. If a new row should start, we return the proper + * height. If this should be another tile in the same row, we say it has 0 height. + * @returns {Number} + */ + getHeight() + { + if (this._index % this.columns == 0) + { + return super.getHeight(); + } + else + { + return 0; + } + } + + /** + * Broadcasts an invalidation through the container tree. Marks the own + * height as invalid. + */ + invalidate() + { + if (this._inTree && this.tr) + { + var template_width = jQuery('.innerContainer', this.tr).children().outerWidth(true); + if (template_width) + { + this.tr.css('width', template_width + (this.tr.outerWidth(true) - this.tr.width())); + } + } + this._recalculate_columns(); + super.invalidate(); + } + + /** + * Recalculate how many columns we can fit in a row. + * While browser takes care of the actual layout, we need this for proper + * pagination. + */ + _recalculate_columns() + { + if (this._inTree && this.tr && this.tr.parent()) + { + this.columns = Math.max(1, parseInt(this.tr.parent().innerWidth() / this.tr.outerWidth(true))); + } + } +} diff --git a/api/js/etemplate/et2_extension_nextmatch.js b/api/js/etemplate/et2_extension_nextmatch.js index 58655257dc..2dc66b0c55 100644 --- a/api/js/etemplate/et2_extension_nextmatch.js +++ b/api/js/etemplate/et2_extension_nextmatch.js @@ -9,21 +9,8 @@ * @author Andreas Stöckel * @copyright Stylite 2011 * @version $Id$ - */ -var __extends = (this && this.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); + * + /*egw:uses // Include the action system @@ -46,14 +33,28 @@ Object.defineProperty(exports, "__esModule", { value: true }); et2_extension_customfields; // Include all nextmatch subclasses - et2_extension_nextmatch_controller; et2_extension_nextmatch_rowProvider; + et2_extension_nextmatch_controller; et2_extension_nextmatch_dynheight; // Include the grid classes et2_dataview; */ +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); require("./et2_core_common"); require("./et2_core_interfaces"); var et2_core_widget_1 = require("./et2_core_widget"); @@ -62,6 +63,10 @@ var et2_core_baseWidget_1 = require("./et2_core_baseWidget"); var et2_core_inputWidget_1 = require("./et2_core_inputWidget"); var et2_widget_selectbox_1 = require("./et2_widget_selectbox"); var et2_core_inheritance_1 = require("./et2_core_inheritance"); +var et2_extension_nextmatch_rowProvider_1 = require("./et2_extension_nextmatch_rowProvider"); +var et2_extension_nextmatch_controller_1 = require("./et2_extension_nextmatch_controller"); +var et2_dataview_1 = require("./et2_dataview"); +var et2_dataview_model_columns_1 = require("./et2_dataview_model_columns"); var et2_INextmatchHeader = "et2_INextmatchHeader"; function implements_et2_INextmatchHeader(obj) { return implements_methods(obj, ["setNextmatch"]); @@ -89,15 +94,15 @@ function implements_et2_INextmatchSortable(obj) { * +--------------+-----------+-------+ * @augments et2_DOMWidget */ -var et2_nextmatch = /** @class */ (function (_super_1) { - __extends(et2_nextmatch, _super_1); +var et2_nextmatch = /** @class */ (function (_super) { + __extends(et2_nextmatch, _super); /** * Constructor * * @memberOf et2_nextmatch */ function et2_nextmatch(_parent, _attrs, _child) { - var _this = _super_1.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_nextmatch._attributes, _child || {})) || this; + var _this = _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_nextmatch._attributes, _child || {})) || this; _this.activeFilters = { col_filter: {} }; _this.columns = []; // keeps sorted columns @@ -128,7 +133,7 @@ var et2_nextmatch = /** @class */ (function (_super_1) { // container. _this.dynheight = _this._getDynheight(); // Create the outer grid container - _this.dataview = new et2_dataview(_this.innerDiv, _this.egw()); + _this.dataview = new et2_dataview_1.et2_dataview(_this.innerDiv, _this.egw()); // Blank placeholder _this.blank = jQuery(document.createElement("div")) .appendTo(_this.dataview.table); @@ -151,15 +156,15 @@ var et2_nextmatch = /** @class */ (function (_super_1) { jQuery(this.getInstanceManager().DOMContainer.parentNode).off('show.et2_nextmatch'); jQuery(this.getInstanceManager().DOMContainer.parentNode).off('hide.et2_nextmatch'); // Free the grid components - this.dataview.free(); + this.dataview.destroy(); if (this.rowProvider) { - this.rowProvider.free(); + this.rowProvider.destroy(); } if (this.controller) { - this.controller.free(); + this.controller.destroy(); } - this.dynheight.free(); - _super_1.prototype.destroy.call(this); + this.dynheight.destroy(); + _super.prototype.destroy.call(this); }; /** * Loads the nextmatch settings @@ -167,7 +172,7 @@ var et2_nextmatch = /** @class */ (function (_super_1) { * @param {object} _attrs */ et2_nextmatch.prototype.transformAttributes = function (_attrs) { - _super_1.prototype.transformAttributes.call(this, _attrs); + _super.prototype.transformAttributes.call(this, _attrs); if (this.id) { var entry = this.getArrayMgr("content").data; _attrs["settings"] = {}; @@ -188,7 +193,7 @@ var et2_nextmatch = /** @class */ (function (_super_1) { } }; et2_nextmatch.prototype.doLoadingFinished = function () { - _super_1.prototype.doLoadingFinished.call(this); + _super.prototype.doLoadingFinished.call(this); if (!this.dynheight) { this.dynheight = this._getDynheight(); } @@ -866,11 +871,11 @@ var et2_nextmatch = /** @class */ (function (_super_1) { "widget": _row[x].widget }, _colData[x]); var visibility = (!_colData[x] || _colData[x].visible) ? - et2_dataview_grid.ET2_COL_VISIBILITY_VISIBLE : - et2_dataview_grid.ET2_COL_VISIBILITY_INVISIBLE; + et2_dataview_model_columns_1.et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE : + et2_dataview_model_columns_1.et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE; if (_colData[x].disabled && _colData[x].disabled !== '' && this.getArrayMgr("content").parseBoolExpression(_colData[x].disabled)) { - visibility = et2_dataview_grid.ET2_COL_VISIBILITY_DISABLED; + visibility = et2_dataview_model_columns_1.et2_dataview_column.ET2_COL_VISIBILITY_DISABLED; } columnData[x] = { "id": "col_" + x, @@ -914,11 +919,7 @@ var et2_nextmatch = /** @class */ (function (_super_1) { this.addChild(_row[x].widget); } // Create the nextmatch row provider - /* TODO - this.rowProvider = new et2_nextmatch_rowProvider( - this.dataview.rowProvider, this._getSubgrid, this); - - */ + this.rowProvider = new et2_extension_nextmatch_rowProvider_1.et2_nextmatch_rowProvider(this.dataview.rowProvider, this._getSubgrid, this); // Register handler to update preferences when column properties are changed var self = this; this.dataview.onUpdateColumns = function () { @@ -962,11 +963,9 @@ var et2_nextmatch = /** @class */ (function (_super_1) { columnWidgets[x].align = _row[x].align; } } - return; - // TODO this.rowProvider.setDataRowTemplate(columnWidgets, _rowData, this); // Create the grid controller - this.controller = new et2_nextmatch_controller(null, this.egw(), this.getInstanceManager().etemplate_exec_id, this, null, this.dataview.grid, this.rowProvider, this.options.settings.action_links, null, this.options.actions); + this.controller = new et2_extension_nextmatch_controller_1.et2_nextmatch_controller(null, this.egw(), this.getInstanceManager().etemplate_exec_id, this, null, this.dataview.grid, this.rowProvider, this.options.settings.action_links, null, this.options.actions); // Need to trigger empty row the first time if (total == 0) this.controller._emptyRow(); @@ -1018,11 +1017,11 @@ var et2_nextmatch = /** @class */ (function (_super_1) { // parent grid var grid = new et2_dataview_grid(_row, this.dataview.grid); // Create a new controller for the grid - var controller = new et2_nextmatch_controller(_controller, this.egw(), this.getInstanceManager().etemplate_exec_id, this, rowId, grid, this.rowProvider, this.options.settings.action_links, _controller.getObjectManager()); + var controller = new et2_extension_nextmatch_controller_1.et2_nextmatch_controller(_controller, this.egw(), this.getInstanceManager().etemplate_exec_id, this, rowId, grid, this.rowProvider, this.options.settings.action_links, _controller.getObjectManager()); controller.update(); // Register inside the destruction callback of the grid grid.setDestroyCallback(function () { - controller.free(); + controller.destroy(); }); return grid; }; @@ -1396,9 +1395,9 @@ var et2_nextmatch = /** @class */ (function (_super_1) { return; } // Free the grid components - they'll be re-created as the template is processed - this.dataview.free(); - this.rowProvider.free(); - this.controller.free(); + this.dataview.destroy(); + this.rowProvider.destroy(); + this.controller.destroy(); // Free any children from previous template // They may get left behind because of how detached nodes are processed // We don't use iterateOver because it checks sub-children @@ -1414,7 +1413,7 @@ var et2_nextmatch = /** @class */ (function (_super_1) { if (this.template == this.options.settings.columnselection_pref) { this.options.settings.columnselection_pref = template_name; } - this.dataview = new et2_dataview(this.innerDiv, this.egw()); + this.dataview = new et2_dataview_1.et2_dataview(this.innerDiv, this.egw()); } // Create the template if (template_name) { @@ -1549,7 +1548,7 @@ var et2_nextmatch = /** @class */ (function (_super_1) { */ et2_nextmatch.prototype.set_disabled = function (_value) { var previous = this.disabled; - _super_1.prototype.set_disabled.call(this, _value); + _super.prototype.set_disabled.call(this, _value); if (previous && !_value) { this.resize(); } @@ -1663,7 +1662,7 @@ var et2_nextmatch = /** @class */ (function (_super_1) { // Fade out nicely status.delay(linked ? 1 : 2000) .fadeOut(500, function () { - link.free(); + link.destroy(); status.remove(); }); }); @@ -2041,8 +2040,8 @@ et2_core_widget_1.et2_register_widget(et2_nextmatch, ["nextmatch"]); * actually load templates from the server. * @augments et2_DOMWidget */ -var et2_nextmatch_header_bar = /** @class */ (function (_super_1) { - __extends(et2_nextmatch_header_bar, _super_1); +var et2_nextmatch_header_bar = /** @class */ (function (_super) { + __extends(et2_nextmatch_header_bar, _super); /** * Constructor * @@ -2051,7 +2050,7 @@ var et2_nextmatch_header_bar = /** @class */ (function (_super_1) { * @memberOf et2_nextmatch_header_bar */ function et2_nextmatch_header_bar(_parent, _attrs, _child) { - var _this = _super_1.call(this, _parent, [_parent, _parent.options.settings], et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_nextmatch_header_bar._attributes, _child || {})) || this; + var _this = _super.call(this, _parent, [_parent, _parent.options.settings], et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_nextmatch_header_bar._attributes, _child || {})) || this; _this.nextmatch = _parent; _this.div = jQuery(document.createElement("div")) .addClass("nextmatch_header"); @@ -2062,7 +2061,7 @@ var et2_nextmatch_header_bar = /** @class */ (function (_super_1) { } et2_nextmatch_header_bar.prototype.destroy = function () { this.nextmatch = null; - _super_1.prototype.destroy.call(this); + _super.prototype.destroy.call(this); this.div = null; }; et2_nextmatch_header_bar.prototype.setNextmatch = function (nextmatch) { @@ -2587,15 +2586,15 @@ et2_core_widget_1.et2_register_widget(et2_nextmatch_header_bar, ["nextmatch_head * * @augments et2_baseWidget */ -var et2_nextmatch_header = /** @class */ (function (_super_1) { - __extends(et2_nextmatch_header, _super_1); +var et2_nextmatch_header = /** @class */ (function (_super) { + __extends(et2_nextmatch_header, _super); /** * Constructor * * @memberOf et2_nextmatch_header */ function et2_nextmatch_header(_parent, _attrs, _child) { - var _this = _super_1.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_nextmatch_header._attributes, _child || {})) || this; + var _this = _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_nextmatch_header._attributes, _child || {})) || this; _this.labelNode = jQuery(document.createElement("span")); _this.nextmatch = null; _this.setDOMNode(_this.labelNode[0]); @@ -2627,24 +2626,24 @@ et2_core_widget_1.et2_register_widget(et2_nextmatch_header, ['nextmatch-header'] * * TODO This should extend customfield widget when it's ready, put the whole column in constructor() back too */ -var et2_nextmatch_customfields = /** @class */ (function (_super_1) { - __extends(et2_nextmatch_customfields, _super_1); +var et2_nextmatch_customfields = /** @class */ (function (_super) { + __extends(et2_nextmatch_customfields, _super); /** * Constructor * * @memberOf et2_nextmatch_customfields */ function et2_nextmatch_customfields(_parent, _attrs, _child) { - return _super_1.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_nextmatch_customfields._attributes, _child || {})) || this; + return _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_nextmatch_customfields._attributes, _child || {})) || this; // Specifically take the whole column // this.table.css("width", "100%"); } et2_nextmatch_customfields.prototype.destroy = function () { this.nextmatch = null; - _super_1.prototype.destroy.call(this); + _super.prototype.destroy.call(this); }; et2_nextmatch_customfields.prototype.transformAttributes = function (_attrs) { - _super_1.prototype.transformAttributes.call(this, _attrs); + _super.prototype.transformAttributes.call(this, _attrs); // Add in settings that are objects if (!_attrs.customfields) { // Check for custom stuff (unlikely) @@ -2744,7 +2743,7 @@ var et2_nextmatch_customfields = /** @class */ (function (_super_1) { * @param {array} _fields */ et2_nextmatch_customfields.prototype.set_visible = function (_fields) { - _super_1.prototype.set_visible.call(this, _fields); + _super.prototype.set_visible.call(this, _fields); // Find data row, and do it too var self = this; if (this.nextmatch) { @@ -2807,21 +2806,21 @@ et2_core_widget_1.et2_register_widget(et2_nextmatch_customfields, ['nextmatch-cu * @augments et2_nextmatch_header */ // @ts-ignore -var et2_nextmatch_sortheader = /** @class */ (function (_super_1) { - __extends(et2_nextmatch_sortheader, _super_1); +var et2_nextmatch_sortheader = /** @class */ (function (_super) { + __extends(et2_nextmatch_sortheader, _super); /** * Constructor * * @memberOf et2_nextmatch_sortheader */ function et2_nextmatch_sortheader(_parent, _attrs, _child) { - var _this = _super_1.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_nextmatch_sortheader._attributes, _child || {})) || this; + var _this = _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_nextmatch_sortheader._attributes, _child || {})) || this; _this.sortmode = "none"; _this.labelNode.addClass("nextmatch_sortheader none"); return _this; } et2_nextmatch_sortheader.prototype.click = function (_event) { - if (this.nextmatch && _super_1.prototype.click.call(this, _event)) { + if (this.nextmatch && _super.prototype.click.call(this, _event)) { // Send default sort mode if not sorted, otherwise send undefined to calculate this.nextmatch.sortBy(this.id, this.sortmode == "none" ? !(this.options.sortmode.toUpperCase() == "DESC") : undefined); return true; @@ -2857,10 +2856,10 @@ et2_core_widget_1.et2_register_widget(et2_nextmatch_sortheader, ['nextmatch-sort /** * @augments et2_selectbox */ -var et2_nextmatch_filterheader = /** @class */ (function (_super_1) { - __extends(et2_nextmatch_filterheader, _super_1); +var et2_nextmatch_filterheader = /** @class */ (function (_super) { + __extends(et2_nextmatch_filterheader, _super); function et2_nextmatch_filterheader() { - return _super_1 !== null && _super_1.apply(this, arguments) || this; + return _super !== null && _super.apply(this, arguments) || this; } /** * Override to add change handler @@ -2872,7 +2871,7 @@ var et2_nextmatch_filterheader = /** @class */ (function (_super_1) { if (!this.options.empty_label && (!this.options.select_options || !this.options.select_options[""])) { this.options.empty_label = this.options.label ? this.options.label : egw.lang("All"); } - _super_1.prototype.createInputWidget.call(this); + _super.prototype.createInputWidget.call(this); jQuery(this.getInputNode()).change(this, function (event) { if (typeof event.data.nextmatch == 'undefined') { // Not fully set up yet @@ -2911,10 +2910,10 @@ et2_core_widget_1.et2_register_widget(et2_nextmatch_filterheader, ['nextmatch-fi /** * @augments et2_selectAccount */ -var et2_nextmatch_accountfilterheader = /** @class */ (function (_super_1) { - __extends(et2_nextmatch_accountfilterheader, _super_1); +var et2_nextmatch_accountfilterheader = /** @class */ (function (_super) { + __extends(et2_nextmatch_accountfilterheader, _super); function et2_nextmatch_accountfilterheader() { - return _super_1 !== null && _super_1.apply(this, arguments) || this; + return _super !== null && _super.apply(this, arguments) || this; } /** * Override to add change handler @@ -2926,7 +2925,7 @@ var et2_nextmatch_accountfilterheader = /** @class */ (function (_super_1) { if (!this.options.empty_label && !this.options.select_options[""]) { this.options.empty_label = this.options.label ? this.options.label : egw.lang("All"); } - this._super.apply(this, arguments); + _super.prototype.createInputWidget.call(this, this, arguments); this.input.change(this, function (event) { if (typeof event.data.nextmatch == 'undefined') { // Not fully set up yet @@ -2968,10 +2967,10 @@ et2_core_widget_1.et2_register_widget(et2_nextmatch_accountfilterheader, ['nextm * * @augments et2_taglist */ -var et2_nextmatch_taglistheader = /** @class */ (function (_super_1) { - __extends(et2_nextmatch_taglistheader, _super_1); +var et2_nextmatch_taglistheader = /** @class */ (function (_super) { + __extends(et2_nextmatch_taglistheader, _super); function et2_nextmatch_taglistheader() { - return _super_1 !== null && _super_1.apply(this, arguments) || this; + return _super !== null && _super.apply(this, arguments) || this; } /** * Override to add change handler @@ -2983,7 +2982,7 @@ var et2_nextmatch_taglistheader = /** @class */ (function (_super_1) { if (!this.options.empty_label && (!this.options.select_options || !this.options.select_options[""])) { this.options.empty_label = this.options.label ? this.options.label : egw.lang("All"); } - _super_1.prototype.createInputWidget.call(this); + _super.prototype.createInputWidget.call(this); }; /** * Disable toggle if there are 2 or less options @@ -2993,7 +2992,7 @@ var et2_nextmatch_taglistheader = /** @class */ (function (_super_1) { if (options && options.length <= 2 && this.options.multiple == 'toggle') { this.set_multiple(false); } - _super_1.prototype.set_select_options.call(this, options); + _super.prototype.set_select_options.call(this, options); }; /** * Set nextmatch is the function which has to be implemented for the @@ -3014,7 +3013,7 @@ var et2_nextmatch_taglistheader = /** @class */ (function (_super_1) { et2_nextmatch_taglistheader.prototype.resize = function () { this.div.css("height", ''); this.div.css("max-width", jQuery(this.parentNode).innerWidth() + "px"); - _super_1.prototype.resize.call(this); + _super.prototype.resize.call(this); }; et2_nextmatch_taglistheader._attributes = { autocomplete_url: { default: '' }, @@ -3042,10 +3041,10 @@ et2_core_widget_1.et2_register_widget(et2_nextmatch_taglistheader, ['nextmatch-t /** * @augments et2_link_entry */ -var et2_nextmatch_entryheader = /** @class */ (function (_super_1) { - __extends(et2_nextmatch_entryheader, _super_1); +var et2_nextmatch_entryheader = /** @class */ (function (_super) { + __extends(et2_nextmatch_entryheader, _super); function et2_nextmatch_entryheader() { - return _super_1 !== null && _super_1.apply(this, arguments) || this; + return _super !== null && _super.apply(this, arguments) || this; } /** * Override to add change handler @@ -3065,7 +3064,7 @@ var et2_nextmatch_entryheader = /** @class */ (function (_super_1) { * id, the original parent value is returned. */ et2_nextmatch_entryheader.prototype.getValue = function () { - var value = _super_1.prototype.getValue.call(this); + var value = _super.prototype.getValue.call(this); if (typeof value == "object" && value != null) { if (!value.app || !value.id) return null; @@ -3108,8 +3107,8 @@ et2_core_widget_1.et2_register_widget(et2_nextmatch_entryheader, ['nextmatch-ent /** * @augments et2_nextmatch_filterheader */ -var et2_nextmatch_customfilter = /** @class */ (function (_super_1) { - __extends(et2_nextmatch_customfilter, _super_1); +var et2_nextmatch_customfilter = /** @class */ (function (_super) { + __extends(et2_nextmatch_customfilter, _super); /** * Constructor * @@ -3118,7 +3117,7 @@ var et2_nextmatch_customfilter = /** @class */ (function (_super_1) { * @memberOf et2_nextmatch_customfilter */ function et2_nextmatch_customfilter(_parent, _attrs, _child) { - var _this = _super_1.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_nextmatch_customfilter._attributes, _child || {})) || this; + var _this = _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_nextmatch_customfilter._attributes, _child || {})) || this; switch (_attrs.widget_type) { case "link-entry": _attrs.type = 'nextmatch-entryheader'; @@ -3133,7 +3132,7 @@ var et2_nextmatch_customfilter = /** @class */ (function (_super_1) { } jQuery.extend(_attrs.widget_options, { id: _this.id }); _attrs.id = ''; - _this = _super_1.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_nextmatch_customfilter._attributes, _child || {})) || this; + _this = _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_nextmatch_customfilter._attributes, _child || {})) || this; _this.real_node = et2_createWidget(_attrs.type, _attrs.widget_options, _this.getParent()); var select_options = []; var correct_type = _attrs.type; diff --git a/api/js/etemplate/et2_extension_nextmatch.ts b/api/js/etemplate/et2_extension_nextmatch.ts index 1939518c3a..e9c318615e 100644 --- a/api/js/etemplate/et2_extension_nextmatch.ts +++ b/api/js/etemplate/et2_extension_nextmatch.ts @@ -8,7 +8,7 @@ * @author Andreas Stöckel * @copyright Stylite 2011 * @version $Id$ - */ + * /*egw:uses @@ -32,8 +32,8 @@ et2_extension_customfields; // Include all nextmatch subclasses - et2_extension_nextmatch_controller; et2_extension_nextmatch_rowProvider; + et2_extension_nextmatch_controller; et2_extension_nextmatch_dynheight; // Include the grid classes @@ -51,6 +51,11 @@ import {et2_inputWidget} from "./et2_core_inputWidget"; import {et2_selectbox} from "./et2_widget_selectbox"; import {ClassWithAttributes} from "./et2_core_inheritance"; +import {et2_nextmatch_rowProvider} from "./et2_extension_nextmatch_rowProvider"; +import {et2_nextmatch_controller} from "./et2_extension_nextmatch_controller"; +import {et2_dataview} from "./et2_dataview"; +import {et2_dataview_column} from "./et2_dataview_model_columns"; + /** * Interface all special nextmatch header elements have to implement. */ @@ -305,16 +310,16 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2 jQuery(this.getInstanceManager().DOMContainer.parentNode).off('hide.et2_nextmatch'); // Free the grid components - this.dataview.free(); + this.dataview.destroy(); if(this.rowProvider) { - this.rowProvider.free(); + this.rowProvider.destroy(); } if(this.controller) { - this.controller.free(); + this.controller.destroy(); } - this.dynheight.free(); + this.dynheight.destroy(); super.destroy(); } @@ -1214,12 +1219,12 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2 },_colData[x]); var visibility = (!_colData[x] || _colData[x].visible) ? - et2_dataview_grid.ET2_COL_VISIBILITY_VISIBLE : - et2_dataview_grid.ET2_COL_VISIBILITY_INVISIBLE; + et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE : + et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE; if(_colData[x].disabled && _colData[x].disabled !=='' && this.getArrayMgr("content").parseBoolExpression(_colData[x].disabled)) { - visibility = et2_dataview_grid.ET2_COL_VISIBILITY_DISABLED; + visibility = et2_dataview_column.ET2_COL_VISIBILITY_DISABLED; } columnData[x] = { "id": "col_" + x, @@ -1277,12 +1282,9 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2 } // Create the nextmatch row provider - /* TODO this.rowProvider = new et2_nextmatch_rowProvider( this.dataview.rowProvider, this._getSubgrid, this); - */ - // Register handler to update preferences when column properties are changed var self = this; this.dataview.onUpdateColumns = function() { @@ -1341,11 +1343,8 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2 } } - return; - // TODO this.rowProvider.setDataRowTemplate(columnWidgets, _rowData, this); - // Create the grid controller this.controller = new et2_nextmatch_controller( null, @@ -1445,7 +1444,7 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2 // Register inside the destruction callback of the grid grid.setDestroyCallback(function () { - controller.free(); + controller.destroy(); }); return grid; @@ -1906,9 +1905,9 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2 } // Free the grid components - they'll be re-created as the template is processed - this.dataview.free(); - this.rowProvider.free(); - this.controller.free(); + this.dataview.destroy(); + this.rowProvider.destroy(); + this.controller.destroy(); // Free any children from previous template // They may get left behind because of how detached nodes are processed @@ -2251,7 +2250,7 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2 // Fade out nicely status.delay(linked ? 1 : 2000) .fadeOut(500, function() { - link.free(); + link.destroy(); status.remove(); }); @@ -3783,7 +3782,7 @@ class et2_nextmatch_accountfilterheader extends et2_selectAccount implements et2 { this.options.empty_label = this.options.label ? this.options.label : egw.lang("All"); } - this._super.apply(this, arguments); + super.createInputWidget(this, arguments); this.input.change(this, function(event) { if(typeof event.data.nextmatch == 'undefined') diff --git a/api/js/etemplate/et2_extension_nextmatch_controller.js b/api/js/etemplate/et2_extension_nextmatch_controller.js index ae5bbd2907..2efd333310 100644 --- a/api/js/etemplate/et2_extension_nextmatch_controller.js +++ b/api/js/etemplate/et2_extension_nextmatch_controller.js @@ -1,3 +1,4 @@ +"use strict"; /** * EGroupware eTemplate2 - Class which contains a the data model for nextmatch widgets * @@ -8,735 +9,611 @@ * @author Andreas Stöckel * @copyright Stylite 2012 * @version $Id$ - */ + * /*egw:uses - et2_core_common; - et2_core_inheritance; + et2_core_common; + et2_core_inheritance; - et2_dataview_view_row; - et2_dataview_controller; - et2_dataview_interfaces; - et2_dataview_view_tile; + et2_dataview_view_row; + et2_dataview_controller; + et2_dataview_interfaces; + et2_dataview_view_tile; - et2_extension_nextmatch_actions; // Contains nm_action + et2_extension_nextmatch_actions; // Contains nm_action - egw_data; + egw_data; */ - +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +var et2_dataview_view_row_1 = require("./et2_dataview_view_row"); +var et2_dataview_view_tile_1 = require("./et2_dataview_view_tile"); /** * @augments et2_dataview_controller */ -var et2_nextmatch_controller = (function(){ "use strict"; return et2_dataview_controller.extend(et2_IDataProvider, -{ - // Display constants - VIEW_ROW: 'row', - VIEW_TILE: 'tile', - - /** - * Initializes the nextmatch controller. - * - * @param _parentController is the parent nextmatch controller instance - * @param _egw is the api instance - * @param _execId is the execId of the etemplate - * @param _widget is the nextmatch-widget we are fetching data for. - * @param _grid is the grid the grid controller will be controlling - * @param _rowProvider is the nextmatch row provider instance. - * @param _objectManager is the parent object manager (if null, the object - * manager) will be created using - * @param _actionLinks contains the action links - * @param _actions contains the actions, may be null if an object manager - * is given. - * @memberOf et2_nextmatch_controller - */ - init: function (_parentController, _egw, _execId, _widget, _parentId, - _grid, _rowProvider, _actionLinks, _objectManager, _actions) { - - // Copy the egw reference - this.egw = _egw; - - // Keep a reference to the widget - this._widget = _widget; - - // Copy the given parameters - this._actionLinks = _actionLinks; - this._execId = _execId; - - // Get full widget ID, including path - var id = _widget.getArrayMgr('content').getPath(); - - if(typeof id == 'string') - { - this._widgetId = id; - } - else if (id.length === 1) - { - this._widgetId = id[0]; - } - else - { - this._widgetId = id.shift() + '[' + id.join('][') + ']'; - } - this._parentId = _parentId; - this._rowProvider = _rowProvider; - - // Initialize the action and the object manager - // _initActions calls _init_link_dnd, which uses this._actionLinks, - // so this must happen after the above parameter copying - if (!_objectManager) - { - this._initActions(_actions); - } - else - { - this._actionManager = null; - this._objectManager = _objectManager; - } - // Add our selection callback to selection manager - var self = this; - this._objectManager.setSelectedCallback = function() {self._selectCallback.apply(self,[this,arguments]);}; - - // Call the parent et2_dataview_controller constructor - this._super(_parentController, _grid, this, this._rowCallback, - this._linkCallback, this, this._objectManager); - - // We start with no filters - this._filters = {}; - - // Keep selection across filter changes - this.kept_selection = null; - this.kept_focus = null; - this.kept_expansion = []; - - // Directly use the API-Implementation of dataRegisterUID and - // dataUnregisterUID - this.dataUnregisterUID = _egw.dataUnregisterUID; - - // Default to rows - this._view = et2_nextmatch_controller.prototype.VIEW_ROW; - }, - - destroy: function () { - // If the actionManager variable is set, the object- and actionManager - // were created by this instance -- clear them - if (this._actionManager) - { - this._objectManager.remove(); - this._actionManager.remove(); - } - - this._super(); - }, - - /** - * Updates the filter instance. - */ - setFilters: function (_filters) { - // Update the filters - this._filters = _filters; - }, - - /** - * Keep the selection, if possible, across a data fetch and restore it - * after - */ - keepSelection: function() { - this.kept_selection = this._selectionMgr ? this._selectionMgr.getSelected() : null; - this.kept_focus = this._selectionMgr && this._selectionMgr._focusedEntry ? - this._selectionMgr._focusedEntry.uid || null : null; - - // Find expanded rows - var nm = this._widget; - var controller = this; - jQuery('.arrow.opened',this._widget.getDOMNode(this._widget)).each(function() { - var entry = controller.getRowByNode(this); - if(entry && entry.uid) - { - controller.kept_expansion.push(entry.uid); - } - }); - }, - - getObjectManager: function () { - return this._objectManager; - }, - - /** - * Deletes a row from the grid - * - * @param {string} uid - */ - deleteRow: function(uid) { - var entry = this._selectionMgr._getRegisteredRowsEntry(uid); - - // Unselect - this._selectionMgr.setSelected(uid,false); - - if(entry && entry.idx !== null) - { - // This will remove the row, but add an empty to the end. - // That's OK, because it will be removed when we update the row count - this._grid.deleteRow(entry.idx); - - // Trigger controller to remove from internals - this.egw.dataStoreUID(uid,null); - // Stop caring about this ID - this.egw.dataDeleteUID(uid); - // Remove from internal map - delete this._indexMap[entry.idx]; - - // Update the indices of all elements after the current one - for(var mapIndex = entry.idx + 1; typeof this._indexMap[mapIndex] !== 'undefined'; mapIndex++) - { - var entry = this._indexMap[mapIndex]; - entry.idx = mapIndex-1; - this._indexMap[mapIndex-1] = entry; - - // Update selection mgr too - if(entry.uid && typeof this._selectionMgr._registeredRows[entry.uid] !== 'undefined') - { - var reg = this._selectionMgr._getRegisteredRowsEntry(entry.uid); - reg.idx = entry.idx; - if(reg.ao && reg.ao._index) reg.ao._index = entry.idx; - } - } - // Remove last one, it was moved to mapIndex-1 before increment - delete this._indexMap[mapIndex-1]; - - // Not needed, they share by reference - // this._selectionMgr.setIndexMap(this._indexMap); - } - }, - - /** -- PRIVATE FUNCTIONS -- **/ - - /** - * Create a new row, either normal or tiled - * - * @param {type} ctx - * @returns {et2_dataview_container} - */ - _createRow: function(ctx) { - switch(this._view) - { - case et2_nextmatch_controller.prototype.VIEW_TILE: - var row = new et2_dataview_tile(this._grid); - // Try to overcome chrome rendering issue where float is not - // applied properly, leading to incomplete rows - window.setTimeout(function() { - if(!row.tr) return; - row.tr.css('float','none'); - window.setTimeout(function() { - if(!row.tr) return; - row.tr.css('float','left'); - },50); - },100); - return row; - case et2_nextmatch_controller.prototype.VIEW_ROW: - default: - return new et2_dataview_row(this._grid); - } - }, - - /** - * Initializes the action and the object manager. - */ - _initActions: function (_actions) { - // Generate a uid for the action and object manager - var uid = this._widget.id||this.egw.uid(); - - if(_actions == null) _actions = []; - - // Initialize the action manager and add some actions to it - // Only look 1 level deep - var gam = egw_getActionManager(this.egw.appName,true,1); - if(this._actionManager == null) - { - this._actionManager = gam.addAction("actionManager", uid); - } - this._actionManager.updateActions(_actions, this.egw.appName); - var data = this._actionManager.data; - if (data == 'undefined' || !data) - { - data = {}; - } - data.nextmatch = this._widget; - this._actionManager.set_data(data); - - // Set the default execute handler - var self = this; - this._actionManager.setDefaultExecute(function (_action, _senders, _target) { - // Get the selected ids descriptor object - var ids = self._selectionMgr.getSelected(); - - // Pass a reference to the actual widget - if (typeof _action.data == 'undefined' || !_action.data) _action.data = {}; - _action.data.nextmatch = self._widget; - - // Call the nm_action function with the ids - nm_action(_action, _senders, _target, ids); - }); - - // Set the 'Select All' handler - var select_all = this._actionManager.getActionById('select_all'); - if(select_all) - { - select_all.set_onExecute(jQuery.proxy(function(action, selected) { - this._selectionMgr.selectAll(); - }, this)); - } - - // Initialize the object manager - look for application - // object manager 1 level deep - var gom = egw_getObjectManager(this.egw.appName,true,1); - if(this._objectManager == null) - { - this._objectManager = gom.addObject( - new egwActionObjectManager(uid, this._actionManager)); - - this._objectManager.handleKeyPress = function(_keyCode, _shift, _ctrl, _alt) { - for(var i = 0; i < self._actionManager.children.length; i++) - { - if(typeof self._actionManager.children[i].shortcut === 'object' && - self._actionManager.children[i].shortcut && - _keyCode == self._actionManager.children[i].shortcut.keyCode) - { - return this.executeActionImplementation( - { - "keyEvent": { - "keyCode": _keyCode, - "shift": _shift, - "ctrl": _ctrl, - "alt": _alt - } - }, "popup", EGW_AO_EXEC_SELECTED); - } - } - return egwActionObject.prototype.handleKeyPress.call(this, _keyCode,_shift,_ctrl,_alt); - } - - } - this._objectManager.flags = this._objectManager.flags - | EGW_AO_FLAG_DEFAULT_FOCUS | EGW_AO_FLAG_IS_CONTAINER; - - this._init_links_dnd(this._actionManager); - - if(this._selectionMgr) - { - // Need to update the action links for every registered row too - for (var uid in this._selectionMgr._registeredRows) - { - // Get the corresponding entry from the registered rows array - var entry = this._selectionMgr._getRegisteredRowsEntry(uid); - if(entry.ao) - { - entry.ao.updateActionLinks(this._actionLinks); - } - } - } - }, - - /** - * Automatically add dnd support for linking - */ - _init_links_dnd: function() { - var mgr = this._actionManager; - var self = this; - - var drop_action = mgr.getActionById('egw_link_drop'); - var drag_action = mgr.getActionById('egw_link_drag'); - var drop_cancel = mgr.getActionById('egw_cancel_drop'); - - if(!this._actionLinks) - { - this._actionLinks = []; - } - - if (!drop_cancel) - { - // Create a generic cancel action in order to cancel drop action - // applied for all apps plus file and link action. - drop_cancel = mgr.addAction('drop', 'egw_cancel_drop', this.egw.lang('Cancel'), egw.image('cancel'), function(){},true); - drop_cancel.set_group('99'); - drop_cancel.acceptedTypes = drop_cancel.acceptedTypes.concat(Object.keys(egw.user('apps')).concat(['link', 'file'])); - this._actionLinks.push (drop_cancel.id); - } - - // Check if this app supports linking - if(!egw.link_get_registry(this.dataStorePrefix || this.egw.appName, 'query') || - egw.link_get_registry(this.dataStorePrefix || this.egw.appName, 'title')) - { - if(drop_action) - { - drop_action.remove(); - if(this._actionLinks.indexOf(drop_action.id) >= 0) - { - this._actionLinks.splice(this._actionLinks.indexOf(drop_action.id),1); - } - } - if(drag_action) - { - drag_action.remove(); - if(this._actionLinks.indexOf(drag_action.id) >= 0) - { - this._actionLinks.splice(this._actionLinks.indexOf(drag_action.id),1); - } - } - return; - } - - // Don't re-add - if(drop_action == null) - { - // Create the drop action that links entries - drop_action = mgr.addAction('drop', 'egw_link_drop', this.egw.lang('Create link'), egw.image('link'), function(action, source, dropped) { - // Extract link IDs - var links = []; - var id = ''; - for(var i = 0; i < source.length; i++) - { - if(!source[i].id) continue; - id = source[i].id.split('::'); - links.push({app: id[0] == 'filemanager' ? 'link' : id[0], id: id[1]}); - } - if(!links.length) - { - return; - } - - // Link the entries - self.egw.json("EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_link", - dropped.id.split('::').concat([links]), - function(result) { - if(result) - { - for (var i=0; i < this._objectManager.selectedChildren.length; i++) - { - this._widget.refresh(this._objectManager.selectedChildren[i].id,'update'); - } - this._widget.egw().message('Linked'); - // Update the target to show the liks - this._widget.refresh(dropped.id,'update'); - } - }, - self, - true, - self - ).sendRequest(); - - },true); - } - if(this._actionLinks.indexOf(drop_action.id) < 0) - { - this._actionLinks.push(drop_action.id); - } - // Accept other links, and files dragged from the filemanager - // This does not handle files dragged from the desktop. They are - // handled by et2_nextmatch, since it needs DOM stuff - if(drop_action.acceptedTypes.indexOf('link') == -1) - { - drop_action.acceptedTypes.push('link'); - } - - // Don't re-add - if(drag_action == null) - { - // Create drag action that allows linking - drag_action = mgr.addAction('drag', 'egw_link_drag', this.egw.lang('link'), 'link', function(action, selected) { - // Drag helper - list titles. Arbitrarily limited to 10. - var helper = jQuery(document.createElement("div")); - for(var i = 0; i < selected.length && i < 10; i++) - { - var id = selected[i].id.split('::'); - var span = jQuery(document.createElement('span')).appendTo(helper); - self.egw.link_title(id[0],id[1], function(title) { - this.append(title); - this.append('
'); - }, span); - } - // As we wanted to have a general defaul helper interface, we return null here and not using customize helper for links - // TODO: Need to decide if we need to create a customized helper interface for links anyway - //return helper; - return null; - },true); - } - if(this._actionLinks.indexOf(drag_action.id) < 0) - { - this._actionLinks.push(drag_action.id); - } - drag_action.set_dragType('link'); - }, - - /** - * Set the data cache prefix - * Overridden from the parent to re-check automatically the added link dnd - * since the prefix is used in support detection. - */ - setPrefix: function(prefix) { - this._super.apply(this, arguments) - - this._init_links_dnd(this._actionManager); - }, - - /** - * Overwrites the inherited _destroyCallback function in order to be able - * to free the "rowWidget". - */ - _destroyCallback: function (_row) { - // Destroy any widget associated to the row - if (this.entry.widget) - { - this.entry.widget.free(); - this.entry.widget = null; - } - - // Call the inherited function - this._super.apply(this, arguments); - }, - - /** - * Creates the actual data row. - * - * @param _data is an array containing the row data - * @param _tr is the tr into which the data will be inserted - * @param _idx is the index of the row - * @param _entry is the internal row datastructure of the controller, in - * this special case used to store the rowWidget reference, so that it can - * be properly freed. - */ - _rowCallback: function (_data, _tr, _idx, _entry) { - // Let the row provider fill in the data row -- store the returned - // rowWidget inside the _entry - _entry.widget = this._rowProvider.getDataRow( - { "content": _data }, _tr, _idx, this); - }, - - /** - * Returns the names of action links for a given data row -- currently these are - * always the same links, as we controll enabled/disabled over the row - * classes, unless the row UID is "", then it's an 'empty' row. - * - * The empty row placeholder can still have actions, but nothing that requires - * an actual UID. - * - * @TODO: Currently empty row is just add, need to actually filter somehow. Here - * might not be the right place. - * - * @param _data Object The data for the row - * @param _idx int The row index - * @param _uid String The row's ID - * - * @return Array List of action names that valid for the row - */ - _linkCallback: function (_data, _idx, _uid) { - if(_uid.trim() != "") - { - return this._actionLinks; - } - - // No UID, so return a filtered list of actions that doesn't need a UID - var links = []; - try { - links = typeof this._widget.options.settings.placeholder_actions != 'undefined' ? - this._widget.options.settings.placeholder_actions : (this._widget.options.add ? ["add"] : []); - // Make sure that placeholder actions are defined and existed in client-side, - // otherwise do not set them as placeholder. for instance actions with - // attribute hideOnMobile do not get sent to client-side. - var action_search = function(current) { - if (typeof this._widget.options.actions[current] !== 'undefined') return true; - // Check children - for(var action in this._widget.options.actions) - { - action = this._widget.options.actions[action]; - if( action.children && action.children[current]) return true; - } - return false; - } - links = links.filter(action_search, this); - } catch (e) { - } - - return links; - }, - - - /** - * Overridden from the parent to also process any additional data that - * the data source adds, such as readonlys and additonal content. - * For example, non-numeric IDs in rows are added to the content manager - */ - _fetchCallback: function (_response) { - var nm = this.self._widget; - if(!nm) - { - // Nextmatch either not connected, or it tried to destroy this - // but the server returned something - return; - } - // Readonlys - // Other stuff - for(var i in _response.rows) - { - if(jQuery.isNumeric(i)) continue; - if(i == 'sel_options') - { - var mgr = nm.getArrayMgr(i); - for(var id in _response.rows.sel_options) - { - mgr.data[id] = _response.rows.sel_options[id]; - var select = nm.getWidgetById(id); - if(select && select.set_select_options) - { - select.set_select_options(_response.rows.sel_options[id]); - } - // Clear rowProvider internal cache so it uses new values - if(id == 'cat_id') - { - this.self._rowProvider.categories = null; - } - // update array mgr so select widgets in row also get refreshed options - nm.getParent().getArrayMgr('sel_options').data[id] = _response.rows.sel_options[id]; - } - } - else - { - var mgr = nm.getArrayMgr('content'); - mgr.data[i] = _response.rows[i]; - - // It's not enough to just update the data, the widgets need to - // be updated too, if there are matching widgets. - var widget = nm.getWidgetById(i); - if(widget && widget.set_value) - { - widget.set_value(mgr.getEntry(i)); - } - } - } - // Might be trying to disable a column - var col_refresh = false; - for(var column_index = 0; column_index < nm.columns.length; column_index++) - { - if(typeof nm.columns[column_index].disabled === 'string' && - nm.columns[column_index].disabled[0] === '@') - { - col_refresh = true; - nm.dataview.columnMgr.getColumnById('col_'+column_index) - .set_visibility( - nm.getArrayMgr('content').parseBoolExpression(nm.columns[column_index].disabled) ? - ET2_COL_VISIBILITY_DISABLED : - nm.columns[column_index].visible - ); - } - } - if(col_refresh) - { - nm.dataview.columnMgr.updated = true; - nm.dataview._updateColumns(); - } - - // If we're doing an autorefresh and the count decreases, preserve the - // selection or it will be lost when the grid rows are shuffled. Increases - // are fine though. - if(this.self && this.self.kept_selection == null && - !this.refresh && this.self._grid.getTotalCount() > _response.total) - { - this.self.keepSelection(); - } - - // Call the inherited function - this._super.apply(this, arguments); - - // Restore selection, if passed - if(this.self && this.self.kept_selection && this.self._selectionMgr) - { - if(this.self.kept_selection.all) - { - this.self._selectionMgr.selectAll(); - } - for(var i = (this.self.kept_selection.ids.length || 1)-1; i >= 0; i--) - { - // Only keep the selected if they came back in the fetch - if(_response.order.indexOf(this.self.kept_selection.ids[i]) >= 0) - { - this.self._selectionMgr.setSelected(this.self.kept_selection.ids[i],true); - this.self.kept_selection.ids.splice(i,1); - } - else - { - this.self.kept_selection.ids.splice(i,1); - } - } - if(this.self.kept_focus && _response.order.indexOf(this.self.kept_focus) >= 0) - { - this.self._selectionMgr.setFocused(this.self.kept_focus,true); - } - // Re-expanding rows handled in et2_extension_nextmatch_rowProvider - // Expansions might still be valid, so we don't clear them - if(this.self.kept_selection != null && typeof this.self.kept_selection.ids != 'undefined' && this.self.kept_selection.ids.length == 0) - { - this.self.kept_selection = null; - } - this.self.kept_focus = null; - } - }, - - /** - * Execute the select callback when the row selection changes - */ - _selectCallback: function(action,senders) - { - if(typeof senders == "undefined") - { - senders = []; - } - if(!this._widget) return; - - // inform mobile framework about nm selections, need to update status of header objects on selection - if (egwIsMobile()) framework.nm_onselect_ctrl(this._widget, action, senders); - - this._widget.onselect.call(this._widget, action,senders); - }, - - /** -- Implementation of et2_IDataProvider -- **/ - - - dataFetch: function (_queriedRange, _callback, _context) { - - // Merge the parent id into the _queriedRange if it is set - if (this._parentId !== null) - { - _queriedRange["parent_id"] = this._parentId; - } - - // sub-levels dont have there own _filters object, need to use the one from parent (or it's parents parent) - var obj = this; - while((typeof obj._filters == 'undefined' || jQuery.isEmptyObject(obj._filters)) && obj._parentController) - { - obj = obj._parentController; - } - - // Pass the fetch call to the API, multiplex the data about the - // nextmatch instance into the call. - this.egw.dataFetch( - this._widget.getInstanceManager().etemplate_exec_id || this._execId, - _queriedRange, - obj._filters, - this._widgetId, - _callback, - _context); - }, - - dataRegisterUID: function (_uid, _callback, _context) { - this.egw.dataRegisterUID(_uid, _callback, _context, - this._widget.getInstanceManager().etemplate_exec_id || this._execId, - this._widgetId - ); - }, - - dataUnregisterUID: function () { - // Overwritten in the constructor - } - -});}).call(this); - - +var et2_nextmatch_controller = /** @class */ (function (_super) { + __extends(et2_nextmatch_controller, _super); + /** + * Initializes the nextmatch controller. + * + * @param _parentController is the parent nextmatch controller instance + * @param _egw is the api instance + * @param _execId is the execId of the etemplate + * @param _widget is the nextmatch-widget we are fetching data for. + * @param _grid is the grid the grid controller will be controlling + * @param _rowProvider is the nextmatch row provider instance. + * @param _objectManager is the parent object manager (if null, the object + * manager) will be created using + * @param _actionLinks contains the action links + * @param _actions contains the actions, may be null if an object manager + * is given. + * @memberOf et2_nextmatch_controller + */ + function et2_nextmatch_controller(_parentController, _egw, _execId, _widget, _parentId, _grid, _rowProvider, _actionLinks, _objectManager, _actions) { + var _this = + // Call the parent et2_dataview_controller constructor + _super.call(this, _parentController, _grid) || this; + _this.setDataProvider(_this); + _this.setRowCallback(_this._rowCallback); + _this.setLinkCallback(_this._linkCallback); + _this.setContext(_this); + // Copy the egw reference + _this.egw = _egw; + // Keep a reference to the widget + _this._widget = _widget; + // Copy the given parameters + _this._actionLinks = _actionLinks; + _this._execId = _execId; + // Get full widget ID, including path + var id = _widget.getArrayMgr('content').getPath(); + if (typeof id == 'string') { + _this._widgetId = id; + } + else if (id.length === 1) { + _this._widgetId = id[0]; + } + else { + _this._widgetId = id.shift() + '[' + id.join('][') + ']'; + } + _this._parentId = _parentId; + _this._rowProvider = _rowProvider; + // Initialize the action and the object manager + // _initActions calls _init_link_dnd, which uses this._actionLinks, + // so this must happen after the above parameter copying + if (!_objectManager) { + _this._initActions(_actions); + } + else { + _this._actionManager = null; + _this._objectManager = _objectManager; + } + _this.setActionObjectManager(_this._objectManager); + // Add our selection callback to selection manager + var self = _this; + _this._objectManager.setSelectedCallback = function () { self._selectCallback.apply(self, [this, arguments]); }; + // We start with no filters + _this._filters = {}; + // Keep selection across filter changes + _this.kept_selection = null; + _this.kept_focus = null; + _this.kept_expansion = []; + // Directly use the API-Implementation of dataRegisterUID and + // dataUnregisterUID + _this.dataUnregisterUID = _egw.dataUnregisterUID; + // Default to rows + _this._view = et2_nextmatch_controller.VIEW_ROW; + return _this; + } + et2_nextmatch_controller.prototype.destroy = function () { + // If the actionManager variable is set, the object- and actionManager + // were created by this instance -- clear them + if (this._actionManager) { + this._objectManager.remove(); + this._actionManager.remove(); + } + _super.prototype.destroy.call(this); + }; + /** + * Updates the filter instance. + */ + et2_nextmatch_controller.prototype.setFilters = function (_filters) { + // Update the filters + this._filters = _filters; + }; + /** + * Keep the selection, if possible, across a data fetch and restore it + * after + */ + et2_nextmatch_controller.prototype.keepSelection = function () { + this.kept_selection = this._selectionMgr ? this._selectionMgr.getSelected() : null; + this.kept_focus = this._selectionMgr && this._selectionMgr._focusedEntry ? + this._selectionMgr._focusedEntry.uid || null : null; + // Find expanded rows + var nm = this._widget; + var controller = this; + jQuery('.arrow.opened', this._widget.getDOMNode(this._widget)).each(function () { + var entry = controller.getRowByNode(this); + if (entry && entry.uid) { + controller.kept_expansion.push(entry.uid); + } + }); + }; + et2_nextmatch_controller.prototype.getObjectManager = function () { + return this._objectManager; + }; + /** + * Deletes a row from the grid + * + * @param {string} uid + */ + et2_nextmatch_controller.prototype.deleteRow = function (uid) { + var entry = this._selectionMgr._getRegisteredRowsEntry(uid); + // Unselect + this._selectionMgr.setSelected(uid, false); + if (entry && entry.idx !== null) { + // This will remove the row, but add an empty to the end. + // That's OK, because it will be removed when we update the row count + this._grid.deleteRow(entry.idx); + // Trigger controller to remove from internals + this.egw.dataStoreUID(uid, null); + // Stop caring about this ID + this.egw.dataDeleteUID(uid); + // Remove from internal map + delete this._indexMap[entry.idx]; + // Update the indices of all elements after the current one + for (var mapIndex = entry.idx + 1; typeof this._indexMap[mapIndex] !== 'undefined'; mapIndex++) { + var entry = this._indexMap[mapIndex]; + entry.idx = mapIndex - 1; + this._indexMap[mapIndex - 1] = entry; + // Update selection mgr too + if (entry.uid && typeof this._selectionMgr._registeredRows[entry.uid] !== 'undefined') { + var reg = this._selectionMgr._getRegisteredRowsEntry(entry.uid); + reg.idx = entry.idx; + if (reg.ao && reg.ao._index) + reg.ao._index = entry.idx; + } + } + // Remove last one, it was moved to mapIndex-1 before increment + delete this._indexMap[mapIndex - 1]; + // Not needed, they share by reference + // this._selectionMgr.setIndexMap(this._indexMap); + } + }; + /** -- PRIVATE FUNCTIONS -- **/ + /** + * Create a new row, either normal or tiled + * + * @param {type} ctx + * @returns {et2_dataview_container} + */ + et2_nextmatch_controller.prototype._createRow = function (ctx) { + switch (this._view) { + case et2_nextmatch_controller.VIEW_TILE: + var row = new et2_dataview_view_tile_1.et2_dataview_tile(this._grid); + // Try to overcome chrome rendering issue where float is not + // applied properly, leading to incomplete rows + window.setTimeout(function () { + if (!row.tr) + return; + row.tr.css('float', 'none'); + window.setTimeout(function () { + if (!row.tr) + return; + row.tr.css('float', 'left'); + }, 50); + }, 100); + return row; + case et2_nextmatch_controller.VIEW_ROW: + default: + return new et2_dataview_view_row_1.et2_dataview_row(this._grid); + } + }; + /** + * Initializes the action and the object manager. + */ + et2_nextmatch_controller.prototype._initActions = function (_actions) { + // Generate a uid for the action and object manager + var uid = this._widget.id || this.egw.uid(); + if (_actions == null) + _actions = []; + // Initialize the action manager and add some actions to it + // Only look 1 level deep + var gam = egw_getActionManager(this.egw.appName, true, 1); + if (this._actionManager == null) { + this._actionManager = gam.addAction("actionManager", uid); + } + this._actionManager.updateActions(_actions, this.egw.appName); + var data = this._actionManager.data; + if (data == 'undefined' || !data) { + data = {}; + } + data.nextmatch = this._widget; + this._actionManager.set_data(data); + // Set the default execute handler + var self = this; + this._actionManager.setDefaultExecute(function (_action, _senders, _target) { + // Get the selected ids descriptor object + var ids = self._selectionMgr.getSelected(); + // Pass a reference to the actual widget + if (typeof _action.data == 'undefined' || !_action.data) + _action.data = {}; + _action.data.nextmatch = self._widget; + // Call the nm_action function with the ids + nm_action(_action, _senders, _target, ids); + }); + // Set the 'Select All' handler + var select_all = this._actionManager.getActionById('select_all'); + if (select_all) { + select_all.set_onExecute(jQuery.proxy(function (action, selected) { + this._selectionMgr.selectAll(); + }, this)); + } + // Initialize the object manager - look for application + // object manager 1 level deep + var gom = egw_getObjectManager(this.egw.appName, true, 1); + if (this._objectManager == null) { + this._objectManager = gom.addObject(new egwActionObjectManager(uid, this._actionManager)); + this._objectManager.handleKeyPress = function (_keyCode, _shift, _ctrl, _alt) { + for (var i = 0; i < self._actionManager.children.length; i++) { + if (typeof self._actionManager.children[i].shortcut === 'object' && + self._actionManager.children[i].shortcut && + _keyCode == self._actionManager.children[i].shortcut.keyCode) { + return this.executeActionImplementation({ + "keyEvent": { + "keyCode": _keyCode, + "shift": _shift, + "ctrl": _ctrl, + "alt": _alt + } + }, "popup", EGW_AO_EXEC_SELECTED); + } + } + return egwActionObject.prototype.handleKeyPress.call(this, _keyCode, _shift, _ctrl, _alt); + }; + } + this._objectManager.flags = this._objectManager.flags + | EGW_AO_FLAG_DEFAULT_FOCUS | EGW_AO_FLAG_IS_CONTAINER; + this._init_links_dnd(); + if (this._selectionMgr) { + // Need to update the action links for every registered row too + for (var uid in this._selectionMgr._registeredRows) { + // Get the corresponding entry from the registered rows array + var entry = this._selectionMgr._getRegisteredRowsEntry(uid); + if (entry.ao) { + entry.ao.updateActionLinks(this._actionLinks); + } + } + } + }; + /** + * Automatically add dnd support for linking + */ + et2_nextmatch_controller.prototype._init_links_dnd = function () { + var mgr = this._actionManager; + var self = this; + var drop_action = mgr.getActionById('egw_link_drop'); + var drag_action = mgr.getActionById('egw_link_drag'); + var drop_cancel = mgr.getActionById('egw_cancel_drop'); + if (!this._actionLinks) { + this._actionLinks = []; + } + if (!drop_cancel) { + // Create a generic cancel action in order to cancel drop action + // applied for all apps plus file and link action. + drop_cancel = mgr.addAction('drop', 'egw_cancel_drop', this.egw.lang('Cancel'), egw.image('cancel'), function () { }, true); + drop_cancel.set_group('99'); + drop_cancel.acceptedTypes = drop_cancel.acceptedTypes.concat(Object.keys(egw.user('apps')).concat(['link', 'file'])); + this._actionLinks.push(drop_cancel.id); + } + // Check if this app supports linking + if (!egw.link_get_registry(this.dataStorePrefix || this.egw.appName, 'query') || + egw.link_get_registry(this.dataStorePrefix || this.egw.appName, 'title')) { + if (drop_action) { + drop_action.remove(); + if (this._actionLinks.indexOf(drop_action.id) >= 0) { + this._actionLinks.splice(this._actionLinks.indexOf(drop_action.id), 1); + } + } + if (drag_action) { + drag_action.remove(); + if (this._actionLinks.indexOf(drag_action.id) >= 0) { + this._actionLinks.splice(this._actionLinks.indexOf(drag_action.id), 1); + } + } + return; + } + // Don't re-add + if (drop_action == null) { + // Create the drop action that links entries + drop_action = mgr.addAction('drop', 'egw_link_drop', this.egw.lang('Create link'), egw.image('link'), function (action, source, dropped) { + // Extract link IDs + var links = []; + var id = ''; + for (var i = 0; i < source.length; i++) { + if (!source[i].id) + continue; + id = source[i].id.split('::'); + links.push({ app: id[0] == 'filemanager' ? 'link' : id[0], id: id[1] }); + } + if (!links.length) { + return; + } + // Link the entries + self.egw.json("EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_link", dropped.id.split('::').concat([links]), function (result) { + if (result) { + for (var i = 0; i < this._objectManager.selectedChildren.length; i++) { + this._widget.refresh(this._objectManager.selectedChildren[i].id, 'update'); + } + this._widget.egw().message('Linked'); + // Update the target to show the liks + this._widget.refresh(dropped.id, 'update'); + } + }, self, true, self).sendRequest(); + }, true); + } + if (this._actionLinks.indexOf(drop_action.id) < 0) { + this._actionLinks.push(drop_action.id); + } + // Accept other links, and files dragged from the filemanager + // This does not handle files dragged from the desktop. They are + // handled by et2_nextmatch, since it needs DOM stuff + if (drop_action.acceptedTypes.indexOf('link') == -1) { + drop_action.acceptedTypes.push('link'); + } + // Don't re-add + if (drag_action == null) { + // Create drag action that allows linking + drag_action = mgr.addAction('drag', 'egw_link_drag', this.egw.lang('link'), 'link', function (action, selected) { + // Drag helper - list titles. Arbitrarily limited to 10. + var helper = jQuery(document.createElement("div")); + for (var i = 0; i < selected.length && i < 10; i++) { + var id = selected[i].id.split('::'); + var span = jQuery(document.createElement('span')).appendTo(helper); + self.egw.link_title(id[0], id[1], function (title) { + this.append(title); + this.append('
'); + }, span); + } + // As we wanted to have a general defaul helper interface, we return null here and not using customize helper for links + // TODO: Need to decide if we need to create a customized helper interface for links anyway + //return helper; + return null; + }, true); + } + if (this._actionLinks.indexOf(drag_action.id) < 0) { + this._actionLinks.push(drag_action.id); + } + drag_action.set_dragType('link'); + }; + /** + * Set the data cache prefix + * Overridden from the parent to re-check automatically the added link dnd + * since the prefix is used in support detection. + */ + et2_nextmatch_controller.prototype.setPrefix = function (prefix) { + _super.prototype.setPrefix.call(this, prefix); + this._init_links_dnd(); + }; + /** + * Overwrites the inherited _destroyCallback function in order to be able + * to free the "rowWidget". + */ + et2_nextmatch_controller.prototype._destroyCallback = function (_row) { + // Destroy any widget associated to the row + if (this.entry.widget) { + this.entry.widget.destroy(); + this.entry.widget = null; + } + // Call the inherited function + _super.prototype._destroyCallback.call(this, _row); + }; + /** + * Creates the actual data row. + * + * @param _data is an array containing the row data + * @param _tr is the tr into which the data will be inserted + * @param _idx is the index of the row + * @param _entry is the internal row datastructure of the controller, in + * this special case used to store the rowWidget reference, so that it can + * be properly freed. + */ + et2_nextmatch_controller.prototype._rowCallback = function (_data, _tr, _idx, _entry) { + // Let the row provider fill in the data row -- store the returned + // rowWidget inside the _entry + _entry.widget = this._rowProvider.getDataRow({ "content": _data }, _tr, _idx, this); + }; + /** + * Returns the names of action links for a given data row -- currently these are + * always the same links, as we controll enabled/disabled over the row + * classes, unless the row UID is "", then it's an 'empty' row. + * + * The empty row placeholder can still have actions, but nothing that requires + * an actual UID. + * + * @TODO: Currently empty row is just add, need to actually filter somehow. Here + * might not be the right place. + * + * @param _data Object The data for the row + * @param _idx int The row index + * @param _uid String The row's ID + * + * @return Array List of action names that valid for the row + */ + et2_nextmatch_controller.prototype._linkCallback = function (_data, _idx, _uid) { + if (_uid.trim() != "") { + return this._actionLinks; + } + // No UID, so return a filtered list of actions that doesn't need a UID + var links = []; + try { + links = typeof this._widget.options.settings.placeholder_actions != 'undefined' ? + this._widget.options.settings.placeholder_actions : (this._widget.options.add ? ["add"] : []); + // Make sure that placeholder actions are defined and existed in client-side, + // otherwise do not set them as placeholder. for instance actions with + // attribute hideOnMobile do not get sent to client-side. + var action_search = function (current) { + if (typeof this._widget.options.actions[current] !== 'undefined') + return true; + // Check children + for (var action in this._widget.options.actions) { + action = this._widget.options.actions[action]; + if (action.children && action.children[current]) + return true; + } + return false; + }; + links = links.filter(action_search, this); + } + catch (e) { + } + return links; + }; + /** + * Overridden from the parent to also process any additional data that + * the data source adds, such as readonlys and additonal content. + * For example, non-numeric IDs in rows are added to the content manager + */ + et2_nextmatch_controller.prototype._fetchCallback = function (_response) { + var nm = this.self._widget; + if (!nm) { + // Nextmatch either not connected, or it tried to destroy this + // but the server returned something + return; + } + // Readonlys + // Other stuff + for (var i in _response.rows) { + if (jQuery.isNumeric(i)) + continue; + if (i == 'sel_options') { + var mgr = nm.getArrayMgr(i); + for (var id in _response.rows.sel_options) { + mgr.data[id] = _response.rows.sel_options[id]; + var select = nm.getWidgetById(id); + if (select && select.set_select_options) { + select.set_select_options(_response.rows.sel_options[id]); + } + // Clear rowProvider internal cache so it uses new values + if (id == 'cat_id') { + this.self._rowProvider.categories = null; + } + // update array mgr so select widgets in row also get refreshed options + nm.getParent().getArrayMgr('sel_options').data[id] = _response.rows.sel_options[id]; + } + } + else { + var mgr = nm.getArrayMgr('content'); + mgr.data[i] = _response.rows[i]; + // It's not enough to just update the data, the widgets need to + // be updated too, if there are matching widgets. + var widget = nm.getWidgetById(i); + if (widget && widget.set_value) { + widget.set_value(mgr.getEntry(i)); + } + } + } + // Might be trying to disable a column + var col_refresh = false; + for (var column_index = 0; column_index < nm.columns.length; column_index++) { + if (typeof nm.columns[column_index].disabled === 'string' && + nm.columns[column_index].disabled[0] === '@') { + col_refresh = true; + nm.dataview.columnMgr.getColumnById('col_' + column_index) + .set_visibility(nm.getArrayMgr('content').parseBoolExpression(nm.columns[column_index].disabled) ? + et2_dataview_column.ET2_COL_VISIBILITY_DISABLED : + nm.columns[column_index].visible); + } + } + if (col_refresh) { + nm.dataview.columnMgr.updated(); + nm.dataview._updateColumns(); + } + // If we're doing an autorefresh and the count decreases, preserve the + // selection or it will be lost when the grid rows are shuffled. Increases + // are fine though. + if (this.self && this.self.kept_selection == null && + !this.refresh && this.self._grid.getTotalCount() > _response.total) { + this.self.keepSelection(); + } + // Call the inherited function + _super.prototype._fetchCallback.apply(this, arguments); + // Restore selection, if passed + if (this.self && this.self.kept_selection && this.self._selectionMgr) { + if (this.self.kept_selection.all) { + this.self._selectionMgr.selectAll(); + } + for (var i = (this.self.kept_selection.ids.length || 1) - 1; i >= 0; i--) { + // Only keep the selected if they came back in the fetch + if (_response.order.indexOf(this.self.kept_selection.ids[i]) >= 0) { + this.self._selectionMgr.setSelected(this.self.kept_selection.ids[i], true); + this.self.kept_selection.ids.splice(i, 1); + } + else { + this.self.kept_selection.ids.splice(i, 1); + } + } + if (this.self.kept_focus && _response.order.indexOf(this.self.kept_focus) >= 0) { + this.self._selectionMgr.setFocused(this.self.kept_focus, true); + } + // Re-expanding rows handled in et2_extension_nextmatch_rowProvider + // Expansions might still be valid, so we don't clear them + if (this.self.kept_selection != null && typeof this.self.kept_selection.ids != 'undefined' && this.self.kept_selection.ids.length == 0) { + this.self.kept_selection = null; + } + this.self.kept_focus = null; + } + }; + /** + * Execute the select callback when the row selection changes + */ + et2_nextmatch_controller.prototype._selectCallback = function (action, senders) { + if (typeof senders == "undefined") { + senders = []; + } + if (!this._widget) + return; + // inform mobile framework about nm selections, need to update status of header objects on selection + if (egwIsMobile()) + framework.nm_onselect_ctrl(this._widget, action, senders); + this._widget.onselect.call(this._widget, action, senders); + }; + /** -- Implementation of et2_IDataProvider -- **/ + et2_nextmatch_controller.prototype.dataFetch = function (_queriedRange, _callback, _context) { + // Merge the parent id into the _queriedRange if it is set + if (this._parentId !== null) { + _queriedRange["parent_id"] = this._parentId; + } + // sub-levels dont have there own _filters object, need to use the one from parent (or it's parents parent) + var obj = this; + while ((typeof obj._filters == 'undefined' || jQuery.isEmptyObject(obj._filters)) && obj._parentController) { + obj = obj._parentController; + } + // Pass the fetch call to the API, multiplex the data about the + // nextmatch instance into the call. + this.egw.dataFetch(this._widget.getInstanceManager().etemplate_exec_id || this._execId, _queriedRange, obj._filters, this._widgetId, _callback, _context); + }; + et2_nextmatch_controller.prototype.dataRegisterUID = function (_uid, _callback, _context) { + this.egw.dataRegisterUID(_uid, _callback, _context, this._widget.getInstanceManager().etemplate_exec_id || this._execId, this._widgetId); + }; + et2_nextmatch_controller.prototype.dataUnregisterUID = function () { + // Overwritten in the constructor + }; + // Display constants + et2_nextmatch_controller.VIEW_ROW = 'row'; + et2_nextmatch_controller.VIEW_TILE = 'tile'; + return et2_nextmatch_controller; +}(et2_dataview_controller)); +exports.et2_nextmatch_controller = et2_nextmatch_controller; +//# sourceMappingURL=et2_extension_nextmatch_controller.js.map \ No newline at end of file diff --git a/api/js/etemplate/et2_extension_nextmatch_controller.ts b/api/js/etemplate/et2_extension_nextmatch_controller.ts new file mode 100644 index 0000000000..2c5d10eb90 --- /dev/null +++ b/api/js/etemplate/et2_extension_nextmatch_controller.ts @@ -0,0 +1,769 @@ +/** + * EGroupware eTemplate2 - Class which contains a the data model for nextmatch widgets + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package etemplate + * @subpackage api + * @link http://www.egroupware.org + * @author Andreas Stöckel + * @copyright Stylite 2012 + * @version $Id$ + * + +/*egw:uses + et2_core_common; + et2_core_inheritance; + + et2_dataview_view_row; + et2_dataview_controller; + et2_dataview_interfaces; + et2_dataview_view_tile; + + et2_extension_nextmatch_actions; // Contains nm_action + + egw_data; +*/ + +import {et2_IDataProvider} from "./et2_dataview_interfaces"; +import {et2_dataview_row} from "./et2_dataview_view_row"; +import {et2_dataview_tile} from "./et2_dataview_view_tile"; + +/** + * @augments et2_dataview_controller + */ +export class et2_nextmatch_controller extends et2_dataview_controller implements et2_IDataProvider +{ + // Display constants + public static readonly VIEW_ROW : string = 'row'; + public static readonly VIEW_TILE: string = 'tile'; + + /** + * Initializes the nextmatch controller. + * + * @param _parentController is the parent nextmatch controller instance + * @param _egw is the api instance + * @param _execId is the execId of the etemplate + * @param _widget is the nextmatch-widget we are fetching data for. + * @param _grid is the grid the grid controller will be controlling + * @param _rowProvider is the nextmatch row provider instance. + * @param _objectManager is the parent object manager (if null, the object + * manager) will be created using + * @param _actionLinks contains the action links + * @param _actions contains the actions, may be null if an object manager + * is given. + * @memberOf et2_nextmatch_controller + */ + constructor( _parentController, _egw, _execId, _widget, _parentId, + _grid, _rowProvider, _actionLinks, _objectManager, _actions) + { + + // Call the parent et2_dataview_controller constructor + super(_parentController, _grid); + + this.setDataProvider(this); + this.setRowCallback(this._rowCallback); + this.setLinkCallback(this._linkCallback); + this.setContext(this); + + // Copy the egw reference + this.egw = _egw; + + // Keep a reference to the widget + this._widget = _widget; + + // Copy the given parameters + this._actionLinks = _actionLinks; + this._execId = _execId; + + // Get full widget ID, including path + var id = _widget.getArrayMgr('content').getPath(); + + if(typeof id == 'string') + { + this._widgetId = id; + } + else if (id.length === 1) + { + this._widgetId = id[0]; + } + else + { + this._widgetId = id.shift() + '[' + id.join('][') + ']'; + } + this._parentId = _parentId; + this._rowProvider = _rowProvider; + + // Initialize the action and the object manager + // _initActions calls _init_link_dnd, which uses this._actionLinks, + // so this must happen after the above parameter copying + if (!_objectManager) + { + this._initActions(_actions); + } + else + { + this._actionManager = null; + this._objectManager = _objectManager; + } + + this.setActionObjectManager(this._objectManager); + + // Add our selection callback to selection manager + var self = this; + this._objectManager.setSelectedCallback = function() {self._selectCallback.apply(self,[this,arguments]);}; + + // We start with no filters + this._filters = {}; + + // Keep selection across filter changes + this.kept_selection = null; + this.kept_focus = null; + this.kept_expansion = []; + + // Directly use the API-Implementation of dataRegisterUID and + // dataUnregisterUID + this.dataUnregisterUID = _egw.dataUnregisterUID; + + // Default to rows + this._view = et2_nextmatch_controller.VIEW_ROW; + + } + + destroy( ) + { + // If the actionManager variable is set, the object- and actionManager + // were created by this instance -- clear them + if (this._actionManager) + { + this._objectManager.remove(); + this._actionManager.remove(); + } + + super.destroy(); + } + + /** + * Updates the filter instance. + */ + setFilters( _filters) + { + // Update the filters + this._filters = _filters; + } + + /** + * Keep the selection, if possible, across a data fetch and restore it + * after + */ + keepSelection( ) + { + this.kept_selection = this._selectionMgr ? this._selectionMgr.getSelected() : null; + this.kept_focus = this._selectionMgr && this._selectionMgr._focusedEntry ? + this._selectionMgr._focusedEntry.uid || null : null; + + // Find expanded rows + var nm = this._widget; + var controller = this; + jQuery('.arrow.opened',this._widget.getDOMNode(this._widget)).each(function() { + var entry = controller.getRowByNode(this); + if(entry && entry.uid) + { + controller.kept_expansion.push(entry.uid); + } + }); + } + + getObjectManager( ) + { + return this._objectManager; + } + + /** + * Deletes a row from the grid + * + * @param {string} uid + */ + deleteRow( uid) + { + var entry = this._selectionMgr._getRegisteredRowsEntry(uid); + + // Unselect + this._selectionMgr.setSelected(uid,false); + + if(entry && entry.idx !== null) + { + // This will remove the row, but add an empty to the end. + // That's OK, because it will be removed when we update the row count + this._grid.deleteRow(entry.idx); + + // Trigger controller to remove from internals + this.egw.dataStoreUID(uid,null); + // Stop caring about this ID + this.egw.dataDeleteUID(uid); + // Remove from internal map + delete this._indexMap[entry.idx]; + + // Update the indices of all elements after the current one + for(var mapIndex = entry.idx + 1; typeof this._indexMap[mapIndex] !== 'undefined'; mapIndex++) + { + var entry = this._indexMap[mapIndex]; + entry.idx = mapIndex-1; + this._indexMap[mapIndex-1] = entry; + + // Update selection mgr too + if(entry.uid && typeof this._selectionMgr._registeredRows[entry.uid] !== 'undefined') + { + var reg = this._selectionMgr._getRegisteredRowsEntry(entry.uid); + reg.idx = entry.idx; + if(reg.ao && reg.ao._index) reg.ao._index = entry.idx; + } + } + // Remove last one, it was moved to mapIndex-1 before increment + delete this._indexMap[mapIndex-1]; + + // Not needed, they share by reference + // this._selectionMgr.setIndexMap(this._indexMap); + } + } + + /** -- PRIVATE FUNCTIONS -- **/ + + /** + * Create a new row, either normal or tiled + * + * @param {type} ctx + * @returns {et2_dataview_container} + */ + _createRow( ctx) + { + switch(this._view) + { + case et2_nextmatch_controller.VIEW_TILE: + var row = new et2_dataview_tile(this._grid); + // Try to overcome chrome rendering issue where float is not + // applied properly, leading to incomplete rows + window.setTimeout(function() { + if(!row.tr) return; + row.tr.css('float','none'); + window.setTimeout(function() { + if(!row.tr) return; + row.tr.css('float','left'); + },50); + },100); + return row; + case et2_nextmatch_controller.VIEW_ROW: + default: + return new et2_dataview_row(this._grid); + } + } + + /** + * Initializes the action and the object manager. + */ + _initActions( _actions) + { + // Generate a uid for the action and object manager + var uid = this._widget.id||this.egw.uid(); + + if(_actions == null) _actions = []; + + // Initialize the action manager and add some actions to it + // Only look 1 level deep + var gam = egw_getActionManager(this.egw.appName,true,1); + if(this._actionManager == null) + { + this._actionManager = gam.addAction("actionManager", uid); + } + this._actionManager.updateActions(_actions, this.egw.appName); + var data = this._actionManager.data; + if (data == 'undefined' || !data) + { + data = {}; + } + data.nextmatch = this._widget; + this._actionManager.set_data(data); + + // Set the default execute handler + var self = this; + this._actionManager.setDefaultExecute(function (_action, _senders, _target) { + // Get the selected ids descriptor object + var ids = self._selectionMgr.getSelected(); + + // Pass a reference to the actual widget + if (typeof _action.data == 'undefined' || !_action.data) _action.data = {}; + _action.data.nextmatch = self._widget; + + // Call the nm_action function with the ids + nm_action(_action, _senders, _target, ids); + }); + + // Set the 'Select All' handler + var select_all = this._actionManager.getActionById('select_all'); + if(select_all) + { + select_all.set_onExecute(jQuery.proxy(function(action, selected) { + this._selectionMgr.selectAll(); + }, this)); + } + + // Initialize the object manager - look for application + // object manager 1 level deep + var gom = egw_getObjectManager(this.egw.appName,true,1); + if(this._objectManager == null) + { + this._objectManager = gom.addObject( + new egwActionObjectManager(uid, this._actionManager)); + + this._objectManager.handleKeyPress = function(_keyCode, _shift, _ctrl, _alt) { + for(var i = 0; i < self._actionManager.children.length; i++) + { + if(typeof self._actionManager.children[i].shortcut === 'object' && + self._actionManager.children[i].shortcut && + _keyCode == self._actionManager.children[i].shortcut.keyCode) + { + return this.executeActionImplementation( + { + "keyEvent": { + "keyCode": _keyCode, + "shift": _shift, + "ctrl": _ctrl, + "alt": _alt + } + }, "popup", EGW_AO_EXEC_SELECTED); + } + } + return egwActionObject.prototype.handleKeyPress.call(this, _keyCode,_shift,_ctrl,_alt); + } + + } + this._objectManager.flags = this._objectManager.flags + | EGW_AO_FLAG_DEFAULT_FOCUS | EGW_AO_FLAG_IS_CONTAINER; + + this._init_links_dnd(); + + if(this._selectionMgr) + { + // Need to update the action links for every registered row too + for (var uid in this._selectionMgr._registeredRows) + { + // Get the corresponding entry from the registered rows array + var entry = this._selectionMgr._getRegisteredRowsEntry(uid); + if(entry.ao) + { + entry.ao.updateActionLinks(this._actionLinks); + } + } + } + } + + /** + * Automatically add dnd support for linking + */ + _init_links_dnd( ) + { + var mgr = this._actionManager; + var self = this; + + var drop_action = mgr.getActionById('egw_link_drop'); + var drag_action = mgr.getActionById('egw_link_drag'); + var drop_cancel = mgr.getActionById('egw_cancel_drop'); + + if(!this._actionLinks) + { + this._actionLinks = []; + } + + if (!drop_cancel) + { + // Create a generic cancel action in order to cancel drop action + // applied for all apps plus file and link action. + drop_cancel = mgr.addAction('drop', 'egw_cancel_drop', this.egw.lang('Cancel'), egw.image('cancel'), function(){},true); + drop_cancel.set_group('99'); + drop_cancel.acceptedTypes = drop_cancel.acceptedTypes.concat(Object.keys(egw.user('apps')).concat(['link', 'file'])); + this._actionLinks.push (drop_cancel.id); + } + + // Check if this app supports linking + if(!egw.link_get_registry(this.dataStorePrefix || this.egw.appName, 'query') || + egw.link_get_registry(this.dataStorePrefix || this.egw.appName, 'title')) + { + if(drop_action) + { + drop_action.remove(); + if(this._actionLinks.indexOf(drop_action.id) >= 0) + { + this._actionLinks.splice(this._actionLinks.indexOf(drop_action.id),1); + } + } + if(drag_action) + { + drag_action.remove(); + if(this._actionLinks.indexOf(drag_action.id) >= 0) + { + this._actionLinks.splice(this._actionLinks.indexOf(drag_action.id),1); + } + } + return; + } + + // Don't re-add + if(drop_action == null) + { + // Create the drop action that links entries + drop_action = mgr.addAction('drop', 'egw_link_drop', this.egw.lang('Create link'), egw.image('link'), function(action, source, dropped) { + // Extract link IDs + var links = []; + var id = ''; + for(var i = 0; i < source.length; i++) + { + if(!source[i].id) continue; + id = source[i].id.split('::'); + links.push({app: id[0] == 'filemanager' ? 'link' : id[0], id: id[1]}); + } + if(!links.length) + { + return; + } + + // Link the entries + self.egw.json("EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_link", + dropped.id.split('::').concat([links]), + function(result) { + if(result) + { + for (var i=0; i < this._objectManager.selectedChildren.length; i++) + { + this._widget.refresh(this._objectManager.selectedChildren[i].id,'update'); + } + this._widget.egw().message('Linked'); + // Update the target to show the liks + this._widget.refresh(dropped.id,'update'); + } + }, + self, + true, + self + ).sendRequest(); + + },true); + } + if(this._actionLinks.indexOf(drop_action.id) < 0) + { + this._actionLinks.push(drop_action.id); + } + // Accept other links, and files dragged from the filemanager + // This does not handle files dragged from the desktop. They are + // handled by et2_nextmatch, since it needs DOM stuff + if(drop_action.acceptedTypes.indexOf('link') == -1) + { + drop_action.acceptedTypes.push('link'); + } + + // Don't re-add + if(drag_action == null) + { + // Create drag action that allows linking + drag_action = mgr.addAction('drag', 'egw_link_drag', this.egw.lang('link'), 'link', function(action, selected) { + // Drag helper - list titles. Arbitrarily limited to 10. + var helper = jQuery(document.createElement("div")); + for(var i = 0; i < selected.length && i < 10; i++) + { + var id = selected[i].id.split('::'); + var span = jQuery(document.createElement('span')).appendTo(helper); + self.egw.link_title(id[0],id[1], function(title) { + this.append(title); + this.append('
'); + }, span); + } + // As we wanted to have a general defaul helper interface, we return null here and not using customize helper for links + // TODO: Need to decide if we need to create a customized helper interface for links anyway + //return helper; + return null; + },true); + } + if(this._actionLinks.indexOf(drag_action.id) < 0) + { + this._actionLinks.push(drag_action.id); + } + drag_action.set_dragType('link'); + } + + /** + * Set the data cache prefix + * Overridden from the parent to re-check automatically the added link dnd + * since the prefix is used in support detection. + */ + setPrefix( prefix) + { + super.setPrefix(prefix); + + this._init_links_dnd(); + } + + /** + * Overwrites the inherited _destroyCallback function in order to be able + * to free the "rowWidget". + */ + _destroyCallback( _row) + { + // Destroy any widget associated to the row + if (this.entry.widget) + { + this.entry.widget.destroy(); + this.entry.widget = null; + } + + // Call the inherited function + super._destroyCallback(_row); + } + + /** + * Creates the actual data row. + * + * @param _data is an array containing the row data + * @param _tr is the tr into which the data will be inserted + * @param _idx is the index of the row + * @param _entry is the internal row datastructure of the controller, in + * this special case used to store the rowWidget reference, so that it can + * be properly freed. + */ + _rowCallback( _data, _tr, _idx, _entry) + { + // Let the row provider fill in the data row -- store the returned + // rowWidget inside the _entry + _entry.widget = this._rowProvider.getDataRow( + { "content": _data }, _tr, _idx, this); + } + + /** + * Returns the names of action links for a given data row -- currently these are + * always the same links, as we controll enabled/disabled over the row + * classes, unless the row UID is "", then it's an 'empty' row. + * + * The empty row placeholder can still have actions, but nothing that requires + * an actual UID. + * + * @TODO: Currently empty row is just add, need to actually filter somehow. Here + * might not be the right place. + * + * @param _data Object The data for the row + * @param _idx int The row index + * @param _uid String The row's ID + * + * @return Array List of action names that valid for the row + */ + _linkCallback( _data, _idx, _uid) + { + if(_uid.trim() != "") + { + return this._actionLinks; + } + + // No UID, so return a filtered list of actions that doesn't need a UID + var links = []; + try { + links = typeof this._widget.options.settings.placeholder_actions != 'undefined' ? + this._widget.options.settings.placeholder_actions : (this._widget.options.add ? ["add"] : []); + // Make sure that placeholder actions are defined and existed in client-side, + // otherwise do not set them as placeholder. for instance actions with + // attribute hideOnMobile do not get sent to client-side. + var action_search = function(current) { + if (typeof this._widget.options.actions[current] !== 'undefined') return true; + // Check children + for(var action in this._widget.options.actions) + { + action = this._widget.options.actions[action]; + if( action.children && action.children[current]) return true; + } + return false; + }; + links = links.filter(action_search, this); + } catch (e) { + } + + return links; + } + + + /** + * Overridden from the parent to also process any additional data that + * the data source adds, such as readonlys and additonal content. + * For example, non-numeric IDs in rows are added to the content manager + */ + _fetchCallback( _response) + { + var nm = this.self._widget; + if(!nm) + { + // Nextmatch either not connected, or it tried to destroy this + // but the server returned something + return; + } + // Readonlys + // Other stuff + for(var i in _response.rows) + { + if(jQuery.isNumeric(i)) continue; + if(i == 'sel_options') + { + var mgr = nm.getArrayMgr(i); + for(var id in _response.rows.sel_options) + { + mgr.data[id] = _response.rows.sel_options[id]; + var select = nm.getWidgetById(id); + if(select && select.set_select_options) + { + select.set_select_options(_response.rows.sel_options[id]); + } + // Clear rowProvider internal cache so it uses new values + if(id == 'cat_id') + { + this.self._rowProvider.categories = null; + } + // update array mgr so select widgets in row also get refreshed options + nm.getParent().getArrayMgr('sel_options').data[id] = _response.rows.sel_options[id]; + } + } + else + { + var mgr = nm.getArrayMgr('content'); + mgr.data[i] = _response.rows[i]; + + // It's not enough to just update the data, the widgets need to + // be updated too, if there are matching widgets. + var widget = nm.getWidgetById(i); + if(widget && widget.set_value) + { + widget.set_value(mgr.getEntry(i)); + } + } + } + // Might be trying to disable a column + var col_refresh = false; + for(var column_index = 0; column_index < nm.columns.length; column_index++) + { + if(typeof nm.columns[column_index].disabled === 'string' && + nm.columns[column_index].disabled[0] === '@') + { + col_refresh = true; + nm.dataview.columnMgr.getColumnById('col_'+column_index) + .set_visibility( + nm.getArrayMgr('content').parseBoolExpression(nm.columns[column_index].disabled) ? + et2_dataview_column.ET2_COL_VISIBILITY_DISABLED : + nm.columns[column_index].visible + ); + } + } + if(col_refresh) + { + nm.dataview.columnMgr.updated(); + nm.dataview._updateColumns(); + } + + // If we're doing an autorefresh and the count decreases, preserve the + // selection or it will be lost when the grid rows are shuffled. Increases + // are fine though. + if(this.self && this.self.kept_selection == null && + !this.refresh && this.self._grid.getTotalCount() > _response.total) + { + this.self.keepSelection(); + } + + // Call the inherited function + super._fetchCallback.apply(this, arguments); + + // Restore selection, if passed + if(this.self && this.self.kept_selection && this.self._selectionMgr) + { + if(this.self.kept_selection.all) + { + this.self._selectionMgr.selectAll(); + } + for(var i = (this.self.kept_selection.ids.length || 1)-1; i >= 0; i--) + { + // Only keep the selected if they came back in the fetch + if(_response.order.indexOf(this.self.kept_selection.ids[i]) >= 0) + { + this.self._selectionMgr.setSelected(this.self.kept_selection.ids[i],true); + this.self.kept_selection.ids.splice(i,1); + } + else + { + this.self.kept_selection.ids.splice(i,1); + } + } + if(this.self.kept_focus && _response.order.indexOf(this.self.kept_focus) >= 0) + { + this.self._selectionMgr.setFocused(this.self.kept_focus,true); + } + // Re-expanding rows handled in et2_extension_nextmatch_rowProvider + // Expansions might still be valid, so we don't clear them + if(this.self.kept_selection != null && typeof this.self.kept_selection.ids != 'undefined' && this.self.kept_selection.ids.length == 0) + { + this.self.kept_selection = null; + } + this.self.kept_focus = null; + } + } + + /** + * Execute the select callback when the row selection changes + */ + _selectCallback(action,senders) + { + if(typeof senders == "undefined") + { + senders = []; + } + if(!this._widget) return; + + // inform mobile framework about nm selections, need to update status of header objects on selection + if (egwIsMobile()) framework.nm_onselect_ctrl(this._widget, action, senders); + + this._widget.onselect.call(this._widget, action,senders); + } + + /** -- Implementation of et2_IDataProvider -- **/ + + + dataFetch( _queriedRange, _callback, _context) + { + + // Merge the parent id into the _queriedRange if it is set + if (this._parentId !== null) + { + _queriedRange["parent_id"] = this._parentId; + } + + // sub-levels dont have there own _filters object, need to use the one from parent (or it's parents parent) + var obj = this; + while((typeof obj._filters == 'undefined' || jQuery.isEmptyObject(obj._filters)) && obj._parentController) + { + obj = obj._parentController; + } + + // Pass the fetch call to the API, multiplex the data about the + // nextmatch instance into the call. + this.egw.dataFetch( + this._widget.getInstanceManager().etemplate_exec_id || this._execId, + _queriedRange, + obj._filters, + this._widgetId, + _callback, + _context); + } + + dataRegisterUID( _uid, _callback, _context) + { + this.egw.dataRegisterUID(_uid, _callback, _context, + this._widget.getInstanceManager().etemplate_exec_id || this._execId, + this._widgetId + ); + } + + dataUnregisterUID( ) + { + // Overwritten in the constructor + } + +} \ No newline at end of file diff --git a/api/js/etemplate/et2_extension_nextmatch_rowProvider.js b/api/js/etemplate/et2_extension_nextmatch_rowProvider.js index b09c354966..99087f52fe 100644 --- a/api/js/etemplate/et2_extension_nextmatch_rowProvider.js +++ b/api/js/etemplate/et2_extension_nextmatch_rowProvider.js @@ -1,3 +1,4 @@ +"use strict"; /** * EGroupware eTemplate2 - Class which contains a factory method for rows * @@ -9,666 +10,540 @@ * @copyright Stylite 2012 * @version $Id$ */ - +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); /*egw:uses - /vendor/bower-asset/jquery/dist/jquery.js; - et2_core_inheritance; - et2_core_interfaces; - et2_core_arrayMgr; - et2_core_widget; + /vendor/bower-asset/jquery/dist/jquery.js; + et2_core_inheritance; + et2_core_interfaces; + et2_core_arrayMgr; + et2_core_widget; + et2_dataview_view_rowProvider; */ - +var et2_core_widget_1 = require("./et2_core_widget"); +var et2_core_arrayMgr_1 = require("./et2_core_arrayMgr"); /** * The row provider contains prototypes (full clonable dom-trees) * for all registered row types. * - * @augments Class */ -var et2_nextmatch_rowProvider = (function(){ "use strict"; return ClassWithAttributes.extend( -{ - /** - * Creates the nextmatch row provider. - * - * @param {et2_nextmatch_rowProvider} _rowProvider - * @param {function} _subgridCallback - * @param {object} _context - * @memberOf et2_nextmatch_rowProvider - */ - init: function (_rowProvider, _subgridCallback, _context) { - // Copy the arguments - this._rowProvider = _rowProvider; - this._subgridCallback = _subgridCallback; - this._context = _context; - - this._createEmptyPrototype(); - }, - - /** - * Creates the data row prototype. - * - * @param _widgets is an array containing the root widget for each column. - * @param _rowData contains the properties of the root "tr" (like its class) - * @param _rootWidget is the parent widget of the data rows (i.e. - * the nextmatch) - */ - setDataRowTemplate: function(_widgets, _rowData, _rootWidget) { - // Copy the root widget - this._rootWidget = _rootWidget; - - // Create the base row - var row = this._rowProvider.getPrototype("default"); - - // Copy the row template - var rowTemplate = { - "row": row[0], - "rowData": _rowData, - "widgets": _widgets, - "root": _rootWidget, - "seperated": null, - "mgrs": _rootWidget.getArrayMgrs() - }; - - // Create the row widget and insert the given widgets into the row - var rowWidget = new et2_nextmatch_rowWidget(rowTemplate.mgrs, row[0]); - rowWidget._parent = _rootWidget; - rowWidget.createWidgets(_widgets); - - // Get the set containing all variable attributes - var variableAttributes = this._getVariableAttributeSet(rowWidget); - - // Filter out all widgets which do not implement the et2_IDetachedDOM - // interface or do not support all attributes listed in the et2_IDetachedDOM - // interface. A warning is issued for all those widgets as they heavily - // degrade the performance of the dataview - var seperated = rowTemplate.seperated = - this._seperateWidgets(variableAttributes); - - // Remove all DOM-Nodes of all widgets inside the "remaining" slot from - // the row-template, then build the access functions for the detachable - // widgets - this._stripTemplateRow(rowTemplate); - this._buildNodeAccessFuncs(rowTemplate); - - // Create the DOM row template - var tmpl = document.createDocumentFragment(); - row.children().each(function() { tmpl.appendChild(this); }); - - this._dataRow = tmpl; - this._template = rowTemplate; - }, - - getDataRow: function(_data, _row, _idx, _controller) { - - // Clone the row template - var row = this._dataRow.cloneNode(true); - - // Create array managers with the given data merged in - var mgrs = et2_arrayMgrs_expand(rowWidget, this._template.mgrs, - _data, _idx); - - // Insert the widgets into the row which do not provide the functions - // to set the _data directly - var rowWidget = null; - if (this._template.seperated.remaining.length > 0) - { - // Transform the variable attributes - for (var i = 0; i < this._template.seperated.remaining.length; i++) - { - var entry = this._template.seperated.remaining[i]; - - for (var j = 0; j < entry.data.length; j++) - { - var set = entry.data[j]; - entry.widget.options[set.attribute] = mgrs["content"].expandName(set.expression); - } - } - - // Create the row widget - var rowWidget = new et2_nextmatch_rowTemplateWidget(this._rootWidget, - row); - - // Let the row widget create the widgets - rowWidget.createWidgets(mgrs, this._template.placeholders); - } - - // Update the content of all other widgets - for (var i = 0; i < this._template.seperated.detachable.length; i++) - { - var entry = this._template.seperated.detachable[i]; - - // Parse the attribute expressions - var data = {}; - for (var j = 0; j < entry.data.length; j++) - { - var set = entry.data[j]; - data[set.attribute] = mgrs["content"].expandName(set.expression); - } - - // Retrieve all DOM-Nodes - var nodes = new Array(entry.nodeFuncs.length); - for (var j = 0; j < nodes.length; j++) - { - // Use the previously compiled node function to get the node - // from the entry - nodes[j] = entry.nodeFuncs[j](row); - } - - // Set the array managers first - entry.widget._mgrs = mgrs; - if (typeof data.id != "undefined") - { - entry.widget.id = data.id; - } - - // Adjust data for that row - entry.widget.transformAttributes.call(entry.widget,data); - - // Call the setDetachedAttributes function - entry.widget.setDetachedAttributes(nodes, data, _data); - } - - // Insert the row into the tr - var tr = _row.getDOMNode(); - tr.appendChild(row); - - // Make the row expandable - if (typeof _data.content["is_parent"] !== "undefined" - && _data.content["is_parent"]) - { - _row.makeExpandable(true, function () { - return this._subgridCallback.call(this._context, - _row, _data, _controller); - }, this); - - // Check for kept expansion, and set the row up to be re-expanded - // Only the top controller tracks expanded, including sub-grids - var top_controller = _controller; - while(top_controller._parentController != null) - { - top_controller = top_controller._parentController; - } - var expansion_index = top_controller.kept_expansion.indexOf( - top_controller.dataStorePrefix + '::' + _data.content[this._context.settings.row_id] - ); - if(top_controller.kept_expansion && expansion_index >=0) - { - top_controller.kept_expansion.splice(expansion_index,1); - // Use a timeout since the DOM nodes might not be finished yet - window.setTimeout(function() { - _row.expansionButton.trigger('click'); - },ET2_GRID_INVALIDATE_TIMEOUT); - } - } - - // Set the row data - this._setRowData(this._template.rowData, tr, mgrs); - - return rowWidget; - }, - - - /** - * Placeholder for empty row - * - * The empty row placeholder is used when there are no results to display. - * This allows the user to still have a drop target, or use actions that - * do not require a row ID, such as 'Add new'. - */ - _createEmptyPrototype: function() { - var label = this._context && this._context.options && this._context.options.settings.placeholder; - - var placeholder = jQuery(document.createElement("td")) - .attr("colspan",this._rowProvider.getColumnCount()) - .css("height","19px") - .text(typeof label != "undefined" && label ? label : egw().lang("No matches found")); - this._rowProvider._prototypes["empty"] = jQuery(document.createElement("tr")) - .addClass("egwGridView_empty") - .append(placeholder); - }, - - /** -- PRIVATE FUNCTIONS -- **/ - - /** - * Returns an array containing objects which have variable attributes - * - * @param {et2_widget} _widget - */ - _getVariableAttributeSet: function(_widget) { - var variableAttributes = []; - - _widget.iterateOver(function(_widget) { - // Create the attribtues - var hasAttr = false; - var widgetData = { - "widget": _widget, - "data": [] - }; - - // Get all attribute values - for (var key in _widget.attributes) - { - if (!_widget.attributes[key].ignore && - typeof _widget.options[key] != "undefined") - { - var val = _widget.options[key]; - - // TODO: Improve detection - if (typeof val == "string" && val.indexOf("$") >= 0) - { - hasAttr = true; - widgetData.data.push({ - "attribute": key, - "expression": val - }); - } - } - } - - // Add the entry if there is any data in it - if (hasAttr) - { - variableAttributes.push(widgetData); - } - - }, this); - - return variableAttributes; - }, - - _seperateWidgets: function(_varAttrs) { - // The detachable array contains all widgets which implement the - // et2_IDetachedDOM interface for all needed attributes - var detachable = []; - - // The remaining array creates all widgets which have to be completely - // cloned when the widget tree is created - var remaining = []; - - // Iterate over the widgets - for (var i = 0; i < _varAttrs.length; i++) - { - var widget = _varAttrs[i].widget; - - // Check whether the widget parents are not allready in the "remaining" - // slot - if this is the case do not include the widget at all. - var insertWidget = true; - var checkWidget = function (_widget) { - if (_widget.parent != null) - { - for (var i = 0; i < remaining.length; i++) - { - if (remaining[i].widget == _widget.parent) - { - insertWidget = false; - return; - } - } - - checkWidget(_widget.parent); - } - }; - checkWidget(widget); - - // Handle the next widget if this one should not be included. - if (!insertWidget) - { - continue; - } - - // Check whether the widget implements the et2_IDetachedDOM interface - var isDetachable = false; - if (widget.implements(et2_IDetachedDOM)) - { - // Get all attributes the widgets supports to be set in the - // "detached" mode - var supportedAttrs = []; - widget.getDetachedAttributes(supportedAttrs); - supportedAttrs.push("id"); - isDetachable = true; - - for (var j = 0; j < _varAttrs[i].data.length/* && isDetachable*/; j++) - { - var data = _varAttrs[i].data[j]; - - var supportsAttr = supportedAttrs.indexOf(data.attribute) != -1; - - if (!supportsAttr) - { - egw.debug("warn", "et2_IDetachedDOM widget " + - widget._type + " does not support " + data.attribute); - } - - isDetachable &= supportsAttr; - } - } - - // Insert the widget into the correct slot - if (isDetachable) - { - detachable.push(_varAttrs[i]); - } - else - { - remaining.push(_varAttrs[i]); - } - } - - return { - "detachable": detachable, - "remaining": remaining - }; - }, - - /** - * Removes to DOM code for all widgets in the "remaining" slot - * - * @param {object} _rowTemplate - */ - _stripTemplateRow: function(_rowTemplate) { - _rowTemplate.placeholders = []; - - for (var i = 0; i < _rowTemplate.seperated.remaining.length; i++) - { - var entry = _rowTemplate.seperated.remaining[i]; - - // Issue a warning - widgets which do not implement et2_IDOMNode - // are very slow - egw.debug("warn", "Non-clonable widget '"+ entry.widget._type + "' in dataview row - this " + - "might be slow", entry); - - // Set the placeholder for the entry to null - entry.placeholder = null; - - // Get the outer DOM-Node of the widget - if (entry.widget.implements(et2_IDOMNode)) - { - var node = entry.widget.getDOMNode(entry.widget); - - if (node && node.parentNode) - { - // Get the parent node and replace the node with a placeholder - entry.placeholder = document.createElement("span"); - node.parentNode.replaceChild(entry.placeholder, node); - _rowTemplate.placeholders.push({ - "widget": entry.widget, - "func": this._compileDOMAccessFunc(_rowTemplate.row, - entry.placeholder) - }); - } - } - } - }, - - _nodeIndex: function(_node) { - if(_node.parentNode == null) - { - return 0; - } - for (var i = 0; i < _node.parentNode.childNodes.length; i++) - { - if (_node.parentNode.childNodes[i] == _node) - { - return i; - } - } - - return -1; - }, - - /** - * Returns a function which does a relative access on the given DOM-Node - * - * @param {DOMElement} _root - * @param {DOMElement} _target - */ - _compileDOMAccessFunc: function(_root, _target) { - function recordPath(_root, _target, _path) - { - if (typeof _path == "undefined") - { - _path = []; - } - - if (_root != _target && _target) - { - // Get the index of _target in its parent node - var idx = this._nodeIndex(_target); - if (idx >= 0) - { - // Add the access selector - _path.unshift("childNodes[" + idx + "]"); - - // Record the remaining path - return recordPath.call(this, _root, _target.parentNode, _path); - } - - throw("Internal error while compiling DOM access function."); - } - else - { - _path.unshift("_node"); - return "return " + _path.join(".") + ";"; - } - } - - return new Function("_node", recordPath.call(this, _root, _target)); - }, - - /** - * Builds relative paths to the DOM-Nodes and compiles fast-access functions - * - * @param {object} _rowTemplate - */ - _buildNodeAccessFuncs: function(_rowTemplate) { - for (var i = 0; i < _rowTemplate.seperated.detachable.length; i++) - { - var entry = _rowTemplate.seperated.detachable[i]; - - // Get all needed nodes from the widget - var nodes = entry.widget.getDetachedNodes(); - var nodeFuncs = entry.nodeFuncs = new Array(nodes.length); - - // Record the path to each DOM-Node - for (var j = 0; j < nodes.length; j++) - { - nodeFuncs[j] = this._compileDOMAccessFunc(_rowTemplate.row, - nodes[j]); - } - } - }, - - /** - * Match category-ids from class attribute eg. "cat_15" or "123,456,789 " - * - * Make sure to not match numbers inside other class-names. - * - * We can NOT use something like /(^| |,|cat_)([0-9]+)( |,|$)/g as it wont find all cats in "123,456,789 "! - */ - cat_regexp: /(^| |,|cat_)([0-9]+)/g, - /** - * Regular expression used to filter out non-nummerical chars from above matches - */ - cat_cleanup: /[^0-9]/g, - - /** - * Applies additional row data (like the class) to the tr - * - * @param {object} _data - * @param {DOMElement} _tr - * @param {object} _mgrs - */ - _setRowData: function (_data, _tr, _mgrs) { - // TODO: Implement other fields than "class" - if (_data["class"]) - { - var classes = _mgrs["content"].expandName(_data["class"]); - - // Get fancy with categories - var cats = []; - // Assume any numeric class is a category - if(_data["class"].indexOf("cat") !== -1 || classes.match(/[0-9]+/)) - { - // Accept either cat, cat_id or category as ID, and look there for category settings - var category_location = _data["class"].match(/(cat(_id|egory)?)/); - if(category_location) category_location = category_location[0]; - - cats = classes.match(this.cat_regexp) || []; - classes = classes.replace(this.cat_regexp, ''); - - // Set category class - for(var i = 0; i < cats.length; i++) - { - // Need cat_, classes can't start with a number - var cat_id = cats[i].replace(this.cat_cleanup, ''); - var cat_class = 'cat_'+cat_id; - - classes += ' '+cat_class; - } - classes += " row_category"; - } - classes += " row"; - _tr.setAttribute("class", classes); - } - if(_data['valign']) - { - var align = _mgrs["content"].expandName(_data["valign"]); - _tr.setAttribute("valign", align); - } - } - -});}).call(this); - +var et2_nextmatch_rowProvider = /** @class */ (function () { + /** + * Creates the nextmatch row provider. + * + * @param {et2_nextmatch_rowProvider} _rowProvider + * @param {function} _subgridCallback + * @param {object} _context + * @memberOf et2_nextmatch_rowProvider + */ + function et2_nextmatch_rowProvider(_rowProvider, _subgridCallback, _context) { + /** + * Match category-ids from class attribute eg. "cat_15" or "123,456,789 " + * + * Make sure to not match numbers inside other class-names. + * + * We can NOT use something like /(^| |,|cat_)([0-9]+)( |,|$)/g as it wont find all cats in "123,456,789 "! + */ + this.cat_regexp = /(^| |,|cat_)([0-9]+)/g; + /** + * Regular expression used to filter out non-nummerical chars from above matches + */ + this.cat_cleanup = /[^0-9]/g; + // Copy the arguments + this._rowProvider = _rowProvider; + this._subgridCallback = _subgridCallback; + this._context = _context; + this._createEmptyPrototype(); + } + /** + * Creates the data row prototype. + * + * @param _widgets is an array containing the root widget for each column. + * @param _rowData contains the properties of the root "tr" (like its class) + * @param _rootWidget is the parent widget of the data rows (i.e. + * the nextmatch) + */ + et2_nextmatch_rowProvider.prototype.setDataRowTemplate = function (_widgets, _rowData, _rootWidget) { + // Copy the root widget + this._rootWidget = _rootWidget; + // Create the base row + var row = this._rowProvider.getPrototype("default"); + // Copy the row template + var rowTemplate = { + "row": row[0], + "rowData": _rowData, + "widgets": _widgets, + "root": _rootWidget, + "seperated": null, + "mgrs": _rootWidget.getArrayMgrs() + }; + // Create the row widget and insert the given widgets into the row + var rowWidget = new et2_nextmatch_rowWidget(rowTemplate.mgrs, row[0]); + rowWidget._parent = _rootWidget; + rowWidget.createWidgets(_widgets); + // Get the set containing all variable attributes + var variableAttributes = this._getVariableAttributeSet(rowWidget); + // Filter out all widgets which do not implement the et2_IDetachedDOM + // interface or do not support all attributes listed in the et2_IDetachedDOM + // interface. A warning is issued for all those widgets as they heavily + // degrade the performance of the dataview + var seperated = rowTemplate.seperated = + this._seperateWidgets(variableAttributes); + // Remove all DOM-Nodes of all widgets inside the "remaining" slot from + // the row-template, then build the access functions for the detachable + // widgets + this._stripTemplateRow(rowTemplate); + this._buildNodeAccessFuncs(rowTemplate); + // Create the DOM row template + var tmpl = document.createDocumentFragment(); + row.children().each(function () { tmpl.appendChild(this); }); + this._dataRow = tmpl; + this._template = rowTemplate; + }; + et2_nextmatch_rowProvider.prototype.getDataRow = function (_data, _row, _idx, _controller) { + // Clone the row template + var row = this._dataRow.cloneNode(true); + // Create array managers with the given data merged in + var mgrs = et2_core_arrayMgr_1.et2_arrayMgrs_expand(rowWidget, this._template.mgrs, _data, _idx); + // Insert the widgets into the row which do not provide the functions + // to set the _data directly + var rowWidget = null; + if (this._template.seperated.remaining.length > 0) { + // Transform the variable attributes + for (var i = 0; i < this._template.seperated.remaining.length; i++) { + var entry = this._template.seperated.remaining[i]; + for (var j = 0; j < entry.data.length; j++) { + var set = entry.data[j]; + entry.widget.options[set.attribute] = mgrs["content"].expandName(set.expression); + } + } + // Create the row widget + var rowWidget = new et2_nextmatch_rowTemplateWidget(this._rootWidget, row); + // Let the row widget create the widgets + rowWidget.createWidgets(mgrs, this._template.placeholders); + } + // Update the content of all other widgets + for (var i = 0; i < this._template.seperated.detachable.length; i++) { + var entry = this._template.seperated.detachable[i]; + // Parse the attribute expressions + var data = {}; + for (var j = 0; j < entry.data.length; j++) { + var set = entry.data[j]; + data[set.attribute] = mgrs["content"].expandName(set.expression); + } + // Retrieve all DOM-Nodes + var nodes = new Array(entry.nodeFuncs.length); + for (var j = 0; j < nodes.length; j++) { + // Use the previously compiled node function to get the node + // from the entry + nodes[j] = entry.nodeFuncs[j](row); + } + // Set the array managers first + entry.widget._mgrs = mgrs; + if (typeof data.id != "undefined") { + entry.widget.id = data.id; + } + // Adjust data for that row + entry.widget.transformAttributes.call(entry.widget, data); + // Call the setDetachedAttributes function + entry.widget.setDetachedAttributes(nodes, data, _data); + } + // Insert the row into the tr + var tr = _row.getDOMNode(); + tr.appendChild(row); + // Make the row expandable + if (typeof _data.content["is_parent"] !== "undefined" + && _data.content["is_parent"]) { + _row.makeExpandable(true, function () { + return this._subgridCallback.call(this._context, _row, _data, _controller); + }, this); + // Check for kept expansion, and set the row up to be re-expanded + // Only the top controller tracks expanded, including sub-grids + var top_controller = _controller; + while (top_controller._parentController != null) { + top_controller = top_controller._parentController; + } + var expansion_index = top_controller.kept_expansion.indexOf(top_controller.dataStorePrefix + '::' + _data.content[this._context.settings.row_id]); + if (top_controller.kept_expansion && expansion_index >= 0) { + top_controller.kept_expansion.splice(expansion_index, 1); + // Use a timeout since the DOM nodes might not be finished yet + window.setTimeout(function () { + _row.expansionButton.trigger('click'); + }, et2_dataview_grid.ET2_GRID_INVALIDATE_TIMEOUT); + } + } + // Set the row data + this._setRowData(this._template.rowData, tr, mgrs); + return rowWidget; + }; + /** + * Placeholder for empty row + * + * The empty row placeholder is used when there are no results to display. + * This allows the user to still have a drop target, or use actions that + * do not require a row ID, such as 'Add new'. + */ + et2_nextmatch_rowProvider.prototype._createEmptyPrototype = function () { + var label = this._context && this._context.options && this._context.options.settings.placeholder; + var placeholder = jQuery(document.createElement("td")) + .attr("colspan", this._rowProvider.getColumnCount()) + .css("height", "19px") + .text(typeof label != "undefined" && label ? label : egw().lang("No matches found")); + this._rowProvider._prototypes["empty"] = jQuery(document.createElement("tr")) + .addClass("egwGridView_empty") + .append(placeholder); + }; + /** -- PRIVATE FUNCTIONS -- **/ + /** + * Returns an array containing objects which have variable attributes + * + * @param {et2_widget} _widget + */ + et2_nextmatch_rowProvider.prototype._getVariableAttributeSet = function (_widget) { + var variableAttributes = []; + _widget.iterateOver(function (_widget) { + // Create the attribtues + var hasAttr = false; + var widgetData = { + "widget": _widget, + "data": [] + }; + // Get all attribute values + for (var key in _widget.attributes) { + if (!_widget.attributes[key].ignore && + typeof _widget.options[key] != "undefined") { + var val = _widget.options[key]; + // TODO: Improve detection + if (typeof val == "string" && val.indexOf("$") >= 0) { + hasAttr = true; + widgetData.data.push({ + "attribute": key, + "expression": val + }); + } + } + } + // Add the entry if there is any data in it + if (hasAttr) { + variableAttributes.push(widgetData); + } + }, this); + return variableAttributes; + }; + et2_nextmatch_rowProvider.prototype._seperateWidgets = function (_varAttrs) { + // The detachable array contains all widgets which implement the + // et2_IDetachedDOM interface for all needed attributes + var detachable = []; + // The remaining array creates all widgets which have to be completely + // cloned when the widget tree is created + var remaining = []; + // Iterate over the widgets + for (var i = 0; i < _varAttrs.length; i++) { + var widget = _varAttrs[i].widget; + // Check whether the widget parents are not allready in the "remaining" + // slot - if this is the case do not include the widget at all. + var insertWidget = true; + var checkWidget = function (_widget) { + if (_widget.parent != null) { + for (var i = 0; i < remaining.length; i++) { + if (remaining[i].widget == _widget.parent) { + insertWidget = false; + return; + } + } + checkWidget(_widget.parent); + } + }; + checkWidget(widget); + // Handle the next widget if this one should not be included. + if (!insertWidget) { + continue; + } + // Check whether the widget implements the et2_IDetachedDOM interface + var isDetachable = false; + if (widget.implements(et2_IDetachedDOM)) { + // Get all attributes the widgets supports to be set in the + // "detached" mode + var supportedAttrs = []; + widget.getDetachedAttributes(supportedAttrs); + supportedAttrs.push("id"); + isDetachable = true; + for (var j = 0; j < _varAttrs[i].data.length /* && isDetachable*/; j++) { + var data = _varAttrs[i].data[j]; + var supportsAttr = supportedAttrs.indexOf(data.attribute) != -1; + if (!supportsAttr) { + egw.debug("warn", "et2_IDetachedDOM widget " + + widget._type + " does not support " + data.attribute); + } + isDetachable = isDetachable && supportsAttr; + } + } + // Insert the widget into the correct slot + if (isDetachable) { + detachable.push(_varAttrs[i]); + } + else { + remaining.push(_varAttrs[i]); + } + } + return { + "detachable": detachable, + "remaining": remaining + }; + }; + /** + * Removes to DOM code for all widgets in the "remaining" slot + * + * @param {object} _rowTemplate + */ + et2_nextmatch_rowProvider.prototype._stripTemplateRow = function (_rowTemplate) { + _rowTemplate.placeholders = []; + for (var i = 0; i < _rowTemplate.seperated.remaining.length; i++) { + var entry = _rowTemplate.seperated.remaining[i]; + // Issue a warning - widgets which do not implement et2_IDOMNode + // are very slow + egw.debug("warn", "Non-clonable widget '" + entry.widget._type + "' in dataview row - this " + + "might be slow", entry); + // Set the placeholder for the entry to null + entry.placeholder = null; + // Get the outer DOM-Node of the widget + if (entry.widget.implements(et2_IDOMNode)) { + var node = entry.widget.getDOMNode(entry.widget); + if (node && node.parentNode) { + // Get the parent node and replace the node with a placeholder + entry.placeholder = document.createElement("span"); + node.parentNode.replaceChild(entry.placeholder, node); + _rowTemplate.placeholders.push({ + "widget": entry.widget, + "func": this._compileDOMAccessFunc(_rowTemplate.row, entry.placeholder) + }); + } + } + } + }; + et2_nextmatch_rowProvider.prototype._nodeIndex = function (_node) { + if (_node.parentNode == null) { + return 0; + } + for (var i = 0; i < _node.parentNode.childNodes.length; i++) { + if (_node.parentNode.childNodes[i] == _node) { + return i; + } + } + return -1; + }; + /** + * Returns a function which does a relative access on the given DOM-Node + * + * @param {DOMElement} _root + * @param {DOMElement} _target + */ + et2_nextmatch_rowProvider.prototype._compileDOMAccessFunc = function (_root, _target) { + function recordPath(_root, _target, _path) { + if (typeof _path == "undefined") { + _path = []; + } + if (_root != _target && _target) { + // Get the index of _target in its parent node + var idx = this._nodeIndex(_target); + if (idx >= 0) { + // Add the access selector + _path.unshift("childNodes[" + idx + "]"); + // Record the remaining path + return recordPath.call(this, _root, _target.parentNode, _path); + } + throw ("Internal error while compiling DOM access function."); + } + else { + _path.unshift("_node"); + return "return " + _path.join(".") + ";"; + } + } + return new Function("_node", recordPath.call(this, _root, _target)); + }; + /** + * Builds relative paths to the DOM-Nodes and compiles fast-access functions + * + * @param {object} _rowTemplate + */ + et2_nextmatch_rowProvider.prototype._buildNodeAccessFuncs = function (_rowTemplate) { + for (var i = 0; i < _rowTemplate.seperated.detachable.length; i++) { + var entry = _rowTemplate.seperated.detachable[i]; + // Get all needed nodes from the widget + var nodes = entry.widget.getDetachedNodes(); + var nodeFuncs = entry.nodeFuncs = new Array(nodes.length); + // Record the path to each DOM-Node + for (var j = 0; j < nodes.length; j++) { + nodeFuncs[j] = this._compileDOMAccessFunc(_rowTemplate.row, nodes[j]); + } + } + }; + /** + * Applies additional row data (like the class) to the tr + * + * @param {object} _data + * @param {DOMElement} _tr + * @param {object} _mgrs + */ + et2_nextmatch_rowProvider.prototype._setRowData = function (_data, _tr, _mgrs) { + // TODO: Implement other fields than "class" + if (_data["class"]) { + var classes = _mgrs["content"].expandName(_data["class"]); + // Get fancy with categories + var cats = []; + // Assume any numeric class is a category + if (_data["class"].indexOf("cat") !== -1 || classes.match(/[0-9]+/)) { + // Accept either cat, cat_id or category as ID, and look there for category settings + var category_location = _data["class"].match(/(cat(_id|egory)?)/); + if (category_location) + category_location = category_location[0]; + cats = classes.match(this.cat_regexp) || []; + classes = classes.replace(this.cat_regexp, ''); + // Set category class + for (var i = 0; i < cats.length; i++) { + // Need cat_, classes can't start with a number + var cat_id = cats[i].replace(this.cat_cleanup, ''); + var cat_class = 'cat_' + cat_id; + classes += ' ' + cat_class; + } + classes += " row_category"; + } + classes += " row"; + _tr.setAttribute("class", classes); + } + if (_data['valign']) { + var align = _mgrs["content"].expandName(_data["valign"]); + _tr.setAttribute("valign", align); + } + }; + return et2_nextmatch_rowProvider; +}()); +exports.et2_nextmatch_rowProvider = et2_nextmatch_rowProvider; /** * @augments et2_widget */ -var et2_nextmatch_rowWidget = (function(){ "use strict"; return et2_widget.extend(et2_IDOMNode, -{ - /** - * Constructor - * - * @param _mgrs - * @param _row - * @memberOf et2_nextmatch_rowWidget - */ - init: function(_mgrs, _row) { - // Call the parent constructor with some dummy attributes - this._super(null, {"id": "", "type": "rowWidget"}); - - // Initialize some variables - this._widgets = []; - - // Copy the given DOM node and the content arrays - this._mgrs = _mgrs; - this._row = _row; - }, - - /** - * Copies the given array manager and clones the given widgets and inserts - * them into the row which has been passed in the constructor. - * - * @param {array} _widgets - */ - createWidgets: function(_widgets) { - // Clone the given the widgets with this element as parent - this._widgets = new Array(_widgets.length); - for (var i = 0; i < _widgets.length; i++) - { - // Disabled columns might be missing widget - skip it - if(!_widgets[i]) continue; - - this._widgets[i] = _widgets[i].clone(this); - this._widgets[i].loadingFinished(); - // Set column alignment from widget - if(this._widgets[i].align) - { - this._row.childNodes[i].align = this._widgets[i].align; - } - } - }, - - /** - * Returns the column node for the given sender - * - * @param {et2_widget} _sender - * @return {DOMElement} - */ - getDOMNode: function(_sender) { - for (var i = 0; i < this._widgets.length; i++) - { - if (this._widgets[i] == _sender) - { - return this._row.childNodes[i].childNodes[0]; // Return the i-th td tag - } - } - - return null; - } - -});}).call(this); - +var et2_nextmatch_rowWidget = /** @class */ (function (_super) { + __extends(et2_nextmatch_rowWidget, _super); + /** + * Constructor + * + * @param _mgrs + * @param _row + * @memberOf et2_nextmatch_rowWidget + */ + function et2_nextmatch_rowWidget(_mgrs, _row) { + var _this = + // Call the parent constructor with some dummy attributes + _super.call(this, null, { "id": "", "type": "rowWidget" }) || this; + // Initialize some variables + _this._widgets = []; + // Copy the given DOM node and the content arrays + _this._mgrs = _mgrs; + _this._row = _row; + return _this; + } + /** + * Copies the given array manager and clones the given widgets and inserts + * them into the row which has been passed in the constructor. + * + * @param {array} _widgets + */ + et2_nextmatch_rowWidget.prototype.createWidgets = function (_widgets) { + // Clone the given the widgets with this element as parent + this._widgets = new Array(_widgets.length); + for (var i = 0; i < _widgets.length; i++) { + // Disabled columns might be missing widget - skip it + if (!_widgets[i]) + continue; + this._widgets[i] = _widgets[i].clone(this); + this._widgets[i].loadingFinished(); + // Set column alignment from widget + if (this._widgets[i].align) { + this._row.childNodes[i].align = this._widgets[i].align; + } + } + }; + /** + * Returns the column node for the given sender + * + * @param {et2_widget} _sender + * @return {DOMElement} + */ + et2_nextmatch_rowWidget.prototype.getDOMNode = function (_sender) { + for (var i = 0; i < this._widgets.length; i++) { + if (this._widgets[i] == _sender) { + return this._row.childNodes[i].childNodes[0]; // Return the i-th td tag + } + } + return null; + }; + return et2_nextmatch_rowWidget; +}(et2_core_widget_1.et2_widget)); /** * @augments et2_widget */ -var et2_nextmatch_rowTemplateWidget = (function(){ "use strict"; return et2_widget.extend(et2_IDOMNode, -{ - /** - * Constructor - * - * @param _root - * @param _row - * @memberOf et2_nextmatch_rowTemplateWidget - */ - init: function(_root, _row) { - // Call the parent constructor with some dummy attributes - this._super(null, {"id": "", "type": "rowTemplateWidget"}); - - this._root = _root; - this._mgrs = {}; - this._row = _row; - - // Set parent to root widget, so sub-widget calls still work - this._parent = _root; - - // Clone the widgets inside the placeholders array - this._widgets = []; - }, - - createWidgets: function(_mgrs, _widgets) { - // Set the array managers - don't use setArrayMgrs here as this creates - // an unnecessary copy of the object - this._mgrs = _mgrs; - - this._widgets = new Array(_widgets.length); - for (var i = 0; i < _widgets.length; i++) - { - this._row.childNodes[0].childNodes[0]; - - this._widgets[i] = { - "widget": _widgets[i].widget.clone(this), - "node": _widgets[i].func(this._row) - }; - this._widgets[i].widget.loadingFinished(); - } - }, - - /** - * Returns the column node for the given sender - * - * @param {et2_widget} _sender - * @return {DOMElement} - */ - getDOMNode: function(_sender) { - - for (var i = 0; i < this._widgets.length; i++) - { - if (this._widgets[i].widget == _sender) - { - return this._widgets[i].node; - } - } - - return null; - } - -});}).call(this); - +var et2_nextmatch_rowTemplateWidget = /** @class */ (function (_super) { + __extends(et2_nextmatch_rowTemplateWidget, _super); + /** + * Constructor + * + * @param _root + * @param _row + * @memberOf et2_nextmatch_rowTemplateWidget + */ + function et2_nextmatch_rowTemplateWidget(_root, _row) { + var _this = + // Call the parent constructor with some dummy attributes + _super.call(this, null, { "id": "", "type": "rowTemplateWidget" }) || this; + _this._root = _root; + _this._mgrs = {}; + _this._row = _row; + // Set parent to root widget, so sub-widget calls still work + _this._parent = _root; + // Clone the widgets inside the placeholders array + _this._widgets = []; + return _this; + } + et2_nextmatch_rowTemplateWidget.prototype.createWidgets = function (_mgrs, _widgets) { + // Set the array managers - don't use setArrayMgrs here as this creates + // an unnecessary copy of the object + this._mgrs = _mgrs; + this._widgets = new Array(_widgets.length); + for (var i = 0; i < _widgets.length; i++) { + this._row.childNodes[0].childNodes[0]; + this._widgets[i] = { + "widget": _widgets[i].widget.clone(this), + "node": _widgets[i].func(this._row) + }; + this._widgets[i].widget.loadingFinished(); + } + }; + /** + * Returns the column node for the given sender + * + * @param {et2_widget} _sender + * @return {DOMElement} + */ + et2_nextmatch_rowTemplateWidget.prototype.getDOMNode = function (_sender) { + for (var i = 0; i < this._widgets.length; i++) { + if (this._widgets[i].widget == _sender) { + return this._widgets[i].node; + } + } + return null; + }; + return et2_nextmatch_rowTemplateWidget; +}(et2_core_widget_1.et2_widget)); +//# sourceMappingURL=et2_extension_nextmatch_rowProvider.js.map \ No newline at end of file diff --git a/api/js/etemplate/et2_extension_nextmatch_rowProvider.ts b/api/js/etemplate/et2_extension_nextmatch_rowProvider.ts new file mode 100644 index 0000000000..67112d6771 --- /dev/null +++ b/api/js/etemplate/et2_extension_nextmatch_rowProvider.ts @@ -0,0 +1,706 @@ +/** + * EGroupware eTemplate2 - Class which contains a factory method for rows + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package etemplate + * @subpackage api + * @link http://www.egroupware.org + * @author Andreas Stöckel + * @copyright Stylite 2012 + * @version $Id$ + */ + +/*egw:uses + /vendor/bower-asset/jquery/dist/jquery.js; + et2_core_inheritance; + et2_core_interfaces; + et2_core_arrayMgr; + et2_core_widget; + et2_dataview_view_rowProvider; +*/ + +import {et2_widget} from "./et2_core_widget"; +import {et2_arrayMgrs_expand} from "./et2_core_arrayMgr"; +import {et2_dataview_rowProvider} from "./et2_dataview_view_rowProvider"; + +/** + * The row provider contains prototypes (full clonable dom-trees) + * for all registered row types. + * + */ +export class et2_nextmatch_rowProvider +{ + private _rowProvider: any; + private _subgridCallback: any; + private _context: any; + private _rootWidget: any; + private _template: any; + /** + * Creates the nextmatch row provider. + * + * @param {et2_nextmatch_rowProvider} _rowProvider + * @param {function} _subgridCallback + * @param {object} _context + * @memberOf et2_nextmatch_rowProvider + */ + constructor( _rowProvider, _subgridCallback, _context) + { + + + // Copy the arguments + this._rowProvider = _rowProvider; + this._subgridCallback = _subgridCallback; + this._context = _context; + + this._createEmptyPrototype(); + } + + /** + * Creates the data row prototype. + * + * @param _widgets is an array containing the root widget for each column. + * @param _rowData contains the properties of the root "tr" (like its class) + * @param _rootWidget is the parent widget of the data rows (i.e. + * the nextmatch) + */ + setDataRowTemplate( _widgets, _rowData, _rootWidget) + { + // Copy the root widget + this._rootWidget = _rootWidget; + + // Create the base row + var row = this._rowProvider.getPrototype("default"); + + // Copy the row template + var rowTemplate = { + "row": row[0], + "rowData": _rowData, + "widgets": _widgets, + "root": _rootWidget, + "seperated": null, + "mgrs": _rootWidget.getArrayMgrs() + }; + + // Create the row widget and insert the given widgets into the row + var rowWidget = new et2_nextmatch_rowWidget(rowTemplate.mgrs, row[0]); + rowWidget._parent = _rootWidget; + rowWidget.createWidgets(_widgets); + + // Get the set containing all variable attributes + var variableAttributes = this._getVariableAttributeSet(rowWidget); + + // Filter out all widgets which do not implement the et2_IDetachedDOM + // interface or do not support all attributes listed in the et2_IDetachedDOM + // interface. A warning is issued for all those widgets as they heavily + // degrade the performance of the dataview + var seperated = rowTemplate.seperated = + this._seperateWidgets(variableAttributes); + + // Remove all DOM-Nodes of all widgets inside the "remaining" slot from + // the row-template, then build the access functions for the detachable + // widgets + this._stripTemplateRow(rowTemplate); + this._buildNodeAccessFuncs(rowTemplate); + + // Create the DOM row template + var tmpl = document.createDocumentFragment(); + row.children().each(function() { tmpl.appendChild(this); }); + + this._dataRow = tmpl; + this._template = rowTemplate; + } + + getDataRow( _data : any, _row, _idx, _controller) + { + + // Clone the row template + var row = this._dataRow.cloneNode(true); + + // Create array managers with the given data merged in + var mgrs = et2_arrayMgrs_expand(rowWidget, this._template.mgrs, + _data, _idx); + + // Insert the widgets into the row which do not provide the functions + // to set the _data directly + var rowWidget : et2_nextmatch_rowTemplateWidget = null; + if (this._template.seperated.remaining.length > 0) + { + // Transform the variable attributes + for (var i = 0; i < this._template.seperated.remaining.length; i++) + { + var entry = this._template.seperated.remaining[i]; + + for (var j = 0; j < entry.data.length; j++) + { + var set = entry.data[j]; + entry.widget.options[set.attribute] = mgrs["content"].expandName(set.expression); + } + } + + // Create the row widget + var rowWidget = new et2_nextmatch_rowTemplateWidget(this._rootWidget, + row); + + // Let the row widget create the widgets + rowWidget.createWidgets(mgrs, this._template.placeholders); + } + + // Update the content of all other widgets + for (var i = 0; i < this._template.seperated.detachable.length; i++) + { + var entry = this._template.seperated.detachable[i]; + + // Parse the attribute expressions + var data : any = {}; + for (var j = 0; j < entry.data.length; j++) + { + var set = entry.data[j]; + data[set.attribute] = mgrs["content"].expandName(set.expression); + } + + // Retrieve all DOM-Nodes + var nodes = new Array(entry.nodeFuncs.length); + for (var j = 0; j < nodes.length; j++) + { + // Use the previously compiled node function to get the node + // from the entry + nodes[j] = entry.nodeFuncs[j](row); + } + + // Set the array managers first + entry.widget._mgrs = mgrs; + if (typeof data.id != "undefined") + { + entry.widget.id = data.id; + } + + // Adjust data for that row + entry.widget.transformAttributes.call(entry.widget,data); + + // Call the setDetachedAttributes function + entry.widget.setDetachedAttributes(nodes, data, _data); + } + + // Insert the row into the tr + var tr = _row.getDOMNode(); + tr.appendChild(row); + + // Make the row expandable + if (typeof _data.content["is_parent"] !== "undefined" + && _data.content["is_parent"]) + { + _row.makeExpandable(true, function () { + return this._subgridCallback.call(this._context, + _row, _data, _controller); + }, this); + + // Check for kept expansion, and set the row up to be re-expanded + // Only the top controller tracks expanded, including sub-grids + var top_controller = _controller; + while(top_controller._parentController != null) + { + top_controller = top_controller._parentController; + } + var expansion_index = top_controller.kept_expansion.indexOf( + top_controller.dataStorePrefix + '::' + _data.content[this._context.settings.row_id] + ); + if(top_controller.kept_expansion && expansion_index >=0) + { + top_controller.kept_expansion.splice(expansion_index,1); + // Use a timeout since the DOM nodes might not be finished yet + window.setTimeout(function() { + _row.expansionButton.trigger('click'); + },et2_dataview_grid.ET2_GRID_INVALIDATE_TIMEOUT); + } + } + + // Set the row data + this._setRowData(this._template.rowData, tr, mgrs); + + return rowWidget; + } + + + /** + * Placeholder for empty row + * + * The empty row placeholder is used when there are no results to display. + * This allows the user to still have a drop target, or use actions that + * do not require a row ID, such as 'Add new'. + */ + _createEmptyPrototype( ) + { + var label = this._context && this._context.options && this._context.options.settings.placeholder; + + var placeholder = jQuery(document.createElement("td")) + .attr("colspan",this._rowProvider.getColumnCount()) + .css("height","19px") + .text(typeof label != "undefined" && label ? label : egw().lang("No matches found")); + this._rowProvider._prototypes["empty"] = jQuery(document.createElement("tr")) + .addClass("egwGridView_empty") + .append(placeholder); + } + + /** -- PRIVATE FUNCTIONS -- **/ + + /** + * Returns an array containing objects which have variable attributes + * + * @param {et2_widget} _widget + */ + _getVariableAttributeSet( _widget) + { + var variableAttributes = []; + + _widget.iterateOver(function(_widget) { + // Create the attribtues + var hasAttr = false; + var widgetData = { + "widget": _widget, + "data": [] + }; + + // Get all attribute values + for (var key in _widget.attributes) + { + if (!_widget.attributes[key].ignore && + typeof _widget.options[key] != "undefined") + { + var val = _widget.options[key]; + + // TODO: Improve detection + if (typeof val == "string" && val.indexOf("$") >= 0) + { + hasAttr = true; + widgetData.data.push({ + "attribute": key, + "expression": val + }); + } + } + } + + // Add the entry if there is any data in it + if (hasAttr) + { + variableAttributes.push(widgetData); + } + + }, this); + + return variableAttributes; + } + + _seperateWidgets( _varAttrs) + { + // The detachable array contains all widgets which implement the + // et2_IDetachedDOM interface for all needed attributes + var detachable = []; + + // The remaining array creates all widgets which have to be completely + // cloned when the widget tree is created + var remaining = []; + + // Iterate over the widgets + for (var i = 0; i < _varAttrs.length; i++) + { + var widget = _varAttrs[i].widget; + + // Check whether the widget parents are not allready in the "remaining" + // slot - if this is the case do not include the widget at all. + var insertWidget = true; + var checkWidget = function (_widget) { + if (_widget.parent != null) + { + for (var i = 0; i < remaining.length; i++) + { + if (remaining[i].widget == _widget.parent) + { + insertWidget = false; + return; + } + } + + checkWidget(_widget.parent); + } + }; + checkWidget(widget); + + // Handle the next widget if this one should not be included. + if (!insertWidget) + { + continue; + } + + // Check whether the widget implements the et2_IDetachedDOM interface + var isDetachable = false; + if (widget.implements(et2_IDetachedDOM)) + { + // Get all attributes the widgets supports to be set in the + // "detached" mode + var supportedAttrs = []; + widget.getDetachedAttributes(supportedAttrs); + supportedAttrs.push("id"); + isDetachable = true; + + for (var j = 0; j < _varAttrs[i].data.length/* && isDetachable*/; j++) + { + var data = _varAttrs[i].data[j]; + + var supportsAttr = supportedAttrs.indexOf(data.attribute) != -1; + + if (!supportsAttr) + { + egw.debug("warn", "et2_IDetachedDOM widget " + + widget._type + " does not support " + data.attribute); + } + + isDetachable = isDetachable && supportsAttr; + } + } + + // Insert the widget into the correct slot + if (isDetachable) + { + detachable.push(_varAttrs[i]); + } + else + { + remaining.push(_varAttrs[i]); + } + } + + return { + "detachable": detachable, + "remaining": remaining + }; + } + + /** + * Removes to DOM code for all widgets in the "remaining" slot + * + * @param {object} _rowTemplate + */ + _stripTemplateRow( _rowTemplate) + { + _rowTemplate.placeholders = []; + + for (var i = 0; i < _rowTemplate.seperated.remaining.length; i++) + { + var entry = _rowTemplate.seperated.remaining[i]; + + // Issue a warning - widgets which do not implement et2_IDOMNode + // are very slow + egw.debug("warn", "Non-clonable widget '"+ entry.widget._type + "' in dataview row - this " + + "might be slow", entry); + + // Set the placeholder for the entry to null + entry.placeholder = null; + + // Get the outer DOM-Node of the widget + if (entry.widget.implements(et2_IDOMNode)) + { + var node = entry.widget.getDOMNode(entry.widget); + + if (node && node.parentNode) + { + // Get the parent node and replace the node with a placeholder + entry.placeholder = document.createElement("span"); + node.parentNode.replaceChild(entry.placeholder, node); + _rowTemplate.placeholders.push({ + "widget": entry.widget, + "func": this._compileDOMAccessFunc(_rowTemplate.row, + entry.placeholder) + }); + } + } + } + } + + _nodeIndex( _node) + { + if(_node.parentNode == null) + { + return 0; + } + for (var i = 0; i < _node.parentNode.childNodes.length; i++) + { + if (_node.parentNode.childNodes[i] == _node) + { + return i; + } + } + + return -1; + } + + /** + * Returns a function which does a relative access on the given DOM-Node + * + * @param {DOMElement} _root + * @param {DOMElement} _target + */ + _compileDOMAccessFunc( _root, _target) + { + function recordPath(_root, _target, _path) + { + if (typeof _path == "undefined") + { + _path = []; + } + + if (_root != _target && _target) + { + // Get the index of _target in its parent node + var idx = this._nodeIndex(_target); + if (idx >= 0) + { + // Add the access selector + _path.unshift("childNodes[" + idx + "]"); + + // Record the remaining path + return recordPath.call(this, _root, _target.parentNode, _path); + } + + throw("Internal error while compiling DOM access function."); + } + else + { + _path.unshift("_node"); + return "return " + _path.join(".") + ";"; + } + } + + return new Function("_node", recordPath.call(this, _root, _target)); + } + + /** + * Builds relative paths to the DOM-Nodes and compiles fast-access functions + * + * @param {object} _rowTemplate + */ + _buildNodeAccessFuncs( _rowTemplate) + { + for (var i = 0; i < _rowTemplate.seperated.detachable.length; i++) + { + var entry = _rowTemplate.seperated.detachable[i]; + + // Get all needed nodes from the widget + var nodes = entry.widget.getDetachedNodes(); + var nodeFuncs = entry.nodeFuncs = new Array(nodes.length); + + // Record the path to each DOM-Node + for (var j = 0; j < nodes.length; j++) + { + nodeFuncs[j] = this._compileDOMAccessFunc(_rowTemplate.row, + nodes[j]); + } + } + } + + /** + * Match category-ids from class attribute eg. "cat_15" or "123,456,789 " + * + * Make sure to not match numbers inside other class-names. + * + * We can NOT use something like /(^| |,|cat_)([0-9]+)( |,|$)/g as it wont find all cats in "123,456,789 "! + */ + cat_regexp: RegExp = /(^| |,|cat_)([0-9]+)/g; + /** + * Regular expression used to filter out non-nummerical chars from above matches + */ + cat_cleanup: RegExp = /[^0-9]/g; + + /** + * Applies additional row data (like the class) to the tr + * + * @param {object} _data + * @param {DOMElement} _tr + * @param {object} _mgrs + */ + _setRowData( _data, _tr, _mgrs) + { + // TODO: Implement other fields than "class" + if (_data["class"]) + { + var classes = _mgrs["content"].expandName(_data["class"]); + + // Get fancy with categories + var cats = []; + // Assume any numeric class is a category + if(_data["class"].indexOf("cat") !== -1 || classes.match(/[0-9]+/)) + { + // Accept either cat, cat_id or category as ID, and look there for category settings + var category_location = _data["class"].match(/(cat(_id|egory)?)/); + if(category_location) category_location = category_location[0]; + + cats = classes.match(this.cat_regexp) || []; + classes = classes.replace(this.cat_regexp, ''); + + // Set category class + for(var i = 0; i < cats.length; i++) + { + // Need cat_, classes can't start with a number + var cat_id = cats[i].replace(this.cat_cleanup, ''); + var cat_class = 'cat_'+cat_id; + + classes += ' '+cat_class; + } + classes += " row_category"; + } + classes += " row"; + _tr.setAttribute("class", classes); + } + if(_data['valign']) + { + var align = _mgrs["content"].expandName(_data["valign"]); + _tr.setAttribute("valign", align); + } + } +} + +/** + * @augments et2_widget + */ +class et2_nextmatch_rowWidget extends et2_widget implements et2_IDOMNode +{ + private _widgets: any[]; + private _row: any; + /** + * Constructor + * + * @param _mgrs + * @param _row + * @memberOf et2_nextmatch_rowWidget + */ + constructor( _mgrs, _row) + { + // Call the parent constructor with some dummy attributes + super(null, {"id": "", "type": "rowWidget"}); + + // Initialize some variables + this._widgets = []; + + // Copy the given DOM node and the content arrays + this._mgrs = _mgrs; + this._row = _row; + } + + /** + * Copies the given array manager and clones the given widgets and inserts + * them into the row which has been passed in the constructor. + * + * @param {array} _widgets + */ + createWidgets( _widgets) + { + // Clone the given the widgets with this element as parent + this._widgets = new Array(_widgets.length); + for (var i = 0; i < _widgets.length; i++) + { + // Disabled columns might be missing widget - skip it + if(!_widgets[i]) continue; + + this._widgets[i] = _widgets[i].clone(this); + this._widgets[i].loadingFinished(); + // Set column alignment from widget + if(this._widgets[i].align) + { + this._row.childNodes[i].align = this._widgets[i].align; + } + } + } + + /** + * Returns the column node for the given sender + * + * @param {et2_widget} _sender + * @return {DOMElement} + */ + getDOMNode( _sender) + { + for (var i = 0; i < this._widgets.length; i++) + { + if (this._widgets[i] == _sender) + { + return this._row.childNodes[i].childNodes[0]; // Return the i-th td tag + } + } + + return null; + } + +} + +/** + * @augments et2_widget + */ +class et2_nextmatch_rowTemplateWidget extends et2_widget implements et2_IDOMNode +{ + private _root: any; + private _row: any; + private _widgets: any[]; + /** + * Constructor + * + * @param _root + * @param _row + * @memberOf et2_nextmatch_rowTemplateWidget + */ + constructor( _root, _row) + { + // Call the parent constructor with some dummy attributes + super(null, {"id": "", "type": "rowTemplateWidget"}); + + this._root = _root; + this._mgrs = {}; + this._row = _row; + + // Set parent to root widget, so sub-widget calls still work + this._parent = _root; + + // Clone the widgets inside the placeholders array + this._widgets = []; + } + + createWidgets( _mgrs, _widgets : {widget : et2_widget,func(_row: any): any; }[]) + { + // Set the array managers - don't use setArrayMgrs here as this creates + // an unnecessary copy of the object + this._mgrs = _mgrs; + + this._widgets = new Array(_widgets.length); + for (var i = 0; i < _widgets.length; i++) + { + this._row.childNodes[0].childNodes[0]; + + this._widgets[i] = { + "widget": _widgets[i].widget.clone(this), + "node": _widgets[i].func(this._row) + }; + this._widgets[i].widget.loadingFinished(); + } + } + + /** + * Returns the column node for the given sender + * + * @param {et2_widget} _sender + * @return {DOMElement} + */ + getDOMNode( _sender: et2_widget): HTMLElement + { + + for (var i = 0; i < this._widgets.length; i++) + { + if (this._widgets[i].widget == _sender) + { + return this._widgets[i].node; + } + } + + return null; + } + +} + diff --git a/api/js/etemplate/et2_types.d.ts b/api/js/etemplate/et2_types.d.ts index dd56cf3bfc..1b4c66feda 100644 --- a/api/js/etemplate/et2_types.d.ts +++ b/api/js/etemplate/et2_types.d.ts @@ -64,7 +64,7 @@ declare var et2_nextmatch_header : any; declare var et2_nextmatch_customfields : any; declare var et2_nextmatch_controller : any; declare var et2_dynheight : any; -declare var et2_nextmatch_rowProvider : any; +declare class et2_nextmatch_rowProvider {} declare var et2_nextmatch_rowWidget : any; declare var et2_nextmatch_rowTemplateWidget : any; declare var et2_ajaxSelect : any; diff --git a/api/js/etemplate/et2_widget_number.js b/api/js/etemplate/et2_widget_number.js index 2e22725c93..26603414d6 100644 --- a/api/js/etemplate/et2_widget_number.js +++ b/api/js/etemplate/et2_widget_number.js @@ -33,21 +33,21 @@ var et2_core_inheritance_1 = require("./et2_core_inheritance"); * * @augments et2_textbox */ -var et2_number = /** @class */ (function (_super_1) { - __extends(et2_number, _super_1); +var et2_number = /** @class */ (function (_super) { + __extends(et2_number, _super); /** * Constructor * * @memberOf et2_number */ function et2_number(_parent, _attrs, _child) { - var _this = _super_1.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_number._attributes, _child || {})) || this; + var _this = _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_number._attributes, _child || {})) || this; _this.min = null; _this.max = null; return _this; } et2_number.prototype.transformAttributes = function (_attrs) { - _super_1.prototype.transformAttributes.call(this, _attrs); + _super.prototype.transformAttributes.call(this, _attrs); if (typeof _attrs.validator == 'undefined') { _attrs.validator = _attrs.type == 'float' ? '/^-?[0-9]*[,.]?[0-9]*$/' : '/^-?[0-9]*$/'; } @@ -64,7 +64,7 @@ var et2_number = /** @class */ (function (_super_1) { _messages.push(this.input[0].validationMessage); ok = false; } - return _super_1.prototype.isValid.call(this, _messages) && ok; + return _super.prototype.isValid.call(this, _messages) && ok; }; et2_number.prototype.createInputWidget = function () { this.input = jQuery(document.createElement("input")); @@ -153,16 +153,16 @@ et2_core_widget_1.et2_register_widget(et2_number, ["int", "integer", "float"]); * @augments et2_textbox_ro * @class */ -var et2_number_ro = /** @class */ (function (_super_1) { - __extends(et2_number_ro, _super_1); +var et2_number_ro = /** @class */ (function (_super) { + __extends(et2_number_ro, _super); function et2_number_ro() { - return _super_1 !== null && _super_1.apply(this, arguments) || this; + return _super !== null && _super.apply(this, arguments) || this; } et2_number_ro.prototype.set_value = function (_value) { if (typeof this.options.precision != 'undefined' && "" + _value != "") { _value = parseFloat(_value).toFixed(this.options.precision); } - this._super.call(this, _value); + _super.prototype.set_value.call(this, _value); }; et2_number_ro._attributes = { min: { ignore: true }, diff --git a/api/js/etemplate/et2_widget_number.ts b/api/js/etemplate/et2_widget_number.ts index a6bcf60440..fc67dcbbde 100644 --- a/api/js/etemplate/et2_widget_number.ts +++ b/api/js/etemplate/et2_widget_number.ts @@ -179,7 +179,7 @@ class et2_number_ro extends et2_textbox_ro { _value = parseFloat(_value).toFixed(this.options.precision); } - this._super.call(this, _value); + super.set_value(_value); } } et2_register_widget(et2_number_ro, ["int_ro", "integer_ro", "float_ro"]); diff --git a/api/js/etemplate/et2_widget_radiobox.js b/api/js/etemplate/et2_widget_radiobox.js index dcabbfe16a..72079a5401 100644 --- a/api/js/etemplate/et2_widget_radiobox.js +++ b/api/js/etemplate/et2_widget_radiobox.js @@ -297,7 +297,7 @@ var et2_radioGroup = /** @class */ (function (_super) { et2_radioGroup.prototype.set_options = function (_options) { // Call the destructor of all children for (var i = this._children.length - 1; i >= 0; i--) { - this._children[i].free(); + this._children[i].destroy(); } this._children = []; // create radio buttons for each option diff --git a/api/js/etemplate/et2_widget_radiobox.ts b/api/js/etemplate/et2_widget_radiobox.ts index 9c8c338702..9a805240ab 100644 --- a/api/js/etemplate/et2_widget_radiobox.ts +++ b/api/js/etemplate/et2_widget_radiobox.ts @@ -367,7 +367,7 @@ class et2_radioGroup extends et2_valueWidget implements et2_IDetachedDOM // Call the destructor of all children for (let i = this._children.length - 1; i >= 0; i--) { - this._children[i].free(); + this._children[i].destroy(); } this._children = []; // create radio buttons for each option