@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
1,303 lines (1,219 loc) • 46.2 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 */
// Provides the base implementation for all model implementations
sap.ui.define([
'sap/ui/core/message/MessageProcessor',
'./ManagedObjectBindingSupport',
'./BindingMode',
'./Context',
'./Filter',
"sap/base/Log",
"sap/base/util/deepEqual",
"sap/base/util/each"
],
function(MessageProcessor, ManagedObjectBindingSupport, BindingMode, Context, Filter, Log, deepEqual,
each) {
"use strict";
/**
* The SAPUI5 Data Binding API.
*
* The default binding mode for model implementations (if not implemented otherwise) is two-way
* and the supported binding modes by the model are one-way, two-way and one-time. The default
* binding mode can be changed by the application for each model instance. A model
* implementation should specify its supported binding modes and set the default binding mode
* accordingly (e.g. if the model supports only one-way binding the default binding mode should
* also be set to one-way).
*
* The default size limit for models is 100. The size limit determines the number of entries
* used for the list bindings.
*
*
* @namespace
* @name sap.ui.model
* @public
*/
/**
* Constructor for a new Model.
*
* Every Model is a MessageProcessor that is able to handle Messages with the normal binding
* path syntax in the target.
*
* @class
* This is an abstract base class for model objects.
* @abstract
*
* @extends sap.ui.core.message.MessageProcessor
*
* @author SAP SE
* @version 1.147.0
*
* @public
* @alias sap.ui.model.Model
*/
var Model = MessageProcessor.extend("sap.ui.model.Model", /** @lends sap.ui.model.Model.prototype */ {
constructor : function () {
MessageProcessor.apply(this, arguments);
// active bindings, i.e. bindings with an attached change handler, see
// Binding#attachChange
this.aBindings = [];
// bindings to be removed after a timeout
this.oBindingsToRemove = new Set();
// maps the absolute binding path to a context instance
this.mContexts = {};
// the data
this.oData = {};
// the default binding mode
this.sDefaultBindingMode = BindingMode.TwoWay;
// whether this model is destroyed
this.bDestroyed = false;
// stored parameter for an upcoming #checkUpdate; needed for async case
this.bForceUpdate = undefined;
// whether to use the legacy path syntax handling
this.bLegacySyntax = false;
// maps a resolved binding path to an array of sap.ui.core.message.Message
this.mMessages = {};
// the id of the timeout for removing bindings
this.sRemoveTimer = null;
// the model's size limit
this.iSizeLimit = 100;
// maps a sap.ui.model.BindingMode to true if the binding mode is supported
this.mSupportedBindingModes = {"OneWay" : true, "TwoWay" : true, "OneTime" : true};
// maps a sap.ui.model.FilterOperator to true if the filter operator is not supported
this.mUnsupportedFilterOperators = {};
// the id of the timeout for calling #checkUpdate
this.sUpdateTimer = null;
// the number of consecutive synchronous calls to #setProperty
this.iSyncSetPropertyCalls = 0;
// the overall number of bindings affected by consecutive sync #setProperty calls
this.iUpdatedBindings = 0;
// the id of timeout for performance check in #setProperty
this.iTimeoutId = undefined;
},
metadata : {
"abstract" : true,
publicMethods : [
// methods
"bindProperty", "bindList", "bindTree", "bindContext", "createBindingContext", "destroyBindingContext", "getProperty",
"getDefaultBindingMode", "setDefaultBindingMode", "isBindingModeSupported", "attachParseError", "detachParseError",
"attachRequestCompleted", "detachRequestCompleted", "attachRequestFailed", "detachRequestFailed", "attachRequestSent",
"detachRequestSent", "attachPropertyChange", "detachPropertyChange", "setSizeLimit", "refresh", "isList", "getObject"
]
}
});
/**
* Map of event names, that are provided by the model.
*/
Model.M_EVENTS = {
/**
* Depending on the model implementation a ParseError should be fired if a parse error
* occurred.
* Contains the parameters:
* errorCode, url, reason, srcText, line, linepos, filepos
*/
ParseError : "parseError",
/**
* Depending on the model implementation a RequestFailed should be fired if a request to a
* backend failed.
* Contains the parameters:
* message, statusCode, statusText and responseText
*/
RequestFailed : "requestFailed",
/**
* Depending on the model implementation a RequestSent should be fired when a request to a
* backend is sent.
* Contains Parameters:
* url, type, async, info (<b>deprecated</b>), infoObject
*/
RequestSent : "requestSent",
/**
* Depending on the model implementation a RequestCompleted should be fired when a request
* to a backend is completed regardless if the request failed or succeeded.
* Contains Parameters:
* url, type, async, info (<b>deprecated</b>), infoObject, success, errorobject
*/
RequestCompleted : "requestCompleted",
/**
* Event is fired when changes occur to a property value in the model. The event contains a
* reason parameter which describes the cause of the property value change. Currently the
* event is only fired with reason <code>sap.ui.model.ChangeReason.Binding</code> which is
* fired when two way changes occur to a value of a property binding.
* Contains the parameters:
* reason, path, context, value
*/
PropertyChange : "propertyChange"
};
/**
* The <code>requestFailed</code> event is fired, when data retrieval from a backend failed.
*
* Note: Subclasses might add additional parameters to the event object. Optional parameters can
* be omitted.
*
* @name sap.ui.model.Model#requestFailed
* @event
* @param {sap.ui.base.Event} oEvent
* @param {sap.ui.base.EventProvider} oEvent.getSource
* @param {object} oEvent.getParameters
* @param {string} oEvent.getParameters.message
* A text that describes the failure.
* @param {string|number} oEvent.getParameters.statusCode
* HTTP status code returned by the request (if available)
* @param {string} oEvent.getParameters.statusText
* The status as a text, details not specified, intended only for diagnosis output
* @param {string} [oEvent.getParameters.responseText]
* Response that has been received for the request, as a text string
* @public
*/
/**
* Attaches event handler <code>fnFunction</code> to the
* {@link #event:requestFailed requestFailed} event of this <code>sap.ui.model.Model</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.Model</code> itself.
*
* @param {object} [oData]
* An application-specific payload object that will be passed to the event handler along with
* the event object when firing the event
* @param {function} fnFunction
* The function to be called when the event occurs
* @param {object} [oListener=this]
* Context object to call the event handler with. Defaults to this
* <code>sap.ui.model.Model</code> itself
*
* @returns {this}
* Reference to <code>this</code> in order to allow method chaining
* @public
*/
Model.prototype.attachRequestFailed = function(oData, fnFunction, oListener) {
this.attachEvent("requestFailed", oData, fnFunction, oListener);
return this;
};
/**
* Detaches event handler <code>fnFunction</code> from the
* {@link #event:requestFailed requestFailed} event of this <code>sap.ui.model.Model</code>.
*
* The passed function and listener object must match the ones used for event registration.
*
* @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
*
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @public
*/
Model.prototype.detachRequestFailed = function(fnFunction, oListener) {
this.detachEvent("requestFailed", fnFunction, oListener);
return this;
};
/**
* Fires event {@link #event:requestFailed requestFailed} to attached listeners.
*
* @param {object} [oParameters]
* Parameters to pass along with the event
* @param {string} [oParameters.message]
* A text that describes the failure.
* @param {string} [oParameters.statusCode]
* HTTP status code returned by the request (if available)
* @param {string} [oParameters.statusText]
* The status as a text, details not specified, intended only for diagnosis output
* @param {string} [oParameters.responseText]
* Response that has been received for the request, as a text string
*
* @returns {this}
* Reference to <code>this</code> in order to allow method chaining
* @protected
*/
Model.prototype.fireRequestFailed = function(oParameters) {
this.fireEvent("requestFailed", oParameters);
return this;
};
/**
* The <code>parseError</code> event is fired when parsing of a model document (e.g. XML
* response) fails.
*
* @name sap.ui.model.Model#parseError
* @event
* @param {sap.ui.base.Event} oEvent
* @param {sap.ui.base.EventProvider} oEvent.getSource
* @param {object} oEvent.getParameters
* @param {int} oEvent.getParameters.errorCode
* @param {string} oEvent.getParameters.url
* @param {string} oEvent.getParameters.reason
* @param {string} oEvent.getParameters.srcText
* @param {int} oEvent.getParameters.line
* @param {int} oEvent.getParameters.linepos
* @param {int} oEvent.getParameters.filepos
* @public
*/
/**
* Attaches event handler <code>fnFunction</code> to the
* {@link #event:parseError parseError} event of this <code>sap.ui.model.Model</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.Model</code> itself.
*
* @param {object} [oData]
* An application-specific payload object that will be passed to the event handler along with
* the event object when firing the event
* @param {function} fnFunction
* The function to be called when the event occurs
* @param {object} [oListener=this]
* Context object to call the event handler with. Defaults to this
* <code>sap.ui.model.Model</code> itself.
*
* @returns {this}
* Reference to <code>this</code> in order to allow method chaining
* @public
*/
Model.prototype.attachParseError = function(oData, fnFunction, oListener) {
this.attachEvent("parseError", oData, fnFunction, oListener);
return this;
};
/**
* Detaches event handler <code>fnFunction</code> from the
* {@link #event:parseError parseError} event of this <code>sap.ui.model.Model</code>.
*
* The passed function and listener object must match the ones used for event registration.
*
* @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
*
* @returns {this}
* Reference to <code>this</code> in order to allow method chaining
* @public
*/
Model.prototype.detachParseError = function(fnFunction, oListener) {
this.detachEvent("parseError", fnFunction, oListener);
return this;
};
/**
* Fires event {@link #event:parseError parseError} to attached listeners.
*
* @param {object} [oParameters]
* Parameters to pass along with the event; May contain the following parameters:
* <code>errorCode</code>, <code>url</code>, <code>reason</code>, <code>srcText</code>,
* <code>line</code>, <code>linePos</code>, <code>filePos</code>
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @protected
*/
Model.prototype.fireParseError = function(oParameters) {
this.fireEvent("parseError", oParameters);
return this;
};
/**
* The <code>requestSent</code> event is fired, after a request has been sent to a backend.
*
* Note: Subclasses might add additional parameters to the event object. Optional parameters can
* be omitted.
*
* @name sap.ui.model.Model#requestSent
* @event
* @param {sap.ui.base.Event} oEvent
* @param {sap.ui.base.EventProvider} oEvent.getSource
* @param {object} oEvent.getParameters
* @param {string} oEvent.getParameters.url
* The url which is sent to the back end
* @param {string} [oEvent.getParameters.type]
* The type of the request (if available)
* @param {boolean} [oEvent.getParameters.async]
* If the request is synchronous or asynchronous (if available)
* @param {string} [oEvent.getParameters.info]
* <b>Deprecated as of version 1.38.0.</b>, additional information for the request (if available)
* @param {object} [oEvent.getParameters.infoObject]
* Additional information for the request (if available)
* @public
*/
/**
* Attaches event handler <code>fnFunction</code> to the
* {@link #event:requestSent requestSent} event of this <code>sap.ui.model.Model</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.Model</code> itself.
*
* @param {object} [oData]
* An application-specific payload object that will be passed to the event handler along with
* the event object when firing the event
* @param {function} fnFunction
* The function to be called when the event occurs
* @param {object} [oListener=this]
* Context object to call the event handler with. Defaults to this
* <code>sap.ui.model.Model</code> itself
*
* @returns {this}
* Reference to <code>this</code> in order to allow method chaining
* @public
*/
Model.prototype.attachRequestSent = function(oData, fnFunction, oListener) {
this.attachEvent("requestSent", oData, fnFunction, oListener);
return this;
};
/**
* Detaches event handler <code>fnFunction</code> from the
* {@link #event:requestSent requestSent} event of this <code>sap.ui.model.Model</code>.
*
* The passed function and listener object must match the ones used for event registration.
*
* @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
*
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @public
*/
Model.prototype.detachRequestSent = function(fnFunction, oListener) {
this.detachEvent("requestSent", fnFunction, oListener);
return this;
};
/**
* Fires event {@link #event:requestSent requestSent} to attached listeners.
*
* @param {object} [oParameters]
* Parameters to pass along with the event
* @param {string} [oParameters.url]
* The url which is sent to the back end.
* @param {string} [oParameters.type]
* The type of the request (if available)
* @param {boolean} [oParameters.async]
* If the request is synchronous or asynchronous (if available)
* @param {string} [oParameters.info]
* <b>Deprecated as of version 1.38.0.</b>, additional information for the request (if available)
* @param {object} [oParameters.infoObject]
* Additional information for the request (if available)
*
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @protected
*/
Model.prototype.fireRequestSent = function(oParameters) {
this.fireEvent("requestSent", oParameters);
return this;
};
/**
* The <code>requestCompleted</code> event is fired, after a request has been completed
* (includes receiving a response), no matter whether the request succeeded or not.
*
* Note: Subclasses might add additional parameters to the event object. Optional parameters can
* be omitted.
*
* @name sap.ui.model.Model#requestCompleted
* @event
* @param {sap.ui.base.Event} oEvent
* The <code>requestCompleted</code> event
* @param {sap.ui.base.EventProvider} oEvent.getSource
* The source of the request
* @param {object} oEvent.getParameters
* The request parameters
* @param {string} oEvent.getParameters.url
* URL which was sent to the back end
* @param {string} [oEvent.getParameters.type]
* Type of the request (if available)
* @param {boolean} oEvent.getParameters.success
* Whether the request has been successful or not. In case of errors, consult the optional
* <code>errorobject</code> parameter.
* @param {object} [oEvent.getParameters.errorobject]
* If the request failed the error if any can be accessed in this property.
* @param {boolean} [oEvent.getParameters.async]
* If the request is synchronous or asynchronous (if available)
* @param {string} [oEvent.getParameters.info]
* <b>Deprecated as of version 1.38.0.</b>, additional information for the request (if available)
* @param {object} [oEvent.getParameters.infoObject]
* Additional information for the request (if available)
* @public
*/
/**
* Attaches event handler <code>fnFunction</code> to the
* {@link #event:requestCompleted requestCompleted} event of this
* <code>sap.ui.model.Model</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.Model</code> itself.
*
* @param {object} [oData]
* An application-specific payload object that will be passed to the event handler along with
* the event object when firing the event
* @param {function} fnFunction
* The function to be called when the event occurs
* @param {object} [oListener=this]
* Context object to call the event handler with. Defaults to this
* <code>sap.ui.model.Model</code> itself
*
* @returns {this}
* Reference to <code>this</code> in order to allow method chaining
* @public
*/
Model.prototype.attachRequestCompleted = function(oData, fnFunction, oListener) {
this.attachEvent("requestCompleted", oData, fnFunction, oListener);
return this;
};
/**
* Detaches event handler <code>fnFunction</code> from the
* {@link #event:requestCompleted requestCompleted} event of this
* <code>sap.ui.model.Model</code>.
*
* The passed function and listener object must match the ones used for event registration.
*
* @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
*
* @returns {this}
* Reference to <code>this</code> in order to allow method chaining
* @public
*/
Model.prototype.detachRequestCompleted = function(fnFunction, oListener) {
this.detachEvent("requestCompleted", fnFunction, oListener);
return this;
};
/**
* Fires event {@link #event:requestCompleted requestCompleted} to attached listeners.
*
* @param {object} [oParameters]
* Parameters to pass along with the event
* @param {string} [oParameters.url]
* The url which was sent to the back end.
* @param {string} [oParameters.type]
* The type of the request (if available)
* @param {boolean} [oParameters.async]
* If the request was synchronous or asynchronous (if available)
* @param {string} [oParameters.info]
* <b>Deprecated as of version 1.38.0.</b>, additional information for the request (if available)
* @param {object} [oParameters.infoObject]
* Additional information for the request (if available)
*
* @returns {this}
* Reference to <code>this</code> in order to allow method chaining
* @protected
*/
Model.prototype.fireRequestCompleted = function(oParameters) {
this.fireEvent("requestCompleted", oParameters);
return this;
};
/**
* Fires event {@link #event:propertyChange propertyChange} to attached listeners.
*
* @param {object} [oParameters] Parameters to pass along with the event
* @param {sap.ui.model.ChangeReason} [oParameters.reason] The reason of the property change
* @param {string} [oParameters.path] The path of the property
* @param {object} [oParameters.context] The context of the property
* @param {object} [oParameters.value] The value of the property
*
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @protected
*/
Model.prototype.firePropertyChange = function(oParameters) {
this.fireEvent("propertyChange", oParameters);
return this;
};
/**
* The <code>propertyChange</code> event is fired when changes occur to a property value in the
* model.
*
* The event contains a <code>reason</code> parameter which describes the cause of the property
* value change. Currently the event is only fired with reason
* <code>sap.ui.model.ChangeReason.Binding</code> which is fired when two way changes occur to a
* value of a property binding.
*
* Note: Subclasses might add additional parameters to the event object. Optional parameters can
* be omitted.
*
* @name sap.ui.model.Model#propertyChange
* @event
* @param {sap.ui.base.Event} oEvent
* @param {sap.ui.base.EventProvider} oEvent.getSource
* @param {object} oEvent.getParameters
* @param {sap.ui.model.ChangeReason} oEvent.getParameters.reason
* The cause of the property value change
* @param {string} oEvent.getParameters.path
* The path of the property
* @param {sap.ui.model.Context} [oEvent.getParameters.context]
* The binding context (if available)
* @param {any} oEvent.getParameters.value
* The current value of the property
* @public
* @since 1.40
*/
/**
* Attaches event handler <code>fnFunction</code> to the
* {@link #event:propertyChange propertyChange} event of this <code>sap.ui.model.Model</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.Model</code> itself.
*
* @param {object} [oData]
* An application-specific payload object that will be passed to the event handler along with
* the event object when firing the event
* @param {function} fnFunction
* The function to be called when the event occurs
* @param {object} [oListener=this]
* Context object to call the event handler with. Defaults to this
* <code>sap.ui.model.Model</code> itself
*
* @returns {this}
* Reference to <code>this</code> in order to allow method chaining
* @public
*/
Model.prototype.attachPropertyChange = function(oData, fnFunction, oListener) {
this.attachEvent("propertyChange", oData, fnFunction, oListener);
return this;
};
/**
* Detaches event handler <code>fnFunction</code> from the
* {@link #event:propertyChange propertyChange} event of this <code>sap.ui.model.Model</code>.
*
* The passed function and listener object must match the ones used for event registration.
*
* @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
*
* @returns {this}
* Reference to <code>this</code> in order to allow method chaining
* @public
*/
Model.prototype.detachPropertyChange = function(fnFunction, oListener) {
this.detachEvent("propertyChange", fnFunction, oListener);
return this;
};
/**
* @abstract
*
* @name sap.ui.model.Model.prototype.bindProperty
* @function
* @param {string} sPath The path pointing to the property that should be bound
* @param {sap.ui.model.Context} [oContext] The context object for this databinding
* @param {object} [mParameters] Additional model-specific parameters
*
* @return {sap.ui.model.PropertyBinding} The newly created binding
* @public
*/
/**
* @abstract
*
* @name sap.ui.model.Model.prototype.bindList
* @function
* @param {string} sPath
* The path pointing to the list / array that should be bound
* @param {sap.ui.model.Context} [oContext]
* The context object for this databinding
* @param {sap.ui.model.Sorter[]|sap.ui.model.Sorter} [aSorters=[]]
* The sorters used initially; call {@link sap.ui.model.ListBinding#sort} to replace them
* @param {sap.ui.model.Filter[]|sap.ui.model.Filter} [aFilters=[]]
* The filters to be used initially with type {@link sap.ui.model.FilterType.Application}; call
* {@link sap.ui.model.ListBinding#filter} to replace them
* @param {object} [mParameters]
* Additional model-specific parameters
* @throws {Error} If the {@link sap.ui.model.Filter.NONE} filter instance is contained in <code>aFilters</code>
* together with other filters
*
* @return {sap.ui.model.ListBinding}
* The newly created binding
* @public
*/
/**
* @abstract
*
* @name sap.ui.model.Model.prototype.bindTree
* @function
* @param {string} sPath
* The path pointing to the tree / array that should be bound
* @param {sap.ui.model.Context} [oContext]
* The context object for this databinding
* @param {sap.ui.model.Filter[]|sap.ui.model.Filter} [aFilters=[]]
* The filters to be used initially with type {@link sap.ui.model.FilterType.Application}; call
* {@link sap.ui.model.TreeBinding#filter} to replace them
* @param {object} [mParameters]
* Additional model specific parameters
* @param {sap.ui.model.Sorter[]|sap.ui.model.Sorter} [aSorters=[]]
* The sorters used initially; call {@link sap.ui.model.TreeBinding#sort} to replace them
* @throws {Error} If the {@link sap.ui.model.Filter.NONE} filter instance is contained in <code>aFilters</code>
* together with other filters
*
* @return {sap.ui.model.TreeBinding}
* The newly created binding
* @public
*/
/**
* @abstract
*
* @name sap.ui.model.Model.prototype.createBindingContext
* @function
* @param {string} sPath
* The path to create the new context from
* @param {sap.ui.model.Context} [oContext]
* The context which should be used to create the new binding context
* @param {object} [mParameters]
* The parameters used to create the new binding context
* @param {function} [fnCallBack]
* The function which should be called after the binding context has been created
* @param {boolean} [bReload]
* Force reload even if data is already available; for server-side models this should refetch
* the data from the server
*
* @return {sap.ui.model.Context|undefined}
* The binding context, if it could be created synchronously
* @public
*/
/**
* @abstract
*
* @name sap.ui.model.Model.prototype.destroyBindingContext
* @function
* @param {sap.ui.model.Context} oContext The context to destroy
* @public
*/
/**
* Returns the value of the property for the given <code>sPath</code> and <code>oContext</code>.
*
* @abstract
*
* @name sap.ui.model.Model.prototype.getProperty
* @function
* @param {string} sPath The path to the attribute value you want to read
* @param {sap.ui.model.Context} [oContext] The context that resolves the path
*
* @returns {any} The value of the addressed property
* @public
*/
/**
* Returns the value of the property for the given <code>sPath</code> and <code>oContext</code>.
*
* @abstract
*
* @param {string} sPath The path to the object you want to read
* @param {sap.ui.model.Context} [oContext] The context that resolves the path
* @param {object} [mParameters] Additional model-specific parameters
*
* @returns {any|undefined}
* The the value of the property for the given path/context or <code>undefined</code> if data could not be found
* @public
*/
Model.prototype.getObject = function(sPath, oContext, mParameters) {
return this.getProperty(sPath, oContext, mParameters);
};
/**
* Create ContextBinding.
* @abstract
*
* @name sap.ui.model.Model.prototype.bindContext
* @function
* @param {string} sPath The path pointing to the property that should be bound
* @param {sap.ui.model.Context} [oContext] The context object for this databinding
* @param {object} [mParameters] Additional model-specific parameters
* @param {Object<string, function>} [oEvents] <b>Deprecated as of version 1.144.0</b> - unused
*
* @return {sap.ui.model.ContextBinding} The newly created binding
* @public
*/
/**
* Gets a binding context. If the context already exists, return it from the map, otherwise create
* one using the context constructor.
*
* @param {string} sPath The path
*
* @returns {sap.ui.model.Context} The context for the path
*/
Model.prototype.getContext = function(sPath) {
if (!sPath.startsWith("/")) {
throw new Error("Path " + sPath + " must start with a / ");
}
var oContext = this.mContexts[sPath];
if (!oContext) {
oContext = new Context(this, sPath);
this.mContexts[sPath] = oContext;
}
return oContext;
};
/**
* Resolve the path relative to the given context.
*
* If a relative path is given (not starting with a '/') but no context, then the path can't be
* resolved and undefined is returned.
*
* If a context is given but no path, the resolved path is the context's path, see
* {@link sap.ui.model.Context#getPath}.
*
* For backward compatibility, the behavior of this method can be changed by setting the
* 'legacySyntax' property. Then an unresolvable, relative path is automatically converted into
* an absolute path.
*
* @param {string} [sPath] Path to resolve
* @param {sap.ui.model.Context} [oContext] Context to resolve a relative path against
*
* @return {string} Resolved path or undefined
*/
Model.prototype.resolve = function(sPath, oContext) {
var bIsRelative = typeof sPath == "string" && !sPath.startsWith("/"),
sResolvedPath = sPath,
sContextPath;
if (bIsRelative) {
if (oContext) {
sContextPath = oContext.getPath();
sResolvedPath = sContextPath + (sContextPath.endsWith("/") ? "" : "/") + sPath;
} else {
sResolvedPath = undefined;
/** @deprecated As of version 1.88.0 */
if (this.isLegacySyntax()) {
sResolvedPath = "/" + sPath;
}
}
}
if (!sPath && oContext) {
sResolvedPath = oContext.getPath();
}
// invariant: path never ends with a slash ... if root is requested we return /
if (sResolvedPath && sResolvedPath !== "/" && sResolvedPath.endsWith("/")) {
sResolvedPath = sResolvedPath.substr(0, sResolvedPath.length - 1);
}
return sResolvedPath;
};
/**
* Cleanup bindings.
*/
Model.prototype._cleanUpBindings = function() {
var oBindingsToRemove = this.oBindingsToRemove;
if (oBindingsToRemove.size > 0) {
this.aBindings = this.aBindings.filter(function(oBinding) {
return !oBindingsToRemove.has(oBinding);
});
oBindingsToRemove.clear();
}
};
/**
* Add a binding to this model.
*
* @param {sap.ui.model.Binding} oBinding The binding to be added
*/
Model.prototype.addBinding = function(oBinding) {
this._cleanUpBindings();
this.aBindings.push(oBinding);
};
/**
* Returns a copy of all active bindings of the model.
*
* @return {sap.ui.model.Binding[]} The active bindings of the model
* @private
*/
Model.prototype.getBindings = function() {
this._cleanUpBindings();
return this.aBindings.slice();
};
/**
* Remove a binding from the model.
*
* @param {sap.ui.model.Binding} oBinding The binding to be removed
*/
Model.prototype.removeBinding = function(oBinding) {
this.oBindingsToRemove.add(oBinding);
if (!this.sRemoveTimer ) {
this.sRemoveTimer = setTimeout(function() {
this.sRemoveTimer = null;
this._cleanUpBindings();
}.bind(this), 0);
}
};
/**
* Get the default binding mode for the model.
*
* @returns {sap.ui.model.BindingMode} Default binding mode of the model
*
* @public
*/
Model.prototype.getDefaultBindingMode = function() {
return this.sDefaultBindingMode;
};
/**
* Set the default binding mode for the model.
*
* If the default binding mode should be changed, this method should be called directly after
* model instance creation and before any binding creation. Otherwise it is not guaranteed that
* the existing bindings will be updated with the new binding mode.
*
* @param {sap.ui.model.BindingMode} sMode The default binding mode to set for the model
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @public
*/
Model.prototype.setDefaultBindingMode = function(sMode) {
if (this.isBindingModeSupported(sMode)) {
this.sDefaultBindingMode = sMode;
return this;
}
throw new Error("Binding mode " + sMode + " is not supported by this model.", this);
};
/**
* Check if the specified binding mode is supported by the model.
*
* @param {sap.ui.model.BindingMode} sMode The binding mode to check
* @returns {boolean} Whether the given binding mode is supported
*
* @public
*/
Model.prototype.isBindingModeSupported = function(sMode) {
return (sMode in this.mSupportedBindingModes);
};
/**
* Enables legacy path syntax handling.
*
* This defines, whether relative bindings, which do not have a defined
* binding context, should be compatible to earlier releases which means
* they are resolved relative to the root element or handled strict and
* stay unresolved until a binding context is set.
*
* @param {boolean} bLegacySyntax The path syntax to use
*
* @deprecated since 1.88.0, legacy path syntax is not supported by most model implementations.
* @public
*/
Model.prototype.setLegacySyntax = function(bLegacySyntax) {
this.bLegacySyntax = bLegacySyntax;
};
/**
* Returns whether legacy path syntax is used.
*
* @returns {boolean} Whether legacy path syntax is used
*
* @deprecated since 1.88.0, legacy path syntax is not supported by most model implementations.
* @public
*/
Model.prototype.isLegacySyntax = function() {
return this.bLegacySyntax;
};
/**
* Set the maximum number of entries which are used for list bindings.
*
* The default size limit for models is 100.
*
* @param {int} iSizeLimit Collection size limit
* @public
*/
Model.prototype.setSizeLimit = function(iSizeLimit) {
this.iSizeLimit = iSizeLimit;
};
/**
* Override getInterface method to avoid creating an Interface object for models.
* @returns {sap.ui.model.Model} Returns a reference to this model
*/
Model.prototype.getInterface = function() {
return this;
};
/**
* Refresh the model.
*
* This will check all bindings for updated data and update the controls if data has been
* changed.
*
* @param {boolean} [bForceUpdate=false] Update controls even if data has not been changed
* @public
*/
Model.prototype.refresh = function(bForceUpdate) {
this.checkUpdate(bForceUpdate);
if (bForceUpdate) {
var aMessages = [];
for (var sKey in this.mMessages) {
aMessages = aMessages.concat(this.mMessages[sKey]);
}
var Messaging = sap.ui.require("sap/ui/core/Messaging");
if (Messaging) {
Messaging.updateMessages(aMessages, []);
}
}
};
/**
* Calls {@link sap.ui.model.Binding#checkUpdate} on all active bindings of this model. With
* <code>bAsync</code> set to <code>true</code> this method is called in a new task via
* <code>setTimeout</code>. Multiple asynchronous calls lead to a single synchronous call where
* <code>bForceUpdate</code> is <code>true</code> if at least one of the asynchronous calls was
* with <code>bForceUpdate=true</code>.
*
* @param {boolean} [bForceUpdate=false]
* The parameter <code>bForceUpdate</code> for the <code>checkUpdate</code> call on the
* bindings
* @param {boolean} [bAsync=false]
* Whether this function is called in a new task via <code>setTimeout</code>
* @returns {number|undefined}
* The number of bindings which were checked synchronously for updates; 0 if <code>bAsync</code> is set.
* Subclasses overwriting this method may also return <code>undefined</code>.
*
* @private
*/
Model.prototype.checkUpdate = function(bForceUpdate, bAsync) {
if (bAsync) {
this.bForceUpdate = this.bForceUpdate || bForceUpdate;
if (!this.sUpdateTimer) {
this.sUpdateTimer = setTimeout(function() {
this.checkUpdate(this.bForceUpdate);
}.bind(this), 0);
}
return 0;
}
bForceUpdate = this.bForceUpdate || bForceUpdate;
if (this.sUpdateTimer) {
clearTimeout(this.sUpdateTimer);
this.sUpdateTimer = null;
this.bForceUpdate = undefined;
}
var aBindings = this.getBindings();
each(aBindings, function(iIndex, oBinding) {
oBinding.checkUpdate(bForceUpdate);
});
return aBindings.length;
};
/**
* Checks if the sum of bindings updated by consecutive synchronous calls exceeds the threshold of 100,000 and log
* a warning in this case if there is more than one synchronous call. Does nothing if called with bAsyncUpdate=true.
*
* @param {number} iUpdatedBindings The number of synchronously updated bindings
* @param {boolean} [bAsyncUpdate] Whether the bindings are updated asynchronously
*
* @private
*/
Model.prototype.checkPerformanceOfUpdate = function (iUpdatedBindings, bAsyncUpdate) {
if (bAsyncUpdate) {
return;
}
this.iSyncSetPropertyCalls += 1;
this.iUpdatedBindings += iUpdatedBindings;
if (this.iTimeoutId) {
return;
}
const fnCheck = () => {
if (this.iSyncSetPropertyCalls > 1 && this.iUpdatedBindings > 100000) {
const sModelName = this.getMetadata().getName();
Log.warning(`Performance issue: ${this.iUpdatedBindings} (more than 100000) bindings are affected by `
+ `${this.iSyncSetPropertyCalls} consecutive synchronous calls to `
+ `${sModelName.slice(sModelName.lastIndexOf(".") + 1)}#setProperty; `
+ `see API documentation for details`,
undefined, sModelName);
}
this.iTimeoutId = undefined;
this.iSyncSetPropertyCalls = 0;
this.iUpdatedBindings = 0;
};
this.iTimeoutId = setTimeout(fnCheck, 0);
};
/**
* Sets the messages for this model and notifies the bindings if the new messages differ from
* the current model messages.
*
* @param {Object<string,sap.ui.core.message.Message[]>} [mMessages={}]
* The new messages for the model, mapping a binding path to an array of
* {@link sap.ui.core.message.Message} objects
*
* @public
*/
Model.prototype.setMessages = function (mMessages) {
mMessages = mMessages || {};
if (!deepEqual(this.mMessages, mMessages)) {
this.mMessages = mMessages;
this.checkMessages();
}
};
/**
* Returns model messages for which the target matches the given resolved binding path.
*
* @param {string} sPath
* The resolved binding path
* @param {boolean} [bPrefixMatch=false]
* Whether also messages with a target starting with the given path are returned, not just the
* messages with a target identical to the given path
* @returns {sap.ui.core.message.Message[]}
* An array of messages matching the given path; may be empty but not <code>null</code> or
* <code>undefined</code>
* @protected
*/
Model.prototype.getMessagesByPath = function (sPath, bPrefixMatch) {
var oMessageSet = new Set(),
that = this;
if (!bPrefixMatch) {
return this.mMessages[sPath] || [];
}
Object.keys(this.mMessages).forEach(function (sMessagePath) {
that.filterMatchingMessages(sMessagePath, sPath).forEach(function (oMessage) {
oMessageSet.add(oMessage);
});
});
return Array.from(oMessageSet);
};
/**
* Returns an array of messages for the given message target matching the given resolved binding
* path prefix.
*
* @param {string} sMessageTarget
* The messages target used as key in <code>this.mMessages</code>
* @param {string} sPathPrefix
* The resolved binding path prefix
* @returns {sap.ui.core.message.Message[]}
* The matching message objects, or an empty array, if no messages match.
*
* @private
*/
Model.prototype.filterMatchingMessages = function (sMessageTarget, sPathPrefix) {
if (sMessageTarget === sPathPrefix
|| sMessageTarget.startsWith(sPathPrefix === "/" ? sPathPrefix : sPathPrefix + "/")
) {
return this.mMessages[sMessageTarget];
}
return [];
};
/**
* Private method iterating the registered bindings of this model instance and initiating their
* check for messages.
* @private
*/
Model.prototype.checkMessages = function() {
each(this.getBindings(), function(iIndex, oBinding) {
if (oBinding.checkDataState) {
oBinding.checkDataState();
}
});
};
/**
* Destroys the model and clears the model data.
*
* A model implementation may override this function and perform model-specific cleanup tasks
* e.g. abort requests, prevent new requests, etc.
*
* @see sap.ui.base.Object.prototype.destroy
* @public
*/
Model.prototype.destroy = function() {
MessageProcessor.prototype.destroy.apply(this, arguments);
this.oData = {};
this.aBindings = [];
this.mContexts = {};
if (this.sRemoveTimer) {
clearTimeout(this.sRemoveTimer);
this.sRemoveTimer = null;
this.oBindingsToRemove.clear();
}
if (this.sUpdateTimer) {
clearTimeout(this.sUpdateTimer);
this.sUpdateTimer = null;
}
this.bDestroyed = true;
};
/**
* Returns the meta model associated with this model if it is available for the concrete
* model type.
* @abstract
* @public
* @returns {sap.ui.model.MetaModel|undefined}
* The meta model or <code>undefined</code> if no meta model exists.
*/
Model.prototype.getMetaModel = function() {
return undefined;
};
/**
* Returns the original value for the property with the given path and context.
*
* The original value is the value that was last responded by a server if using a server model
* implementation.
*
* @abstract
*
* @param {string} sPath The path/name of the property
* @param {sap.ui.model.Context} [oContext] Context for accessing the property value
* @returns {any} The value of the property
* @public
*/
Model.prototype.getOriginalProperty = function(sPath, oContext) {
return this.getProperty(sPath, oContext);
};
/**
* Returns whether a given path relative to the given contexts is in laundering state.
*
* If data is sent to the server, the data state becomes laundering until the
* data was accepted or rejected
*
* @param {string} sPath Path to resolve
* @param {sap.ui.model.Context} [oContext] Context to resolve a relative path against
* @returns {boolean} true if the data in this path is laundering
*/
Model.prototype.isLaundering = function(sPath, oContext) {
return false;
};
/**
* Checks whether the given filters contain an unsupported operator.
*
* OData v1, v2 and Client Bindings cannot be filtered with
* <code>sap.ui.model.FilterOperator</code>s, <code>"Any"</code> and <code>"All"</code>. The
* model property <code>mUnsupportedFilterOperators</code> can be configured in each model
* subclass to describe the unsupported operators.
*
* If any of the given filters contains nested filters, those are checked recursively.
*
* @param {sap.ui.model.Filter|sap.ui.model.Filter[]} vFilters
* Single filter or an array of filter instances
* @throws {Error}
* If at least one filter uses an <code>sap.ui.model.FilterOperator</code> that is not
* supported by the related model instance or if the {@link sap.ui.model.Filter.NONE} filter instance is contained
* in <code>vFilters</code> together with other filters
* @private
* @ui5-restricted sap.ui.model
*/
Model.prototype.checkFilter = function(vFilters) {
Filter.checkFilterNone(vFilters);
Model._traverseFilter(vFilters, function (oFilter) {
if (this.mUnsupportedFilterOperators[oFilter.sOperator]) {
throw new Error("Filter instances contain an unsupported FilterOperator: " + oFilter.sOperator);
}
}.bind(this));
};
/**
* Returns messages of this model associated with the given context, that is messages belonging
* to the object referred to by this context or a child object of that object. The messages are
* sorted by their {@link sap.ui.core.message.Message#getType type} according to the type's
* severity in a way that messages with highest severity come first.
*
* @param {sap.ui.model.Context} oContext The context to retrieve messages for
* @returns {sap.ui.core.message.Message[]}
* The messages associated with this context sorted by severity; empty array in case no
* messages exist
*
* @abstract
* @function
* @name sap.ui.model.Model.prototype.getMessages
* @public
* @since 1.76.0
*/
/**
* Traverses the given filter tree.
*
* @param {sap.ui.model.Filter[]|sap.ui.model.Filter} vFilters
* Array of filters or a single filter instance, which will be checked for unsupported filter
* operators
* @param {function} fnCheck
* Check function which is called for each filter instance in the tree
* @private
*/
Model._traverseFilter = function(vFilters, fnCheck) {
vFilters = vFilters || [];
if (vFilters instanceof Filter) {
vFilters = [vFilters];
}
// filter has more sub-filter instances (we ignore the subfilters below the any/all operators)
for (var i = 0; i < vFilters.length; i++) {
// check single Filter
var oFilter = vFilters[i];
fnCheck(oFilter);
// check subfilter for lambda expressions (e.g. Any, All, ...)
Model._traverseFilter(oFilter.oCondition, fnCheck);
// check multi filter if necessary
Model._traverseFilter(oFilter.aFilters, fnCheck);
}
};
/**
* Introduces data binding support on the ManagedObject prototype via mixin.
* Called by the ManagedObject during property propagation.
* @param {sap.ui.base.ManagedObject.prototype} ManagedObject
* the sap.ui.base.ManagedObject.prototype
*/
Model.prototype.mixinBindingSupport = function(ManagedObject) {
Object.assign(ManagedObject, ManagedObjectBindingSupport);
};
return Model;
});