@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
1,011 lines (904 loc) • 35 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2026 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
/*eslint-disable max-len */
/*global */
sap.ui.define([
"./BindingMode",
"./StaticBinding",
"./CompositeBinding",
"./FilterType",
"./FormatException",
"./ParseException",
"./ValidateException",
"./Context",
"./Type",
"sap/base/future",
"sap/base/Log",
"sap/base/assert",
"sap/ui/base/BindingInfo",
"sap/ui/base/Object",
"sap/base/util/ObjectPath",
"sap/ui/base/SyncPromise",
"sap/ui/base/ManagedObjectMetadata"
], function(
BindingMode,
StaticBinding,
CompositeBinding,
FilterType,
FormatException,
ParseException,
ValidateException,
Context,
Type,
future,
Log,
assert,
BindingInfo,
BaseObject,
ObjectPath,
SyncPromise,
ManagedObjectMetadata
) {
"use strict";
const makeArray = (vFilter) => {
if (vFilter === undefined) {
return [];
}
return Array.isArray(vFilter) ? vFilter : [vFilter];
};
/**
* Mixin for data binding support on the ManagedObject class.
* Comes as a dependency of the "sap/ui/model/Model" base class.
* The mixin is applied to the ManagedObject.prototype during the
* property propagation.
*/
var ManagedObjectBindingSupport = {
/*
* ObjectBinding
*/
_bindObject: function(oBindingInfo) {
var oBinding,
oContext,
sModelName,
oModel,
that = this;
var fnChangeHandler = function(oEvent) {
that.setElementBindingContext(oBinding.getBoundContext(), sModelName);
};
var fnDataStateChangeHandler = function(oEvent) {
var oDataState = oBinding.getDataState();
if (!oDataState) {
return;
}
//inform generic refreshDataState method
if (that.refreshDataState) {
that.refreshDataState('', oDataState);
}
};
sModelName = oBindingInfo.model;
oModel = this.getModel(sModelName);
oContext = this.getBindingContext(sModelName);
oBinding = oModel.bindContext(oBindingInfo.path, oContext, oBindingInfo.parameters);
if (oBindingInfo.suspended) {
oBinding.suspend(true);
}
oBinding.attachChange(fnChangeHandler);
oBindingInfo.binding = oBinding;
oBindingInfo.modelChangeHandler = fnChangeHandler;
oBindingInfo.dataStateChangeHandler = fnDataStateChangeHandler;
oBinding.attachEvents(oBindingInfo.events);
if (this.refreshDataState) {
oBinding.attachAggregatedDataStateChange(fnDataStateChangeHandler);
}
oBinding.initialize();
},
_unbindObject: function(oBindingInfo, sModelName, _bSkipUpdateBindingContext) {
if (oBindingInfo.binding) {
if (!this._bIsBeingDestroyed) {
this._detachObjectBindingHandlers(oBindingInfo);
}
oBindingInfo.binding.destroy();
}
delete this.mElementBindingContexts[sModelName];
if ( !_bSkipUpdateBindingContext ) {
this.updateBindingContext(false, sModelName);
this.propagateProperties(sModelName);
this.fireModelContextChange();
}
},
_detachObjectBindingHandlers: function(oBindingInfo) {
if (oBindingInfo.binding) {
oBindingInfo.binding.detachChange(oBindingInfo.modelChangeHandler);
oBindingInfo.binding.detachEvents(oBindingInfo.events);
if (this.refreshDataState) {
oBindingInfo.binding.detachAggregatedDataStateChange(oBindingInfo.dataStateChangeHandler);
}
}
},
/*
* Update Bindings
*/
updateBindings: function(bUpdateAll, sModelName) {
var that = this,
sName,
bCanCreate,
oBindingInfo;
/*
* Checks whether the binding for the given oBindingInfo became invalid because
* of the current model change (as identified by bUpdateAll and sModelName).
*
* Precondition: oBindingInfo contains a 'binding' object
*
* @param {object} oBindingInfo
* @returns {boolean} Whether the binding info became invalid
* @private
*/
function becameInvalid(oBindingInfo) {
var aParts = oBindingInfo.parts,
i;
if (aParts) {
if (aParts.length == 1) {
// simple property binding: invalid when the model has the same name (or updateall) and when the model instance differs
return (bUpdateAll || aParts[0].model == sModelName) && !oBindingInfo.binding.updateRequired(that.getModel(aParts[0].model));
} else {
// simple or composite binding: invalid when for any part the model has the same name (or updateall) and when the model instance for that part differs
for (i = 0; i < aParts.length; i++) {
if ( (bUpdateAll || aParts[i].model == sModelName) && !oBindingInfo.binding.aBindings[i].updateRequired(that.getModel(aParts[i].model)) ) {
return true;
}
}
}
} else {
// list or object binding: invalid when the model has the same name (or updateall) and when the model instance differs
return (bUpdateAll || oBindingInfo.model == sModelName) && !oBindingInfo.binding.updateRequired(that.getModel(oBindingInfo.model));
}
}
/*
* Remove binding, detach all events and destroy binding object
*/
function removeBinding(oBindingInfo) {
var oBinding = oBindingInfo.binding;
// Also tell the Control that the messages have been removed (if any)
if (that.refreshDataState) {
that.refreshDataState(sName, oBinding.getDataState());
}
oBinding.detachChange(oBindingInfo.modelChangeHandler);
if (oBindingInfo.modelRefreshHandler) { // only list bindings currently have a refresh handler attached
oBinding.detachRefresh(oBindingInfo.modelRefreshHandler);
}
oBinding.detachEvents(oBindingInfo.events);
that._removeBoundFilters(oBinding);
oBinding.destroy();
// remove all binding related data from the binding info
delete oBindingInfo.binding;
delete oBindingInfo.modelChangeHandler;
delete oBindingInfo.dataStateChangeHandler;
delete oBindingInfo.modelRefreshHandler;
}
// create object bindings if they don't exist yet
for ( sName in this.mObjectBindingInfos ) {
oBindingInfo = this.mObjectBindingInfos[sName];
bCanCreate = BindingInfo.isReady(oBindingInfo, this);
// if there is a binding and if it became invalid through the current model change, then remove it
if ( oBindingInfo.binding && becameInvalid(oBindingInfo) ) {
removeBinding(oBindingInfo);
// if model does not exists anymore, also delete the BindingContext
if (!bCanCreate) {
delete this.mElementBindingContexts[sName];
}
}
// if there is no binding and if all required information is available, create a binding object
if ( !oBindingInfo.binding && bCanCreate ) {
this._bindObject(oBindingInfo);
}
}
// create property and aggregation bindings if they don't exist yet
for ( sName in this.mBindingInfos ) {
oBindingInfo = this.mBindingInfos[sName];
// if there is a binding and if it became invalid through the current model change, then remove it
if ( oBindingInfo.binding && becameInvalid(oBindingInfo) ) {
if (this._observer) {
var sMember = oBindingInfo.factory ? "aggregation" : "property";
this._observer.bindingChange(this, sName, "remove", oBindingInfo, sMember);
}
removeBinding(oBindingInfo);
}
// if there is no binding and if all required information is available, create a binding object
if ( !oBindingInfo.binding && BindingInfo.isReady(oBindingInfo, this) ) {
if (oBindingInfo.factory) {
this._bindAggregation(sName, oBindingInfo);
} else {
this._bindProperty(sName, oBindingInfo);
}
}
}
},
updateProperty: function(sName) {
var oBindingInfo = this.mBindingInfos[sName],
oBinding = oBindingInfo.binding,
oPropertyInfo = this.getMetadata().getPropertyLikeSetting(sName),
that = this;
function handleException(oException) {
if (oException instanceof FormatException) {
that.fireFormatError({
element : that,
property : sName,
type : oBinding.getType(),
newValue : oBinding.getValue(),
oldValue : that[oPropertyInfo._sGetter](),
exception: oException,
message: oException.message
}, false, true); // bAllowPreventDefault, bEnableEventBubbling
Log.error("FormatException in property '" + sName + "' of '" + that + "': " + oException.message +
"\nHint: single properties referenced in composite bindings and within binding expressions are automatically converted " +
"into the type of the bound control property, unless a different 'targetType' is specified. targetType:'any' may avoid " +
"the conversion and lead to the expected behavior.");
oBindingInfo.skipModelUpdate++;
that.resetProperty(sName);
oBindingInfo.skipModelUpdate--;
} else {
throw oException;
}
}
// If model change was triggered by the property itself, don't call the setter again
if (oBindingInfo.skipPropertyUpdate) {
return;
}
SyncPromise.resolve().then(function() {
return oBinding.getExternalValue();
}).then(function(oValue) {
oBindingInfo.skipModelUpdate++;
that[oPropertyInfo._sMutator](oValue);
oBindingInfo.skipModelUpdate--;
}).catch(function(oException) {
handleException(oException);
}).unwrap();
},
updateModelProperty: function(sName, oValue, oOldValue){
var oBindingInfo, oBinding,
that = this;
function handleException(oException) {
var mErrorParameters = {
element: that,
property: sName,
type: oBinding.getType(),
newValue: oValue,
oldValue: oOldValue,
exception: oException,
message: oException.message
};
if (oException instanceof ParseException) {
that.fireParseError(mErrorParameters, false, true); // mParameters, bAllowPreventDefault, bEnableEventBubbling
} else if (oException instanceof ValidateException) {
that.fireValidationError(mErrorParameters, false, true); // mParameters, bAllowPreventDefault, bEnableEventBubbling
} else {
throw oException;
}
}
function handleSuccess() {
var mSuccessParameters = {
element: that,
property: sName,
type: oBinding.getType(),
newValue: oValue,
oldValue: oOldValue
};
// Only fire validation success, if a type is used
if (oBinding.hasValidation()) {
that.fireValidationSuccess(mSuccessParameters, false, true); // bAllowPreventDefault, bEnableEventBubbling
}
}
if (this.isBound(sName)) {
oBindingInfo = this.mBindingInfos[sName];
oBinding = oBindingInfo.binding;
// If property change was triggered by the model, don't update the model again
if (oBindingInfo.skipModelUpdate || (oBinding && oBinding.isSuspended())) {
return;
}
// only two-way bindings allow model updates
if (oBinding && oBinding.getBindingMode() == BindingMode.TwoWay) {
oBindingInfo.skipPropertyUpdate++;
SyncPromise.resolve(oValue).then(function(oValue) {
return oBinding.setExternalValue(oValue);
}).then(function() {
oBindingInfo.skipPropertyUpdate--;
return oBinding.getExternalValue();
}).then(function(oExternalValue) {
if (oValue != oExternalValue) {
that.updateProperty(sName);
}
handleSuccess();
}).catch(function(oException) {
oBindingInfo.skipPropertyUpdate--;
handleException(oException);
}).unwrap();
}
}
},
updateAggregation: function(sName, sChangeReason, oEventInfo) {
var oBindingInfo = this.mBindingInfos[sName],
oBinding = oBindingInfo.binding,
fnFactory = oBindingInfo.factory,
oAggregationInfo = this.getMetadata().getAggregation(sName), // TODO fix handling of hidden aggregations
sGroup,
bGrouped,
aContexts,
sGroupFunction = oAggregationInfo._sMutator + "Group",
that = this;
function getIdSuffix(oControl, iIndex) {
if (that.bUseExtendedChangeDetection) {
return ManagedObjectMetadata.uid('clone');
} else {
return oControl.getId() + "-" + iIndex;
}
}
// Update a single aggregation with the array of contexts. Reuse existing children
// and just append or remove at the end, if some are missing or too many.
function update(oControl, aContexts, fnBefore, fnAfter) {
var aChildren = oControl[oAggregationInfo._sGetter]() || [],
oContext,
oClone;
if (aChildren.length > aContexts.length) {
for (var i = aContexts.length; i < aChildren.length; i++) {
oClone = aChildren[i];
oControl[oAggregationInfo._sRemoveMutator](oClone);
oClone.destroy("KeepDom");
}
}
for (var i = 0; i < aContexts.length; i++) {
oContext = aContexts[i];
oClone = aChildren[i];
if (fnBefore) {
fnBefore(oContext);
}
if (oClone) {
oClone.setBindingContext(oContext, oBindingInfo.model);
} else {
oClone = fnFactory(getIdSuffix(oControl, i), oContext);
oClone.setBindingContext(oContext, oBindingInfo.model);
oControl[oAggregationInfo._sMutator](oClone);
}
if (fnAfter) {
fnAfter(oContext, oClone);
}
}
}
// Update a single aggregation with the array of contexts. Use the calculated diff to
// only add/remove children as the data has changed to minimize control updates and rendering
function updateDiff(oControl, aContexts) {
var aDiff = aContexts.diff,
aChildren = oControl[oAggregationInfo._sGetter]() || [],
oDiff, oClone, oContext, i;
// If no diff exists or aggregation is empty, fall back to default update
if (!aDiff || aChildren.length === 0) {
update(oControl, aContexts);
return;
}
// Loop through the diff and apply it
for (i = 0; i < aDiff.length; i++) {
oDiff = aDiff[i];
switch (oDiff.type) {
case "insert":
oContext = aContexts[oDiff.index];
oClone = fnFactory(getIdSuffix(oControl, oDiff.index), oContext);
oClone.setBindingContext(oContext, oBindingInfo.model);
oControl[oAggregationInfo._sInsertMutator](oClone, oDiff.index);
break;
case "delete":
oClone = oControl[oAggregationInfo._sRemoveMutator](oDiff.index);
oClone.destroy("KeepDom");
break;
default:
future.errorThrows("Unknown diff type \"" + oDiff.type + "\"");
}
}
// Loop through all children and set the binding context again. This is needed for
// indexed contexts, where inserting/deleting entries shifts the index of all following items
aChildren = oControl[oAggregationInfo._sGetter]() || [];
for (i = 0; i < aChildren.length; i++) {
aChildren[i].setBindingContext(aContexts[i], oBindingInfo.model);
}
}
// Check the current context for its group. If the group key changes, call the
// group function on the control.
function updateGroup(oContext) {
var oNewGroup = oBinding.getGroup(oContext);
if (oNewGroup.key !== sGroup) {
var oGroupHeader;
//If factory is defined use it
if (oBindingInfo.groupHeaderFactory) {
oGroupHeader = oBindingInfo.groupHeaderFactory(oNewGroup);
}
that[sGroupFunction](oNewGroup, oGroupHeader);
sGroup = oNewGroup.key;
}
}
// Update the tree recursively
function updateRecursive(oControl, oContexts) {
update(oControl, oContexts, null, function(oContext, oClone) {
updateRecursive(oClone, oBinding.getNodeContexts(oContext));
});
}
if (BaseObject.isObjectA(oBinding, "sap.ui.model.ListBinding")) {
aContexts = oBinding.getContexts(oBindingInfo.startIndex, oBindingInfo.length);
bGrouped = oBinding.isGrouped() && that[sGroupFunction];
if (bGrouped || oBinding.bWasGrouped) {
// If grouping is enabled, destroy aggregation and use updateGroup as fnBefore to create groups
this[oAggregationInfo._sDestructor]();
update(this, aContexts, bGrouped ? updateGroup : undefined);
} else if (this.bUseExtendedChangeDetection) {
// With extended change detection just update according to the diff
updateDiff(this, aContexts);
} else {
// If factory function is used without extended change detection, destroy aggregation
if (!oBindingInfo.template) {
this[oAggregationInfo._sDestructor]();
}
update(this, aContexts);
}
oBinding.bWasGrouped = bGrouped;
} else if (BaseObject.isObjectA(oBinding, "sap.ui.model.TreeBinding")) {
// Destroy all children in case a factory function is used
if (!oBindingInfo.template) {
this[oAggregationInfo._sDestructor]();
}
// In fnAfter call update recursively for the child nodes of the current tree node
updateRecursive(this, oBinding.getRootContexts());
}
},
updateBindingContext: function(bSkipLocal, sFixedModelName, bUpdateAll){
var oModel,
oModelNames = {},
sModelName,
oContext,
sName,
oBindingInfo,
aParts;
// Whether the binding part with the given index belongs to the current model name and is
// not a static binding
function isPartForModel(iPartIndex) {
return aParts[iPartIndex].model == sModelName && aParts[iPartIndex].value === undefined;
}
// find models that need a context update
if (bUpdateAll) {
for (sModelName in this.oModels) {
if ( this.oModels.hasOwnProperty(sModelName) ) {
oModelNames[sModelName] = sModelName;
}
}
for (sModelName in this.oPropagatedProperties.oModels) {
if ( this.oPropagatedProperties.oModels.hasOwnProperty(sModelName) ) {
oModelNames[sModelName] = sModelName;
}
}
} else {
oModelNames[sFixedModelName] = sFixedModelName;
}
for (sModelName in oModelNames ) {
if ( oModelNames.hasOwnProperty(sModelName) ) {
sModelName = sModelName === "undefined" ? undefined : sModelName;
oModel = this.getModel(sModelName);
oBindingInfo = this.mObjectBindingInfos[sModelName];
if (oModel && oBindingInfo && !bSkipLocal) {
if (!oBindingInfo.binding) {
this._bindObject(oBindingInfo);
} else {
oContext = this._getBindingContext(sModelName);
var oOldContext = oBindingInfo.binding.getContext();
if (Context.hasChanged(oOldContext, oContext)) {
oBindingInfo.binding.setContext(oContext);
}
}
continue;
}
oContext = this.getBindingContext(sModelName);
// update context in existing bindings
for ( sName in this.mBindingInfos ){
var oBindingInfo = this.mBindingInfos[sName],
oBinding = oBindingInfo.binding;
aParts = oBindingInfo.parts;
if (!oBinding) {
continue;
}
if (oBinding instanceof CompositeBinding) {
oBinding.setContext(oContext, {fnIsBindingRelevant : isPartForModel});
this.updateFieldHelp?.(sName);
} else if (oBindingInfo.factory) {
// list binding: update required when the model has the same name (or updateall)
if ( oBindingInfo.model == sModelName) {
oBinding.setContext(oContext);
this.updateFieldHelp?.(sName);
}
} else if (isPartForModel(0)) {
// simple property binding: update required when the model has the same name
oBinding.setContext(oContext);
this.updateFieldHelp?.(sName);
}
}
}
}
},
/*
* Refresh Bindings
*/
refreshAggregation: function(sName) {
var oBindingInfo = this.mBindingInfos[sName],
oBinding = oBindingInfo.binding;
oBinding.getContexts(oBindingInfo.startIndex, oBindingInfo.length);
},
/*
* Setter
*/
setElementBindingContext: function(oContext, sModelName){
assert(sModelName === undefined || (typeof sModelName === "string" && !/^(undefined|null)?$/.test(sModelName)), "sModelName must be a string or omitted");
var oOldContext = this.mElementBindingContexts[sModelName];
if (Context.hasChanged(oOldContext, oContext)) {
if (oContext === undefined) {
delete this.mElementBindingContexts[sModelName];
} else {
this.mElementBindingContexts[sModelName] = oContext;
}
this.updateBindingContext(true, sModelName);
this.propagateProperties(sModelName);
this.fireModelContextChange();
}
return this;
},
/*
* Property Binding
*/
_bindProperty: function(sName, oBindingInfo) {
var oModel,
oContext,
oBinding,
sMode,
sCompositeMode = BindingMode.TwoWay,
oPropertyInfo = this.getMetadata().getPropertyLikeSetting(sName), // TODO fix handling of hidden entities?
sInternalType = oPropertyInfo._iKind === /* PROPERTY */ 0 ? oPropertyInfo.type : oPropertyInfo.altTypes[0],
that = this,
aBindings = [],
fnModelChangeHandler = function(oEvent){
that.updateProperty(sName);
//clear Messages from Messaging
var oDataState = oBinding.getDataState();
if (oDataState) {
var oControlMessages = oDataState.getControlMessages();
if (oControlMessages && oControlMessages.length > 0) {
oDataState.setControlMessages([]); //remove the controlMessages before informing manager to avoid 'dataStateChange' event to fire
var Messaging = sap.ui.require("sap/ui/core/Messaging");
if (Messaging) {
Messaging.removeMessages(oControlMessages);
}
}
oDataState.setInvalidValue(undefined); //assume that the model always sends valid data
}
if (oBinding.getBindingMode() === BindingMode.OneTime && oBinding.isResolved()) {
// if binding is one time but not resolved yet we don't destroy it yet.
oBinding.detachChange(fnModelChangeHandler);
if (this.refreshDataState) {
oBinding.detachAggregatedDataStateChange(fnDataStateChangeHandler);
}
oBinding.detachEvents(oBindingInfo.events);
}
},
fnDataStateChangeHandler = function(){
var oDataState = oBinding.getDataState();
if (!oDataState) {
return;
}
//inform generic refreshDataState method
if (that.refreshDataState) {
that.refreshDataState(sName, oDataState);
}
},
fnResolveTypeClass = function(sTypeName, oInstance) {
var sModulePath = sTypeName.replace(/\./g, "/");
// 1. require probing
var TypeClass = sap.ui.require(sModulePath);
/**
* @deprecated
*/
if (!TypeClass) {
// 2. Global lookup
TypeClass = ObjectPath.get(sTypeName);
if (typeof TypeClass === "function" && !TypeClass._sapUiLazyLoader) {
future.errorThrows("The type class '" + sTypeName + "' is exported to the global namespace without being set as an export value of a UI5 module. " +
"This scenario will not be supported in the future and a separate UI5 module needs to be created which exports this type class.");
} else {
// 3. requireSync fallback
TypeClass = sap.ui.requireSync(sModulePath); // legacy-relevant
}
}
if (typeof TypeClass !== "function") {
throw new Error(`Cannot find type "${sTypeName}" used in control "${oInstance.getId()}"!`);
}
return TypeClass;
},
fnCreateTypeInstance = function(oBindingInfo) {
const vType = oBindingInfo.type;
let clType;
if (typeof vType == "string") {
clType = fnResolveTypeClass(vType, that);
} else if (typeof vType === "function" && vType.prototype instanceof Type) {
clType = vType;
}
if (clType) {
return new clType(oBindingInfo.formatOptions, oBindingInfo.constraints);
} else {
return vType;
}
};
oBindingInfo.parts.forEach(function(oPart) {
// get context and model for this part
oContext = that.getBindingContext(oPart.model);
oModel = that.getModel(oPart.model);
// Create type instance if needed
const oType = fnCreateTypeInstance(oPart);
if (oPart.value !== undefined) {
oBinding = new StaticBinding(oPart.value);
} else {
oBinding = oModel.bindProperty(oPart.path, oContext, oPart.parameters || oBindingInfo.parameters);
}
oBinding.setType(oType, oPart.targetType || sInternalType);
oBinding.setFormatter(oPart.formatter);
if (oPart.suspended) {
oBinding.suspend(true);
}
sMode = oPart.mode || (oModel && oModel.getDefaultBindingMode()) || BindingMode.TwoWay;
oBinding.setBindingMode(sMode);
// Only if all parts have twoway binding enabled, the composite binding will also have twoway binding
if (sMode !== BindingMode.TwoWay) {
sCompositeMode = BindingMode.OneWay;
}
oBinding.attachEvents(oPart.events);
aBindings.push(oBinding);
});
// check if we have a composite binding or a formatter function created by the BindingParser which has property textFragments
if (aBindings.length > 1 || ( oBindingInfo.formatter && oBindingInfo.formatter.textFragments )) {
// Create type instance if needed
const oType = fnCreateTypeInstance(oBindingInfo);
oBinding = new CompositeBinding(aBindings, oBindingInfo.useRawValues, oBindingInfo.useInternalValues);
oBinding.setType(oType, oBindingInfo.targetType || sInternalType);
oBinding.setBindingMode(oBindingInfo.mode || sCompositeMode);
} else {
oBinding = aBindings[0];
}
oBinding.attachChange(fnModelChangeHandler);
if (this.refreshDataState) {
oBinding.attachAggregatedDataStateChange(fnDataStateChangeHandler);
}
// set only one formatter function if any
// because the formatter gets the context of the element, we have to set the context via proxy to ensure compatibility
// for formatter function which is now called by the property binding
// proxy formatter here because "this" is the correct cloned object
if (typeof oBindingInfo.formatter === "function") {
oBinding.setFormatter(oBindingInfo.formatter.bind(this));
}
// Set additional information on the binding info
oBindingInfo.binding = oBinding;
oBindingInfo.modelChangeHandler = fnModelChangeHandler;
oBindingInfo.dataStateChangeHandler = fnDataStateChangeHandler;
oBinding.attachEvents(oBindingInfo.events);
oBinding.initialize();
this.updateFieldHelp?.(sName);
if (this._observer) {
this._observer.bindingChange(this, sName, "ready", oBindingInfo, "property");
}
},
_unbindProperty: function(oBindingInfo, sName){
var oBinding;
oBinding = oBindingInfo.binding;
if (oBinding) {
if (!this._bIsBeingDestroyed) {
this._detachPropertyBindingHandlers(sName);
}
oBinding.destroy();
/* to reset messages on a control we need to detach the datastate handler after destroy,
as binding destroy clears up validation messages */
if (this.refreshDataState && !this._bIsBeingDestroyed) {
oBinding.detachAggregatedDataStateChange(oBindingInfo.dataStateChangeHandler);
}
this.updateFieldHelp?.(sName);
}
},
_detachPropertyBindingHandlers: function(sName) {
var oBindingInfo = this.mBindingInfos[sName],
oBinding;
if (oBindingInfo) {
oBinding = oBindingInfo.binding;
if (oBinding) {
oBinding.detachChange(oBindingInfo.modelChangeHandler);
oBinding.detachEvents(oBindingInfo.events);
/* to reset messages on a control we need to detach the datastate handler after destroy,
as binding destroy clears up validation messages */
if (this.refreshDataState && this._bIsBeingDestroyed) {
oBinding.detachAggregatedDataStateChange(oBindingInfo.dataStateChangeHandler);
}
}
// For CompositeBindings the part bindings are kept in aBindings not in the BindingInfos.
const aBindings = oBindingInfo.aBindings;
aBindings?.forEach(function(oPartBinding, i) {
oPartBinding.detachEvents(oBindingInfo.parts[i].events);
});
}
},
/**
* Creates a bound filter control for the given filter and the given binding.
*
* @param {sap.ui.model.Filter} oFilter The filter
* @param {function} fnGetBinding The function returning the aggregation binding
*/
_createBoundFilter: function (oFilter, fnGetBinding) {
oFilter.setBound();
if (oFilter.isMultiFilter()) {
oFilter.aFilters.forEach((oFilter) => {
this._createBoundFilter(oFilter, fnGetBinding);
});
return;
}
const oValue1BindingInfo = BindingInfo.extract(oFilter.oValue1, /*oScope*/ undefined,
/*bDetectValue*/ true);
const oValue2BindingInfo = BindingInfo.extract(oFilter.oValue2, /*oScope*/ undefined,
/*bDetectValue*/ true);
if (!oValue1BindingInfo && !oValue2BindingInfo) {
return;
}
oFilter.setResolved(false);
sap.ui.require(["sap/ui/base/BoundFilter"], (BoundFilter) => {
const oBoundFilter = new BoundFilter({
value1: oFilter.oValue1,
value2: oFilter.oValue2
}, oFilter, fnGetBinding());
this.addDependent(oBoundFilter);
});
},
/**
* Creates bound filter controls for the bound filters in the given binding info in the dependents
* aggregation of this control to allow for resolution of binding expressions in these filters.
*
* @param {object} oFilters The filters as defined for sap.ui.base.ManagedObject.AggregationBindingInfo
* @param {sap.ui.model.Filter[]|sap.ui.model.Filter|undefined} oFilters.boundFilters The bound filters
* @param {sap.ui.model.Filter[]|sap.ui.model.Filter|undefined} oFilters.filters The constant filters
* @param {function} fnGetBinding The function returning the aggregation binding
* @returns {sap.ui.model.Filter[]|sap.ui.model.Filter|undefined}
* The filters to be used when the binding is created
*/
_processFilters: function (oFilters, fnGetBinding) {
// keep behavior before introduction of bound filters in case there are none
if (oFilters.boundFilters === undefined) {
return oFilters.filters;
}
const aBoundFilters = makeArray(oFilters.boundFilters);
aBoundFilters.forEach((oBoundFilter) => this._createBoundFilter(oBoundFilter, fnGetBinding));
return makeArray(oFilters.filters).concat(aBoundFilters);
},
/**
* Computes the given list binding's application filters by replacing application filters of the given type
* with the given filters, see {@link ListBinding#computeApplicationFilters}.
*
* @param {sap.ui.model.ListBinding} oBinding The list binding
* @param {sap.ui.model.Filter[]|sap.ui.model.Filter} [vFilters] The filters to be applied
* @param {sap.ui.model.FilterType} [sFilterType=sap.ui.model.FilterType.Application] The type of the filters
* @returns {sap.ui.model.Filter[]|sap.ui.model.Filter|undefined} The filters to be used for the binding after
* the update
* @throws {Error} For filter type <code>sap.ui.model.FilterType.Control</code>
*/
computeApplicationFilters: function (oBinding, vFilters, sFilterType) {
if (sFilterType === FilterType.Control) {
throw new Error("Must not use filter type Control");
}
if (oBinding._isBoundFilterUpdate()) {
return vFilters;
}
if (sFilterType === FilterType.ApplicationBound) {
this._removeBoundFilters(oBinding);
const aConstantFilters = oBinding.aApplicationFilters.filter((oFilter) => !oFilter.isBound());
return this._processFilters({boundFilters: vFilters, filters: aConstantFilters}, () => oBinding,
/*bIsTreeBinding*/ false);
}
const aBoundFilters = oBinding.aApplicationFilters.filter((oFilter) => oFilter.isBound());
if (aBoundFilters.length === 0) {
return vFilters;
}
return makeArray(vFilters).concat(aBoundFilters);
},
/*
* Aggregation Binding
*/
_bindAggregation: function(sName, oBindingInfo) {
var that = this,
oBinding,
oAggregationInfo = this.getMetadata().getAggregation(sName),
fnModelChangeHandler = function(oEvent){
oAggregationInfo.update(that, oEvent.getParameter("reason"), {
detailedReason: oEvent.getParameter("detailedReason")
});
},
fnModelRefreshHandler = function(oEvent){
oAggregationInfo.refresh(that, oEvent.getParameter("reason"));
},
fnDataStateChangeHandler = function(oEvent) {
var oDataState = oBinding.getDataState();
if (!oDataState) {
return;
}
//inform generic refreshDataState method
if (that.refreshDataState) {
that.refreshDataState(sName, oDataState);
}
};
const getBinding = () => oBinding;
const aFilters = this._processFilters(oBindingInfo, getBinding);
var oModel = this.getModel(oBindingInfo.model);
if (this.isTreeBinding(sName)) {
oBinding = oModel.bindTree(oBindingInfo.path, this.getBindingContext(oBindingInfo.model), aFilters, oBindingInfo.parameters, oBindingInfo.sorter);
} else {
oBinding = oModel.bindList(oBindingInfo.path, this.getBindingContext(oBindingInfo.model), oBindingInfo.sorter, aFilters, oBindingInfo.parameters);
if (this.bUseExtendedChangeDetection) {
assert(!this.oExtendedChangeDetectionConfig || !this.oExtendedChangeDetectionConfig.symbol, "symbol function must not be set by controls");
oBinding.enableExtendedChangeDetection(!oBindingInfo.template, oBindingInfo.key, this.oExtendedChangeDetectionConfig);
}
}
oBinding.computeApplicationFilters = this.computeApplicationFilters.bind(this, oBinding);
if (oBindingInfo.suspended) {
oBinding.suspend(true);
}
oBindingInfo.binding = oBinding;
oBindingInfo.modelChangeHandler = fnModelChangeHandler;
oBindingInfo.modelRefreshHandler = fnModelRefreshHandler;
oBindingInfo.dataStateChangeHandler = fnDataStateChangeHandler;
oBinding.attachChange(fnModelChangeHandler);
oBinding.attachRefresh(fnModelRefreshHandler);
oBinding.attachEvents(oBindingInfo.events);
if (this.refreshDataState) {
oBinding.attachAggregatedDataStateChange(fnDataStateChangeHandler);
}
oBinding.initialize();
if (this._observer) {
this._observer.bindingChange(this, sName, "ready", oBindingInfo, "aggregation");
}
},
/**
* Removes bound filters for the given binding from the dependents aggregation.
*
* @param {sap.ui.model.Binding} oBinding The binding
*/
_removeBoundFilters: function (oBinding) {
if (!oBinding.isA(["sap.ui.model.ListBinding", "sap.ui.model.TreeBinding"])) {
return;
}
this.getDependents?.().forEach((oDependent) => {
if (oDependent.isA("sap.ui.base.BoundFilter") && oDependent.getBinding() === oBinding) {
this.removeDependent(oDependent);
oDependent.destroy();
}
});
},
_unbindAggregation: function(oBindingInfo, sName){
if (oBindingInfo.binding) {
if (!this._bIsBeingDestroyed) {
this._detachAggregationBindingHandlers(sName);
this._removeBoundFilters(oBindingInfo.binding);
}
oBindingInfo.binding.destroy();
}
},
_detachAggregationBindingHandlers: function(sName) {
var oBindingInfo = this.mBindingInfos[sName];
if (oBindingInfo) {
if (oBindingInfo.binding) {
oBindingInfo.binding.detachChange(oBindingInfo.modelChangeHandler);
oBindingInfo.binding.detachRefresh(oBindingInfo.modelRefreshHandler);
oBindingInfo.binding.detachEvents(oBindingInfo.events);
if (this.refreshDataState) {
oBindingInfo.binding.detachAggregatedDataStateChange(oBindingInfo.dataStateChangeHandler);
}
}
}
}
};
return ManagedObjectBindingSupport;
});