@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
412 lines (370 loc) • 13.8 kB
JavaScript
/*!
* 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;
});