@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
1,183 lines (1,096 loc) • 43.5 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2009-2021 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
// Provides the base implementation for all model implementations
sap.ui.define([
'sap/ui/core/message/MessageProcessor',
'./BindingMode',
'./Context',
'./Filter',
"sap/base/util/deepEqual",
"sap/base/util/each"
],
function(MessageProcessor, BindingMode, Context, Filter, deepEqual, each) {
"use strict";
/*global Set */
/**
* 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.87.1
*
* @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;
},
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"
]
/* the following would save code, but requires the new ManagedObject (1.9.1)
, events : {
"parseError" : {},
"requestFailed" : {},
"requestSent" : {},
"requestCompleted" ; {}
}
*/
}
});
/**
* 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 (<strong>deprecated</strong>), 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 (<strong>deprecated</strong>), 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} 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] 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] 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
* @param {int} [oParameters.errorCode]
* @param {string} [oParameters.url]
* @param {string} [oParameters.reason]
* @param {string} [oParameters.srcText]
* @param {int} [oParameters.line]
* @param {int} [oParameters.linepos]
* @param {int} [oParameters.filepos]
*
* @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 backend
* @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] Additional information for the request (if available) <strong>deprecated</strong>
* @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] 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 backend.
* @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] additional information for the request (if available) <strong>deprecated</strong>
* @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
* @param {sap.ui.base.EventProvider} oEvent.getSource
* @param {object} oEvent.getParameters
* @param {string} oEvent.getParameters.url URL which was sent to the backend
* @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]
* Additional information for the request (if available) <strong>deprecated</strong>
* @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] 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 backend.
* @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] additional information for the request (if available) <strong>deprecated</strong>
* @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;
};
Model.prototype.attachMessageChange = function(oData, fnFunction, oListener) {
this.attachEvent("messageChange", oData, fnFunction, oListener);
return this;
};
Model.prototype.detachMessageChange = function(fnFunction, oListener) {
this.detachEvent("messageChange", fnFunction, oListener);
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 {object} 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] 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;
};
// the 'abstract methods' to be implemented by child classes
/**
* Implement in inheriting classes.
* @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=null] the context object for this databinding (optional)
* @param {object}
* [mParameters=null] additional model specific parameters (optional)
* @return {sap.ui.model.PropertyBinding}
*
* @public
*/
/**
* Implement in inheriting classes.
* @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=null] the context object for this databinding (optional)
* @param {sap.ui.model.Sorter|sap.ui.model.Sorter[]}
* [aSorters=null] initial sort order (can be either a sorter or an array of sorters) (optional)
* @param {sap.ui.model.Filter|sap.ui.model.Filter[]}
* [aFilters=null] predefined filter/s (can be either a filter or an array of filters) (optional)
* @param {object}
* [mParameters=null] additional model specific parameters (optional)
* @return {sap.ui.model.ListBinding}
* @public
*/
/**
* Implement in inheriting classes.
* @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=null] the context object for this databinding (optional)
* @param {sap.ui.model.Filter[]}
* [aFilters=null] predefined filter/s contained in an array (optional)
* @param {object}
* [mParameters=null] additional model specific parameters (optional)
* @param {sap.ui.model.Sorter[]}
* [aSorters=null] predefined sap.ui.model.sorter/s contained in an array (optional)
* @return {sap.ui.model.TreeBinding}
* @public
*/
/**
* Implement in inheriting classes.
* @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=null] the context which should be used to create the new binding context
* @param {object}
* [mParameters=null] 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} the binding context, if it could be created synchronously
*
* @public
*/
/**
* Implement in inheriting classes.
* @abstract
*
* @name sap.ui.model.Model.prototype.destroyBindingContext
* @function
* @param {sap.ui.model.Context}
* oContext to destroy
* @public
*/
/**
* Implement in inheriting classes.
* @abstract
*
* @name sap.ui.model.Model.prototype.getProperty
* @function
* @param {string}
* sPath the path to where to read the attribute value
* @param {sap.ui.model.Context}
* [oContext=null] the context with which the path should be resolved
* @returns {any} Value of the addressed property
* @public
*/
/**
* Implement in inheriting classes.
* @abstract
*
* @param {string}
* sPath Path to where to read the object
* @param {sap.ui.model.Context}
* [oContext=null] Context with which the path should be resolved
* @param {object}
* [mParameters] Additional model specific parameters
* @returns {any} The value 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 | object}
* sPath the path pointing to the property that should be bound or an object
* which contains the following parameter properties: path, context, parameters
* @param {sap.ui.model.Context}
* [oContext=null] the context object for this databinding (optional)
* @param {object}
* [mParameters=null] additional model specific parameters (optional)
* @param {object}
* [oEvents=null] event handlers can be passed to the binding ({change:myHandler})
* @return {sap.ui.model.ContextBinding}
*
* @public
*/
/**
* Gets a binding context. If context already exists, return it from the map,
* otherwise create one using the context constructor.
*
* @param {string} sPath 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.
*
* 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.core.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 = this.isLegacySyntax() ? "/" + sPath : undefined;
}
}
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[]} aBindings 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
*
* @public
*/
Model.prototype.setLegacySyntax = function(bLegacySyntax) {
this.bLegacySyntax = bLegacySyntax;
};
/**
* Returns whether legacy path syntax is used.
*
* @returns {boolean} Whether legacy path syntax is used
*
* @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]);
}
this.fireMessageChange({
oldMessages: 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>
*
* @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;
}
if (this.sUpdateTimer) {
clearTimeout(this.sUpdateTimer);
this.sUpdateTimer = null;
this.bForceUpdate = undefined;
}
var aBindings = this.getBindings();
each(aBindings, function(iIndex, oBinding) {
oBinding.checkUpdate(bForceUpdate);
});
};
/**
* 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} 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.
*
* @param {string} sPath Path/name of the property
* @param {sap.ui.model.Context} [oContext] Context if available to access the property value
* @returns {any} vValue 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.core.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
* @private
* @ui5-restricted sap.ui.model
*/
Model.prototype.checkFilterOperation = function(vFilters) {
_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
*/
function _traverseFilter (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, ...)
_traverseFilter(oFilter.oCondition, fnCheck);
// check multi filter if necessary
_traverseFilter(oFilter.aFilters, fnCheck);
}
}
return Model;
});