@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
619 lines (589 loc) • 27.9 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2009-2023 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
/*eslint-disable max-len */
// Provides object sap.ui.model.odata.AnnotationHelper
sap.ui.define([
"./_AnnotationHelperBasics",
"./_AnnotationHelperExpression",
"sap/base/Log",
"sap/ui/base/BindingParser"
], function (Basics, Expression, Log, BindingParser) {
'use strict';
/**
* Returns a function representing the composition <code>fnAfter</code> after
* <code>fnBefore</code>.
*
* @param {function} fnAfter
* the second function, taking a single argument
* @param {function} fnBefore
* the first function, taking multiple arguments
* @returns {function}
* the composition <code>fnAfter</code> after <code>fnBefore</code>
*/
function chain(fnAfter, fnBefore) {
return function () {
return fnAfter.call(this, fnBefore.apply(this, arguments));
};
}
/**
* @classdesc
* A collection of methods which help to consume
* <a href="http://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part3-csdl.html">
* OData V4 annotations</a> in XML template views. Every context argument must belong to a
* <code>sap.ui.model.odata.ODataMetaModel</code> instance.
*
* Formatter functions like {@link #.format format} and {@link #.simplePath simplePath} can
* be used in complex bindings to turn OData V4 annotations into texts or data bindings,
* e.g. <code><sfi:SmartField value="{path : 'meta>Value', formatter :
* 'sap.ui.model.odata.AnnotationHelper.simplePath'}"/></code>.
*
* Helper functions like {@link #.resolvePath resolvePath} can be used by template
* instructions in XML template views, e.g. <code><template:with path="meta>Value"
* helper="sap.ui.model.odata.AnnotationHelper.resolvePath" var="target"></code>.
*
* Since 1.31.0, you DO NOT need to {@link sap.ui.require} this module before use.
*
* @public
* @since 1.27.0
* @namespace
* @alias sap.ui.model.odata.AnnotationHelper
*/
var AnnotationHelper = {
/**
* Creates a property setting (which is either a constant value or a binding info
* object) from the given parts and from the optional root formatter function.
* Each part can have one of the following types:
* <ul>
* <li><code>boolean</code>, <code>number</code>, <code>undefined</code>: The part is
* a constant value.
*
* <li><code>string</code>: The part is a data binding expression with complex
* binding syntax (for example, as created by {@link #.format format}) and is parsed
* accordingly to create either a constant value or a binding info object. Proper
* backslash escaping must be used for constant values with curly braces.
*
* <li><code>object</code>: The part is a binding info object if it has a "path" or
* "parts" property, otherwise it is a constant value.
* </ul>
* If a binding info object is not the only part and has a "parts" property itself,
* then it must have no other properties except "formatter"; this is the case for
* expression bindings and data binding expressions created by {@link #.format format}.
*
* If all parts are constant values, the resulting property setting is also a constant
* value computed by applying the root formatter function to the constant parts once.
* If at least one part is a binding info object, the resulting property setting is
* also a binding info object and the root formatter function will be applied again and
* again to the current values of all parts, no matter whether constant or variable.
*
* Note: The root formatter function should not rely on its <code>this</code> value
* because it depends on how the function is called.
*
* Note: A single data binding expression can be given directly to
* {@link sap.ui.base.ManagedObject#applySettings applySettings}, no need to call this
* function first.
*
* Example:
* <pre>
* function myRootFormatter(oValue1, oValue2, sFullName, sGreeting, iAnswer) {
* return ...;
* }
*
* oSupplierContext = oMetaModel.getMetaContext("/ProductSet('HT-1021')/ToSupplier");
* oValueContext = oMetaModel.createBindingContext("com.sap.vocabularies.UI.v1.DataPoint/Value", oSupplierContext);
*
* vPropertySetting = sap.ui.model.odata.AnnotationHelper.createPropertySetting([
* sap.ui.model.odata.AnnotationHelper.format(oValueContext),
* "{path : 'meta>Value', formatter : 'sap.ui.model.odata.AnnotationHelper.simplePath'}",
* "{:= 'Mr. ' + ${/FirstName} + ' ' + ${/LastName}}",
* "hello, world!",
* 42
* ], myRootFormatter);
*
* oControl.applySettings({"someProperty" : vPropertySetting});
* </pre>
*
* @param {any[]} aParts
* array of parts
* @param {function} [fnRootFormatter]
* root formatter function; default: <code>Array.prototype.join(., " ")</code>
* in case of multiple parts, just like
* {@link sap.ui.model.CompositeBinding#getExternalValue getExternalValue}
* @returns {any|object}
* constant value or binding info object for a property as expected by
* {@link sap.ui.base.ManagedObject#applySettings applySettings}
* @throws {Error}
* if some part has an unsupported type or refers to a function name which is not
* found
* @public
* @since 1.31.0
*/
createPropertySetting : function (aParts, fnRootFormatter) {
var bMergeNeeded = false,
vPropertySetting;
aParts = aParts.slice(); // shallow copy to avoid changes visible to caller
aParts.forEach(function (vPart, i) {
switch (typeof vPart) {
case "boolean":
case "number":
case "undefined":
bMergeNeeded = true;
break;
case "string":
vPropertySetting = BindingParser.complexParser(vPart, null, true, true);
if (vPropertySetting !== undefined) {
if (vPropertySetting.functionsNotFound) {
throw new Error("Function name(s) "
+ vPropertySetting.functionsNotFound.join(", ")
+ " not found");
}
aParts[i] = vPart = vPropertySetting;
}
// falls through
case "object":
// merge is needed if some parts are constants or again have parts
// Note: a binding info object has either "path" or "parts"
if (!vPart || typeof vPart !== "object" || !("path" in vPart)) {
bMergeNeeded = true;
}
break;
default:
throw new Error("Unsupported part: " + vPart);
}
});
vPropertySetting = {
formatter : fnRootFormatter,
parts : aParts
};
if (bMergeNeeded) {
BindingParser.mergeParts(vPropertySetting);
}
fnRootFormatter = vPropertySetting.formatter; // may have changed due to merge
if (vPropertySetting.parts.length === 0) {
// special case: all parts are constant values, call formatter once
vPropertySetting = fnRootFormatter && fnRootFormatter();
if (typeof vPropertySetting === "string") {
vPropertySetting = BindingParser.complexParser.escape(vPropertySetting);
}
} else if (vPropertySetting.parts.length === 1) {
// special case: a single property setting only
// Note: sap.ui.base.ManagedObject#_bindProperty cannot handle the single-part
// case with two formatters, unless the root formatter is marked with
// "textFragments". Unpacking the single part does not work in case it has
// a type! We do not unpack here, but chain the formatters ourselves.
// Note: we prefer a root formatter because it has access to "this" and has an
// influence on the binding mode (OneTime/OneWay)
if (fnRootFormatter && !fnRootFormatter.textFragments
&& vPropertySetting.parts[0].formatter) {
vPropertySetting.formatter
= chain(fnRootFormatter, vPropertySetting.parts[0].formatter);
// avoid changes visible to caller
vPropertySetting.parts[0] = Object.assign({}, vPropertySetting.parts[0]);
delete vPropertySetting.parts[0].formatter;
}
}
return vPropertySetting;
},
/**
* A formatter function to be used in a complex binding inside an XML template view
* in order to interpret OData V4 annotations. It knows about
* <ul>
* <li> the "14.4 Constant Expressions" for "edm:Bool", "edm:Date",
* "edm:DateTimeOffset", "edm:Decimal", "edm:Float", "edm:Guid", "edm:Int",
* "edm:TimeOfDay".
* <li> the constant "14.4.11 Expression edm:String": This is turned into a fixed
* text (e.g. <code>"Width"</code>) or into a data binding expression (e.g. <code>
* "{/##/dataServices/schema/0/entityType/1/com.sap.vocabularies.UI.v1.FieldGroup#Dimensions/Data/0/Label/String}"
* </code>). Data binding expressions are used in case XML template processing has
* been started with the setting <code>bindTexts : true</code>. The purpose is to
* reference translatable texts from OData V4 annotations, especially for XML
* template processing at design time. Since 1.31.0, string constants that contain a
* simple binding <code>"{@i18n>...}"</code> to the hard-coded model name "@i18n"
* with arbitrary path are not turned into a fixed text, but kept as a data binding
* expression; this allows local annotation files to refer to a resource bundle for
* internationalization.
* <li> the dynamic "14.5.1 Comparison and Logical Operators": These are turned into
* expression bindings to perform the operations at run-time.
* <li> the dynamic "14.5.3 Expression edm:Apply":
* <ul>
* <li> "14.5.3.1.1 Function odata.concat": This is turned into a data binding
* expression relative to an entity.
* <li> "14.5.3.1.2 Function odata.fillUriTemplate": This is turned into an
* expression binding to fill the template at run-time.
* <li> "14.5.3.1.3 Function odata.uriEncode": This is turned into an expression
* binding to encode the parameter at run-time.
* <li> Apply functions may be nested arbitrarily.
* </ul>
* <li> the dynamic "14.5.6 Expression edm:If": This is turned into an expression
* binding to be evaluated at run-time. The expression is a conditional expression
* like <code>"{=condition ? expression1 : expression2}"</code>.
* <li> the dynamic "14.5.10 Expression edm:Null": This is turned into a
* <code>null</code> value. In <code>odata.concat</code> it is ignored.
* <li> the dynamic "14.5.12 Expression edm:Path" and "14.5.13 Expression
* edm:PropertyPath": This is turned into a data binding relative to an entity,
* including type information and constraints as available from metadata,
* e.g. <code>"{path : 'Name', type : 'sap.ui.model.odata.type.String',
* constraints : {'maxLength':'255'}}"</code>.
* Depending on the used type, some additional constraints of this type are set:
* <ul>
* <li>Edm.DateTime: The "displayFormat" constraint is set to the value of the
* "sap:display-format" annotation of the referenced property.
* <li>Edm.Decimal: The "precision" and "scale" constraints are set to the values
* of the corresponding attributes of the referenced property. The "minimum",
* "maximum", "minimumExclusive" and "maximumExlusive" constraints are set to the
* values of the corresponding "Org.OData.Validation.V1" annotation of the
* referenced property; note that in this case only constant expressions are
* supported to determine the annotation value.
* <li>Edm.String: The "maxLength" constraint is set to the value of the
* corresponding attribute of the referenced property and the "isDigitSequence"
* constraint is set to the value of the
* "com.sap.vocabularies.Common.v1.IsDigitSequence" annotation of the referenced
* property; note that in this case only constant expressions are supported to
* determine the annotation value.
* </ul>
* </ul>
* Unsupported or incorrect values are turned into a string nevertheless, but indicated
* as such. Proper escaping is used to make sure that data binding syntax is not
* corrupted. An error describing the problem is logged to the console in such a case.
*
* Example:
* <pre>
* <Text text="{path: 'meta>Value', formatter: 'sap.ui.model.odata.AnnotationHelper.format'}" />
* </pre>
*
* @param {sap.ui.core.util.XMLPreprocessor.IContext|sap.ui.model.Context} oInterface
* the callback interface related to the current formatter call
* @param {any} [vRawValue]
* the raw value from the meta model, which is embedded within an entity set or
* entity type:
* <ul>
* <li>if this function is used as formatter the value
* is provided by the framework</li>
* <li>if this function is called directly, provide the parameter only if it is
* already calculated</li>
* <li>if the parameter is omitted, it is calculated automatically through
* <code>oInterface.getObject("")</code></li>
* </ul>
* @returns {string}
* the resulting string value to write into the processed XML
* @public
*/
format : function (oInterface, vRawValue) {
if (arguments.length === 1) {
vRawValue = oInterface.getObject("");
}
return Expression.getExpression(oInterface, vRawValue, true);
},
/**
* A formatter function to be used in a complex binding inside an XML template view
* in order to interpret OData V4 annotations. It knows about the following dynamic
* expressions:
* <ul>
* <li>"14.5.2 Expression edm:AnnotationPath"</li>
* <li>"14.5.11 Expression edm:NavigationPropertyPath"</li>
* <li>"14.5.12 Expression edm:Path"</li>
* <li>"14.5.13 Expression edm:PropertyPath"</li>
* </ul>
* It returns a binding expression for a navigation path in an OData model, starting at
* an entity.
* Currently supports navigation properties. Term casts and annotations of
* navigation properties terminate the navigation path.
*
* Examples:
* <pre>
* <template:if test="{path: 'facet>Target', formatter: 'sap.ui.model.odata.AnnotationHelper.getNavigationPath'}">
* <form:SimpleForm binding="{path: 'facet>Target', formatter: 'sap.ui.model.odata.AnnotationHelper.getNavigationPath'}" />
* </template:if>
* </pre>
*
* @param {sap.ui.core.util.XMLPreprocessor.IContext|sap.ui.model.Context} oInterface
* the callback interface related to the current formatter call
* @param {any} [vRawValue]
* the raw value from the meta model, e.g. <code>{AnnotationPath :
* "ToSupplier/@com.sap.vocabularies.Communication.v1.Address"}</code> or <code>
* {AnnotationPath : "@com.sap.vocabularies.UI.v1.FieldGroup#Dimensions"}</code>;
* embedded within an entity set or entity type;
* <ul>
* <li>if this function is used as formatter the value
* is provided by the framework</li>
* <li>if this function is called directly, provide the parameter only if it is
* already calculated</li>
* <li>if the parameter is omitted, it is calculated automatically through
* <code>oInterface.getObject("")</code></li>
* </ul>
* @returns {string}
* the resulting string value to write into the processed XML, e.g. "{ToSupplier}"
* or "{}" (in case no navigation is needed); returns "" in case the navigation path
* cannot be determined (this is treated as falsy in <code>template:if</code>
* statements!)
* @public
*/
getNavigationPath : function (oInterface, vRawValue) {
if (arguments.length === 1) {
vRawValue = oInterface.getObject("");
}
var oResult = Basics.followPath(oInterface, vRawValue);
return oResult
? "{" + oResult.navigationProperties.join("/") + "}"
: "";
},
/**
* Helper function for a <code>template:with</code> instruction that depending on how
* it is called goes to the entity set with the given name or to the one determined
* by the last navigation property. Supports the following dynamic expressions:
* <ul>
* <li>"14.5.2 Expression edm:AnnotationPath"</li>
* <li>"14.5.11 Expression edm:NavigationPropertyPath"</li>
* <li>"14.5.12 Expression edm:Path"</li>
* <li>"14.5.13 Expression edm:PropertyPath"</li>
* </ul>
*
* Example:
* <pre>
* <template:with path="facet>Target" helper="sap.ui.model.odata.AnnotationHelper.gotoEntitySet" var="entitySet"/>
* <template:with path="associationSetEnd>entitySet" helper="sap.ui.model.odata.AnnotationHelper.gotoEntitySet" var="entitySet"/>
* </pre>
*
* @param {sap.ui.model.Context} oContext
* a context which must point to a simple string or to an annotation (or annotation
* property) of type <code>Edm.AnnotationPath</code>,
* <code>Edm.NavigationPropertyPath</code>, <code>Edm.Path</code>, or
* <code>Edm.PropertyPath</code> embedded within an entity set or entity type;
* the context's model must be an {@link sap.ui.model.odata.ODataMetaModel}
* @returns {string|undefined}
* the path to the entity set, or <code>undefined</code> if no such set is found. In
* this case, a warning is logged to the console.
* @public
*/
gotoEntitySet : function (oContext) {
var sEntitySet,
sEntitySetPath,
vRawValue = oContext.getObject(),
oResult;
if (typeof vRawValue === "string") {
sEntitySet = vRawValue;
} else {
oResult = Basics.followPath(oContext, vRawValue);
sEntitySet = oResult
&& oResult.associationSetEnd
&& oResult.associationSetEnd.entitySet;
}
if (sEntitySet) {
sEntitySetPath = oContext.getModel().getODataEntitySet(sEntitySet, true);
}
if (!sEntitySetPath) {
Log.warning(oContext.getPath() + ": found '" + sEntitySet
+ "' which is not a name of an entity set", undefined,
"sap.ui.model.odata.AnnotationHelper");
}
return sEntitySetPath;
},
/**
* Helper function for a <code>template:with</code> instruction that goes to the
* entity type with the qualified name which <code>oContext</code> points at.
*
* Example: Assume that "entitySet" refers to an entity set within an OData meta model;
* the helper function is then called on the "entityType" property of that entity set
* (which holds the qualified name of the entity type) and in turn the path of that
* entity type is assigned to the variable "entityType".
* <pre>
* <template:with path="entitySet>entityType" helper="sap.ui.model.odata.AnnotationHelper.gotoEntityType" var="entityType">
* </pre>
*
* @param {sap.ui.model.Context} oContext
* a context which must point to the qualified name of an entity type;
* the context's model must be an {@link sap.ui.model.odata.ODataMetaModel}
* @returns {string|undefined}
* the path to the entity type with the given qualified name,
* or <code>undefined</code> if no such type is found. In this case, a warning is
* logged to the console.
* @public
*/
gotoEntityType : function (oContext) {
var sEntityType = oContext.getProperty(""),
oResult = oContext.getModel().getODataEntityType(sEntityType, true);
if (!oResult) {
Log.warning(oContext.getPath() + ": found '" + sEntityType
+ "' which is not a name of an entity type", undefined,
"sap.ui.model.odata.AnnotationHelper");
}
return oResult;
},
/**
* Helper function for a <code>template:with</code> instruction that goes to the
* function import with the name which <code>oContext</code> points at.
*
* Example: Assume that "dataField" refers to a DataFieldForAction within an
* OData meta model;
* the helper function is then called on the "Action" property of that data field
* (which holds an object with the qualified name of the function import in the
* <code>String</code> property) and in turn the path of that function import
* is assigned to the variable "function".
* <pre>
* <template:with path="dataField>Action"
* helper="sap.ui.model.odata.AnnotationHelper.gotoFunctionImport" var="function">
* </pre>
* @param {sap.ui.model.Context} oContext
* a context which must point to an object with a <code>String</code> property, which
* holds the qualified name of the function import;
* the context's model must be an {@link sap.ui.model.odata.ODataMetaModel}
* @returns {string|undefined}
* the path to the function import with the given qualified name,
* or <code>undefined</code> if no function import is found. In this case, a warning
* is logged to the console.
* @since 1.29.1
* @public
*/
gotoFunctionImport : function (oContext) {
var sFunctionImport = oContext.getProperty("String"),
oResult = oContext.getModel().getODataFunctionImport(sFunctionImport, true);
if (!oResult) {
Log.warning(oContext.getPath() + ": found '" + sFunctionImport
+ "' which is not a name of a function import", undefined,
"sap.ui.model.odata.AnnotationHelper");
}
return oResult;
},
/**
* A formatter function to be used in a complex binding inside an XML template view
* in order to interpret OData V4 annotations. It knows about the following dynamic
* expressions:
* <ul>
* <li>"14.5.2 Expression edm:AnnotationPath"</li>
* <li>"14.5.11 Expression edm:NavigationPropertyPath"</li>
* <li>"14.5.12 Expression edm:Path"</li>
* <li>"14.5.13 Expression edm:PropertyPath"</li>
* </ul>
* It returns the information whether the navigation path ends with an association end
* with multiplicity "*". It throws an error if the navigation path has an association
* end with multiplicity "*" which is not the last one.
* Currently supports navigation properties. Term casts and annotations of
* navigation properties terminate the navigation path.
*
* Examples:
* <pre>
* <template:if test="{path: 'facet>Target', formatter: 'sap.ui.model.odata.AnnotationHelper.isMultiple'}">
* </pre>
*
* @param {sap.ui.core.util.XMLPreprocessor.IContext|sap.ui.model.Context} oInterface
* the callback interface related to the current formatter call
* @param {any} [vRawValue]
* the raw value from the meta model, e.g. <code>{AnnotationPath :
* "ToSupplier/@com.sap.vocabularies.Communication.v1.Address"}</code> or <code>
* {AnnotationPath : "@com.sap.vocabularies.UI.v1.FieldGroup#Dimensions"}</code>;
* embedded within an entity set or entity type;
* <ul>
* <li>if this function is used as formatter the value
* is provided by the framework</li>
* <li>if this function is called directly, provide the parameter only if it is
* already calculated</li>
* <li>if the parameter is omitted, it is calculated automatically through
* <code>oInterface.getObject("")</code></li>
* </ul>
* @returns {string}
* <code>"true"</code> if the navigation path ends with an association end with
* multiplicity "*", <code>""</code> in case the navigation path cannot be
* determined, <code>"false"</code> otherwise (the latter are both treated as falsy
* in <code>template:if</code> statements!)
* @throws {Error}
* if the navigation path has an association end with multiplicity "*" which is not
* the last one
* @public
*/
isMultiple : function (oInterface, vRawValue) {
if (arguments.length === 1) {
vRawValue = oInterface.getObject("");
}
var oResult = Basics.followPath(oInterface, vRawValue);
if (oResult) {
if (oResult.navigationAfterMultiple) {
throw new Error(
'Association end with multiplicity "*" is not the last one: '
+ vRawValue.AnnotationPath);
}
return String(oResult.isMultiple);
}
return "";
},
/**
* Helper function for a <code>template:with</code> instruction that resolves a dynamic
* "14.5.2 Expression edm:AnnotationPath",
* "14.5.11 Expression edm:NavigationPropertyPath", "14.5.12 Expression edm:Path" or
* "14.5.13 Expression edm:PropertyPath".
* Currently supports navigation properties and term casts.
*
* Example:
* <pre>
* <template:with path="meta>Value" helper="sap.ui.model.odata.AnnotationHelper.resolvePath" var="target">
* </pre>
*
* @param {sap.ui.model.Context} oContext
* a context which must point to an annotation or annotation property of type
* <code>Edm.AnnotationPath</code>, <code>Edm.NavigationPropertyPath</code>,
* <code>Edm.Path</code> or <code>Edm.PropertyPath</code>, embedded within an entity
* set or entity type;
* the context's model must be an {@link sap.ui.model.odata.ODataMetaModel}
* @returns {string|undefined}
* the path to the target, or <code>undefined</code> in case the path cannot be
* resolved. In this case, a warning is logged to the console.
* @public
*/
resolvePath : function (oContext) {
var oResult = Basics.followPath(oContext, oContext.getObject());
if (!oResult) {
Log.warning(oContext.getPath() + ": Path could not be resolved ",
undefined, "sap.ui.model.odata.AnnotationHelper");
}
return oResult
? oResult.resolvedPath
: undefined;
},
/**
* Formatter function that is used in a complex binding inside an XML template view.
* The function is used to interpret OData V4 annotations, supporting the same
* annotations as {@link #.format format} but with a simplified output aimed at
* design-time templating with smart controls.
*
* In contrast to <code>format</code>, "14.5.12 Expression edm:Path" or
* "14.5.13 Expression edm:PropertyPath" is turned into a simple binding path without
* type or constraint information. In certain cases, a complex binding is required to
* allow for proper escaping of the path.
*
* Example:
* <pre>
* <sfi:SmartField value="{path: 'meta>Value', formatter: 'sap.ui.model.odata.AnnotationHelper.simplePath'}"/>
* </pre>
*
* @param {sap.ui.core.util.XMLPreprocessor.IContext|sap.ui.model.Context} oInterface
* the callback interface related to the current formatter call
* @param {any} [vRawValue]
* the raw value from the meta model, which is embedded within an entity set or
* entity type:
* <ul>
* <li>if this function is used as formatter the value
* is provided by the framework</li>
* <li>if this function is called directly, provide the parameter only if it is
* already calculated</li>
* <li>if the parameter is omitted, it is calculated automatically through
* <code>oInterface.getObject("")</code></li>
* </ul>
* @returns {string}
* the resulting string value to write into the processed XML
* @public
*/
simplePath : function (oInterface, vRawValue) {
if (arguments.length === 1) {
vRawValue = oInterface.getObject("");
}
return Expression.getExpression(oInterface, vRawValue, false);
}
};
// BEWARE: keep this in sync with sap/ui/core/library.js!
AnnotationHelper.format.requiresIContext = true;
AnnotationHelper.getNavigationPath.requiresIContext = true;
AnnotationHelper.isMultiple.requiresIContext = true;
AnnotationHelper.simplePath.requiresIContext = true;
return AnnotationHelper;
}, /* bExport= */ true);