UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

587 lines (548 loc) 22.6 kB
/*! * OpenUI5 * (c) Copyright 2009-2023 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ /*eslint-disable max-len */ // Provides an abstraction for list bindings sap.ui.define(['./Binding', './Filter', './FilterType', './Sorter', 'sap/base/util/array/diff'], function(Binding, Filter, FilterType, Sorter, diff) { "use strict"; /** * Constructor for ListBinding. * * @abstract * @class * ListBinding is a specific binding for lists in the model, which can be used to populate * Tables or ItemLists. * * @param {sap.ui.model.Model} oModel * Model instance that this binding belongs to * @param {string} sPath * Binding path for this binding; a relative path will be resolved relative to a given context * @param {sap.ui.model.Context} oContext * Context to be used to resolve a relative path * @param {sap.ui.model.Sorter|sap.ui.model.Sorter[]} [aSorters] * Initial sort order (can be either a sorter or an array of sorters) * @param {sap.ui.model.Filter|sap.ui.model.Filter[]} [aFilters] * Predefined filter/s (can be either a filter or an array of filters) * @param {object} [mParameters] * Additional, implementation-specific parameters that should be used by the new list binding; * this base class doesn't define any parameters, check the API reference for the concrete * model implementations to learn about their supported parameters (if any) * * @public * @alias sap.ui.model.ListBinding * @extends sap.ui.model.Binding */ var ListBinding = Binding.extend("sap.ui.model.ListBinding", /** @lends sap.ui.model.ListBinding.prototype */ { constructor : function(oModel, sPath, oContext, aSorters, aFilters, mParameters){ Binding.call(this, oModel, sPath, oContext, mParameters); this.aSorters = makeArray(aSorters, Sorter); this.aFilters = []; this.aApplicationFilters = makeArray(aFilters, Filter); this.oCombinedFilter = null; this.bUseExtendedChangeDetection = false; this.bDetectUpdates = true; }, metadata : { "abstract" : true, publicMethods : [ // methods "getContexts", "getCurrentContexts", "sort", "attachSort", "detachSort", "filter", "attachFilter", "detachFilter", "getDistinctValues", "isGrouped", "getLength", "isLengthFinal" ] } }); function makeArray(a, FNClass) { if ( Array.isArray(a) ) { return a; } return a instanceof FNClass ? [a] : []; } // the 'abstract methods' to be implemented by child classes /** * Returns all current contexts of this list binding in no special order. Just like * {@link #getCurrentContexts}, this method does not request any data from a back end and does * not change the binding's state. In contrast to {@link #getCurrentContexts}, it does not only * return those contexts that were last requested by a control, but all contexts that are * currently available in the binding. * * @returns {sap.ui.model.Context[]} * All current contexts of this list binding, in no special order * * @function * @name sap.ui.model.ListBinding.prototype.getAllCurrentContexts * @public * @since 1.97.0 */ /** * Returns an array of binding contexts for the bound target list. * * In case of extended change detection, the context array may have an additional * <code>diff</code> property, see * {@link topic:7cdff73f308b4b10bdf7d83b7aba72e7 documentation on extended change detection} for * details. * * <strong>Note:</strong>The public usage of this method is deprecated, as calls from outside of * controls will lead to unexpected side effects. To avoid this, use * {@link sap.ui.model.ListBinding.prototype.getCurrentContexts} instead. * * @function * @name sap.ui.model.ListBinding.prototype.getContexts * @param {int} [iStartIndex=0] * The startIndex where to start the retrieval of contexts * @param {int} [iLength=length of the list] * Determines how many contexts to retrieve beginning from the start index; default is the * whole list length up to the model's size limit; see {@link sap.ui.model.Model#setSizeLimit} * @param {int} [iMaximumPrefetchSize] * The maximum number of contexts to read before and after the given range; with this, * controls can prefetch data that is likely to be needed soon, e.g. when scrolling down in a * table; this parameter is model-specific and not implemented by all models * @param {boolean} [bKeepCurrent] * Whether this call keeps the result of {@link #getCurrentContexts} untouched; since 1.86.0. * This parameter is model-specific and not implemented by all models * @return {sap.ui.model.Context[]} * The array of contexts for each row of the bound list * @throws {Error} * If <code>bKeepCurrent</code> is set and extended change detection is enabled or * <code>iMaximumPrefetchSize</code> is set * * @protected */ /** * Applies a new set of filters to the list represented by this binding. * * Depending on the nature of the model (client or server), the operation might be * executed locally or on a server and it might execute asynchronously. * * <h4>Application and Control Filters</h4> * Each list binding maintains two separate lists of filters, one for filters defined by the * control that owns the binding, and another list for filters that an application can define in * addition. When executing the filter operation, both sets of filters are combined. * * By using the <code>sFilterType</code> parameter of the <code>filter</code> method, the * caller can control which set of filters is modified. If no type is given, then the * behavior depends on the model implementation and should be documented in the API reference * for that model. * * <h4>Auto-Grouping of Filters</h4> * Filters are first grouped according to their binding path. All filters belonging to the same * path are ORed, and after that the results of all paths are ANDed. Usually this means that all * filters applied to the same property are ORed, while filters on different properties are * ANDed. * Please use either the automatic grouping of filters (where applicable) or explicit * AND/OR filters, as a mixture of both is not supported. * * @param {sap.ui.model.Filter|sap.ui.model.Filter[]} aFilters * Single filter object or an array of filter objects * @param {sap.ui.model.FilterType} [sFilterType=undefined] * Type of the filter which should be adjusted; if no type is given, the behavior depends on * the model implementation * @return {this} * Returns <code>this</code> to facilitate method chaining * * @function * @name sap.ui.model.ListBinding.prototype.filter * @public */ /** * Sorts the list according to the sorter object. * * Instead of a single sorter also an array of sorters can be passed to the sort method. In this * case they are processed in the sequence in which they are contained in the array. * * <h4>Grouping</h4> * Sorting and grouping are closely related. In case a list should be grouped, it must be sorted * by the property to group with. Grouping is enabled by setting the <code>group</code> property * on the sorter object. If it is enabled, you can get the current group of an item using * {@link sap.ui.model.ListBinding.prototype.getGroup}. In case multiple sorters are provided, * grouping can only be done on the first sorter, nested grouping is not supported. * * @function * @name sap.ui.model.ListBinding.prototype.sort * @param {sap.ui.model.Sorter|sap.ui.model.Sorter[]} aSorters * The Sorter object or an array of sorters which defines the sort order * @return {this} * Returns <code>this</code> to facilitate method chaining * @public */ /** * Checks whether keeping current contexts untouched is supported. * * @param {int} [iMaximumPrefetchSize] * The maximum number of contexts to read before and after the given range * @throws {Error} * If extended change detection is enabled, or if <code>iMaximumPrefetchSize</code> is set * * @private */ ListBinding.prototype._checkKeepCurrentSupported = function (iMaximumPrefetchSize) { if (this.bUseExtendedChangeDetection) { throw new Error("Unsupported operation: " + this.getMetadata().getName() + "#getContexts, must not use bKeepCurrent if extended change detection is" + " enabled"); } if (iMaximumPrefetchSize) { throw new Error("Unsupported operation: " + this.getMetadata().getName() + "#getContexts, must not use both iMaximumPrefetchSize and bKeepCurrent"); } }; /** * Returns the contexts of this list binding as last requested by the control and in the same * order the control has received them. * * This method does not request any data from a back end and does not change the binding's * state. * * @return {sap.ui.model.Context[]} * The contexts of this list binding as last requested by the control and in the same order * the control has received them * * @since 1.28 * @public */ ListBinding.prototype.getCurrentContexts = function() { return this.getContexts(); }; /** * Returns the count of entries in the list, or <code>undefined</code> if it is unknown. * The count is by default identical to the list length if it is final. Concrete subclasses may, * however, override the method, for example: * <ul> * <li> for server-side models where lists are not completely read by the client, * <li> for lists representing hierarchical data. * </ul> * * @returns {number|undefined} The count of entries * @public * @see #getLength * @see #isLengthFinal * @since 1.93.0 */ ListBinding.prototype.getCount = function() { return this.isLengthFinal() ? this.getLength() : undefined; }; /** * Returns the number of entries in the list. * * This might be an estimated or preliminary length, in case the full length is not known yet, * see method {@link #isLengthFinal}. * * @return {int} Returns the number of entries in the list * @since 1.24 * @public */ ListBinding.prototype.getLength = function() { return 0; }; /** * Returns whether the length which can be retrieved using getLength() is a known, final length, * or a preliminary or estimated length which may change if further data is requested. * * @returns {boolean} Whether the length is final * @since 1.24 * @public */ ListBinding.prototype.isLengthFinal = function() { return true; }; // base methods, may be overridden by child classes /** * Returns list of distinct values for the given relative binding path. * * @param {string} sPath Relative binding path * @returns {Array} Array of distinct values. * * @public */ ListBinding.prototype.getDistinctValues = function(sPath) { return null; }; //Eventing and related /** * The <code>sort</code> event is fired when the list binding is sorted. * * @name sap.ui.model.ListBinding#sort * @event * @param {sap.ui.base.Event} oEvent * @public * @deprecated * As of version 1.11, use the <code>change</code> event. It now contains a parameter * <code>(reason : "sort")</code> when a sorter event is fired. */ /** * Attaches event handler <code>fnFunction</code> to the {@link #event:sort sort} event of this * <code>sap.ui.model.ListBinding</code>. * * When called, the context of the event handler (its <code>this</code>) will be bound to * <code>oListener</code> if specified, otherwise it will be bound to this * <code>sap.ui.model.ListBinding</code> itself. * * @param {function} fnFunction * The function to be called, when the event occurs * @param {object} [oListener] * Context object to call the event handler with; defaults to this <code>ListBinding</code> * itself * @protected * @deprecated * As of version 1.11, use the <code>change</code> event. It now contains a parameter * <code>(reason : "sort")</code> when a sorter event is fired. */ ListBinding.prototype.attachSort = function(fnFunction, oListener) { this.attachEvent("sort", fnFunction, oListener); }; /** * Detaches event handler <code>fnFunction</code> from the {@link #event:sort sort} event of * this <code>sap.ui.model.ListBinding</code>. * * @param {function} fnFunction * The function to be called, when the event occurs * @param {object} [oListener] * Context object on which the given function had to be called * @protected * @deprecated As of version 1.11, use the <code>change</code> event. */ ListBinding.prototype.detachSort = function(fnFunction, oListener) { this.detachEvent("sort", fnFunction, oListener); }; /** * Fires event {@link #event:sort sort} to attached listeners. * * @param {object} [oParameters] Parameters to pass along with the event. * @private * @deprecated * As of version 1.11, use the <code>change</code> event. It now contains a parameter * <code>(reason : "sort")</code> when a sorter event is fired. */ ListBinding.prototype._fireSort = function(oParameters) { this.fireEvent("sort", oParameters); }; /** * The <code>filter</code> event is fired when the list binding is filtered. * * @name sap.ui.model.ListBinding#filter * @event * @param {sap.ui.base.Event} oEvent * @public * @deprecated * As of version 1.11, use the <code>change</code> event. It now contains a parameter * <code>(reason : "filter")</code> when a filter event is fired. */ /** * Attaches event handler <code>fnFunction</code> to the {@link #event:filter filter} event of * this <code>sap.ui.model.ListBinding</code>. * * When called, the context of the event handler (its <code>this</code>) will be bound to * <code>oListener</code> if specified, otherwise it will be bound to this * <code>sap.ui.model.ListBinding</code> itself. * * @param {function} fnFunction * The function to be called, when the event occurs * @param {object} [oListener] * Context object to call the event handler with; defaults to this <code>ListBinding</code> * itself * @protected * @deprecated * As of version 1.11, use the <code>change</code> event. It now contains a parameter * <code>(reason : "filter")</code> when a filter event is fired. */ ListBinding.prototype.attachFilter = function(fnFunction, oListener) { this.attachEvent("filter", fnFunction, oListener); }; /** * Detaches event handler <code>fnFunction</code> from the {@link #event:filter filter} event of * this <code>sap.ui.model.ListBinding</code>. * * @param {function} fnFunction The function to be called, when the event occurs * @param {object} [oListener] On which object the given function had to be called * @protected * @deprecated As of version 1.11, use the <code>change</code> event. */ ListBinding.prototype.detachFilter = function(fnFunction, oListener) { this.detachEvent("filter", fnFunction, oListener); }; /** * Fires event {@link #event:filter filter} to attached listeners. * * @param {object} [oParameters] Parameters to pass along with the event. * @private * @deprecated * As of version 1.11, use the <code>change</code> event. It now contains a parameter * <code>(reason : "filter")</code> when a filter event is fired. */ ListBinding.prototype._fireFilter = function(oParameters) { this.fireEvent("filter", oParameters); }; /** * Indicates whether grouping is enabled for the binding. * Grouping is enabled for a list binding if at least one sorter exists on the binding and the * first sorter is a grouping sorter. * @public * @returns {boolean} Whether grouping is enabled */ ListBinding.prototype.isGrouped = function() { return !!(this.aSorters && this.aSorters[0] && this.aSorters[0].fnGroup); }; /** * Gets the group for the given context. * Must only be called if <code>isGrouped()</code> returns that grouping is enabled for this * binding. The grouping will be performed using the first sorter (in case multiple sorters are * defined). * @param {sap.ui.model.Context} oContext The binding context * @public * @returns {object} The group object containing a key property and optional custom properties * @see sap.ui.model.Sorter#getGroup */ ListBinding.prototype.getGroup = function(oContext) { return this.aSorters[0].getGroup(oContext); }; /** * Calculates delta of specified old data array and new data array. * * For more information, see {@link module:sap/base/util/array/diff}. * * @param {Array} aOld Old data array * @param {Array} aNew New data array * @returns {Array.<{type:string,index:int}>} List of update operations * @protected */ ListBinding.prototype.diffData = function(aOld, aNew) { return diff(aOld, aNew, this.oExtendedChangeDetectionConfig); }; /** * Enable extended change detection. * When extended change detection is enabled, the list binding provides detailed information * about changes, for example which entries have been removed or inserted. This can be utilized * by a control for fine-grained update of its elements. * Please see {@link sap.ui.model.ListBinding.prototype.getContexts} for more information. * * For models that do not have a unique key on each entry by default, a key property or function * can be set which is used to identify entries. * * @param {boolean} bDetectUpdates * Whether changes within the same entity should cause a delete and insert command * @param {function|string} vKey * The path of the property containing the key or a function getting the context as only * parameter to calculate a key to identify an entry * @param {object} oExtendedChangeDetectionConfig * The configuration for the change detection * @protected */ ListBinding.prototype.enableExtendedChangeDetection = function(bDetectUpdates, vKey, oExtendedChangeDetectionConfig /* restricted */) { this.bUseExtendedChangeDetection = true; this.bDetectUpdates = bDetectUpdates; this.oExtendedChangeDetectionConfig = oExtendedChangeDetectionConfig; if (typeof vKey === "string") { this.getEntryKey = function(oContext) { return oContext.getProperty(vKey); }; } else if (typeof vKey === "function") { this.getEntryKey = vKey; } if (this.update) { this.update(); } }; /** * Return the data used for the extended change detection. Dependent on the configuration this * can either be a serialization of the complete data, or just a unique key identifying the * entry. If grouping is enabled, the grouping key will also be included, to detect grouping * changes. * * @param {sap.ui.model.Context} oContext The context object * @returns {string} A string which is used for diff comparison */ ListBinding.prototype.getContextData = function(oContext) { var sContextData; if (this.getEntryKey && !this.bDetectUpdates) { sContextData = this.getEntryKey(oContext); if (this.isGrouped()) { sContextData += "-" + this.getGroup(oContext).key; } } else { sContextData = this.getEntryData(oContext); } return sContextData; }; /** * Return the entry data serialized as a string. The default implementation assumes a JS object * and uses JSON.stringify to serialize it. Subclasses may override as needed. * * @param {sap.ui.model.Context} oContext The context object * @returns {string} The serialized object data */ ListBinding.prototype.getEntryData = function(oContext) { return JSON.stringify(oContext.getObject()); }; /** * Returns the filters set via the constructor or via {@link #filter} for the given * {@link sap.ui.model.FilterType}. * * @param {sap.ui.model.FilterType} sFilterType * The FilterType * @returns {sap.ui.model.Filter[]} * An array of filters for the given filter type. * @throws {Error} * If no or an invalid filter type was given * @public * @since 1.96.0 */ ListBinding.prototype.getFilters = function (sFilterType) { switch (sFilterType) { case FilterType.Application: return this.aApplicationFilters && this.aApplicationFilters.slice() || []; case FilterType.Control: return this.aFilters && this.aFilters.slice() || []; default: throw new Error("Invalid FilterType: " + sFilterType); } }; /** * Return the filter information as an AST. The default implementation checks for * <code>this.oCombinedFilter</code>. Models not using this member may override the method. * Consumers must not rely on the origin information to be available as future filter * implementations will not provide this information. * * @param {boolean} bIncludeOrigin * Include information about the filter objects the tree has been created from * @returns {object} * The AST of the filter tree * @private * @ui5-restricted sap.ui.table, sap.ui.export */ ListBinding.prototype.getFilterInfo = function(bIncludeOrigin) { if (this.oCombinedFilter) { return this.oCombinedFilter.getAST(bIncludeOrigin); } return null; }; /** * Requests a {@link sap.ui.model.Filter} object which can be used to filter the list binding by * entries with model messages. With the filter callback, you can define if a message is * considered when creating the filter for entries with messages. * * The resulting filter does not consider application or control filters specified for this list * binding in its constructor or in its {@link #filter} method; add filters which you want to * keep with the "and" conjunction to the resulting filter before calling {@link #filter}. * * The implementation of this method is optional for model specific implementations of * <code>sap.ui.model.ListBinding</code>. Check for existence of this function before calling * it. * * @abstract * @function * @name sap.ui.model.ListBinding.prototype.requestFilterForMessages * @param {function(sap.ui.core.message.Message):boolean} [fnFilter] * A callback function to filter only relevant messages. The callback returns whether the * given {@link sap.ui.core.message.Message} is considered. If no callback function is given, * all messages are considered. * @returns {Promise<sap.ui.model.Filter|null>} * A Promise that resolves with a {@link sap.ui.model.Filter} representing the entries with * messages; it resolves with <code>null</code> if the binding is not resolved or if the * binding knows that there is no message for any entry * * @protected * @since 1.77.0 */ return ListBinding; });