UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

412 lines (370 loc) 13.8 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 */ sap.ui.define([ "sap/base/Log", "sap/base/util/each", "sap/ui/model/ChangeReason", "sap/ui/model/Filter", "sap/ui/model/FilterType", "sap/ui/model/ListBinding", "sap/ui/model/FilterProcessor", "sap/ui/model/Sorter", "sap/ui/model/SorterProcessor" ], function (Log, each, ChangeReason, Filter, FilterType, ListBinding, FilterProcessor, Sorter, SorterProcessor) { "use strict"; /** * Creates a new ClientListBinding. * * This constructor should only be called by subclasses or model implementations, not by application or control code. * Such code should use {@link sap.ui.model.Model#bindList Model#bindList} on the corresponding model implementation instead. * * @param {sap.ui.model.Model} oModel Model instance that this binding is created for and that it belongs to * @param {string} sPath Binding path to be used for this binding, syntax depends on the concrete subclass * @param {sap.ui.model.Context} oContext Binding context relative to which a relative binding path will be resolved * @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] Map of optional parameters as defined by subclasses; this class does not introduce any own parameters * @throws {Error} When one of the filters uses an operator that is not supported by the underlying model implementation * * @class * List binding implementation for client models. * * @alias sap.ui.model.ClientListBinding * @extends sap.ui.model.ListBinding * @protected */ var ClientListBinding = ListBinding.extend("sap.ui.model.ClientListBinding", /** @lends sap.ui.model.ClientListBinding.prototype */ { constructor : function(oModel, sPath, oContext, aSorters, aFilters, mParameters){ ListBinding.apply(this, arguments); this.mNormalizeCache = {}; this.oModel.checkFilterOperation(this.aApplicationFilters); this.oCombinedFilter = FilterProcessor.combineFilters(this.aFilters, this.aApplicationFilters); this.bIgnoreSuspend = false; // the serialized context data for the contexts returned by the last call of getContexts // if extended change detection is enabled this.aLastContextData = undefined; // the contexts returned by the last call of getContexts if extended change detection is // enabled this.aLastContexts = undefined; // if this.bUseExtendedChangeDetection is true it is the end index calculated from the // defaulted values of the given iStartIndex and iLength from the last call of // getContexts this.iLastEndIndex = undefined; // the defaulted value of the given iLength from the last call of getContexts with // bKeepCurrent !== true this.iLastLength = undefined; // the defaulted value of the given iStartIndex from the last call of getContexts with // bKeepCurrent !== true this.iLastStartIndex = undefined; this.update(); }, metadata : { publicMethods : [ "getLength" ] } }); /** * Return contexts for the list or a specified subset of contexts * @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. * * @return {Array} the contexts array * @private */ ClientListBinding.prototype._getContexts = function(iStartIndex, iLength) { if (!iStartIndex) { iStartIndex = 0; } if (!iLength) { iLength = Math.min(this.iLength, this.oModel.iSizeLimit); } var iEndIndex = Math.min(iStartIndex + iLength, this.aIndices.length), oContext, aContexts = [], sPrefix = this.getResolvedPath(); if (sPrefix && !sPrefix.endsWith("/")) { sPrefix += "/"; } for (var i = iStartIndex; i < iEndIndex; i++) { oContext = this.oModel.getContext(sPrefix + this.aIndices[i]); aContexts.push(oContext); } return aContexts; }; /** * This helper function must be called only by {@link #getContexts}. It updates * <code>iLastStartIndex</code> and <code>iLastLength</code> of the current instance with the * given start index and length. If <code>bKeepCurrent</code> is set, throw an error if keeping * current contexts untouched is not supported, otherwise don't update * <code>iLastStartIndex</code> and <code>iLastLength</code>. * * @param {int} [iStartIndex] * The start index * @param {int} [iLength] * The length * @param {int} [iMaximumPrefetchSize] * Must not be used * @param {boolean} [bKeepCurrent] * Whether the result of {@link #getCurrentContexts} keeps untouched * @throws {Error} * If extended change detection is enabled and <code>bKeepCurrent</code> is set, or if * <code>iMaximumPrefetchSize</code> and <code>bKeepCurrent</code> are set * * @private */ ClientListBinding.prototype._updateLastStartAndLength = function (iStartIndex, iLength, iMaximumPrefetchSize, bKeepCurrent) { if (bKeepCurrent) { this._checkKeepCurrentSupported(iMaximumPrefetchSize); } else { this.iLastStartIndex = iStartIndex; this.iLastLength = iLength; } }; /** * 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. * * @param {int} [iStartIndex=0] * The start index 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] * Must not be used * @param {boolean} [bKeepCurrent] * Whether this call keeps the result of {@link #getCurrentContexts} untouched; since 1.102.0. * @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 */ ClientListBinding.prototype.getContexts = function (iStartIndex, iLength, iMaximumPrefetchSize, bKeepCurrent) { var aContextData, aContexts; // Do not update last start and length with the defaulted values as #checkUpdate would only // check in this range for changes. For controls that want to show all data the range must // not be limited. this._updateLastStartAndLength(iStartIndex, iLength, iMaximumPrefetchSize, bKeepCurrent); iStartIndex = iStartIndex || 0; iLength = iLength || Math.min(this.iLength, this.oModel.iSizeLimit); aContexts = this._getContexts(iStartIndex, iLength); if (this.bUseExtendedChangeDetection) { aContextData = []; // Use try/catch to detect issues with getting context data try { for (var i = 0; i < aContexts.length; i++) { aContextData.push(this.getContextData(aContexts[i])); } if (this.aLastContextData && iStartIndex < this.iLastEndIndex) { aContexts.diff = this.diffData(this.aLastContextData, aContextData); } this.iLastEndIndex = iStartIndex + iLength; this.aLastContextData = aContextData; this.aLastContexts = aContexts.slice(0); } catch (oError) { this.aLastContextData = undefined; this.aLastContexts = undefined; this.bUseExtendedChangeDetection = false; Log.warning( "Disabled extended change detection for binding path '" + this.getResolvedPath() + "'; context data could not be serialized", oError, this.getMetadata().getName()); } } return aContexts; }; // documented in sap.ui.model.ListBinding#getCurrentContexts ClientListBinding.prototype.getCurrentContexts = function() { if (this.bUseExtendedChangeDetection) { return this.aLastContexts || []; } else { return this.getContexts(this.iLastStartIndex, this.iLastLength); } }; /* * @see sap.ui.model.ListBinding#getAllCurrentContexts */ ClientListBinding.prototype.getAllCurrentContexts = function () { return this._getContexts(0, Infinity); }; /** * Setter for context * @param {Object} oContext the new context object */ ClientListBinding.prototype.setContext = function(oContext) { if (this.oContext != oContext) { this.oContext = oContext; if (this.isRelative()) { this.update(); this._fireChange({reason: ChangeReason.Context}); } } }; /* * @see sap.ui.model.ListBinding.prototype.getLength */ ClientListBinding.prototype.getLength = function() { return this.iLength; }; /** * Return the length of the list * * @return {int} the length */ ClientListBinding.prototype._getLength = function() { return this.aIndices.length; }; /** * Get indices of the list */ ClientListBinding.prototype.updateIndices = function(){ this.aIndices = []; for (var i = 0; i < this.oList.length; i++) { this.aIndices.push(i); } }; /* * @see sap.ui.model.ListBinding.prototype.sort */ ClientListBinding.prototype.sort = function(aSorters){ if (this.bSuspended) { this.checkUpdate(true); } if (!aSorters) { this.aSorters = null; this.updateIndices(); this.applyFilter(); } else { if (aSorters instanceof Sorter) { aSorters = [aSorters]; } this.aSorters = aSorters; this.applySort(); } this.bIgnoreSuspend = true; this._fireChange({reason: ChangeReason.Sort}); this._fireSort({sorter: aSorters}); this.bIgnoreSuspend = false; return this; }; /** * Sorts the list * @private */ ClientListBinding.prototype.applySort = function(){ var that = this; if (!this.aSorters || this.aSorters.length == 0) { return; } this.aIndices = SorterProcessor.apply(this.aIndices, this.aSorters, function(vRef, sPath) { return that.oModel.getProperty(sPath, that.oList[vRef]); }); }; /** * Applies a new set of filters to the list represented by this binding. * * See {@link sap.ui.model.ListBinding#filter ListBinding#filter} for a more detailed * description of list filtering. * * When no <code>sFilterType</code> is given, any previously configured application * filters are cleared and the given filters are used as control filters * * @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, then any previously configured application filters are * cleared and the given filters are used as control filters * @returns {this} returns <code>this</code> to facilitate method chaining * @throws {Error} When one of the filters uses an operator that is not supported by the underlying model implementation * @public */ ClientListBinding.prototype.filter = function(aFilters, sFilterType){ this.oModel.checkFilterOperation(aFilters); if (this.bSuspended) { this.checkUpdate(true); } this.updateIndices(); if (aFilters instanceof Filter) { aFilters = [aFilters]; } if (sFilterType == FilterType.Application) { this.aApplicationFilters = aFilters || []; } else if (sFilterType == FilterType.Control) { this.aFilters = aFilters || []; } else { //Previous behaviour this.aFilters = aFilters || []; this.aApplicationFilters = []; } this.oCombinedFilter = FilterProcessor.combineFilters(this.aFilters, this.aApplicationFilters); if (this.aFilters.length === 0 && this.aApplicationFilters.length === 0) { this.iLength = this._getLength(); } else { this.applyFilter(); } this.applySort(); this.bIgnoreSuspend = true; this._fireChange({reason: ChangeReason.Filter}); if (sFilterType == FilterType.Application) { this._fireFilter({filters: this.aApplicationFilters}); } else { this._fireFilter({filters: this.aFilters}); } this.bIgnoreSuspend = false; return this; }; /** * Filters the list * Filters are first grouped according to their binding path. * All filters belonging to a group are ORed and after that the * results of all groups are ANDed. * Usually this means, all filters applied to a single table column * are ORed, while filters on different table columns are ANDed. * Multiple MultiFilters are ORed. * * @private */ ClientListBinding.prototype.applyFilter = function(){ var that = this; this.aIndices = FilterProcessor.apply(this.aIndices, this.oCombinedFilter, function(vRef, sPath) { return that.oModel.getProperty(sPath, that.oList[vRef]); }, this.mNormalizeCache); this.iLength = this.aIndices.length; }; /* * @see sap.ui.model.ListBinding.prototype.getDistinctValues */ ClientListBinding.prototype.getDistinctValues = function(sPath){ var aResult = [], oMap = {}, sValue, that = this; each(this.oList, function(i, oContext) { sValue = that.oModel.getProperty(sPath, oContext); if (!oMap[sValue]) { oMap[sValue] = true; aResult.push(sValue); } }); return aResult; }; return ClientListBinding; });