UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

1,303 lines (1,219 loc) 46.2 kB
/*! * 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; });