@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
1,144 lines (1,026 loc) • 110 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2026 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
// Provides the render manager sap.ui.core.RenderManager
sap.ui.define([
"./ControlBehavior",
"./FocusHandler",
"./InvisibleRenderer",
'./LabelEnablement',
"./Patcher",
"sap/base/assert",
"sap/base/future",
"sap/base/Log",
"sap/base/security/encodeCSS",
"sap/base/security/encodeXML",
"sap/base/util/extend",
'sap/base/util/uid',
'sap/ui/base/Object',
"sap/ui/performance/Measurement",
'sap/ui/performance/trace/Interaction',
"sap/ui/thirdparty/jquery",
"sap/ui/util/ActivityDetection"
], function(
ControlBehavior,
FocusHandler,
InvisibleRenderer,
LabelEnablement,
Patcher,
assert,
future,
Log,
encodeCSS,
encodeXML,
extend,
uid,
BaseObject,
Measurement,
Interaction,
jQuery,
ActivityDetection
) {
"use strict";
/*global SVGElement*/
const mOwnerInfo = new Map();
var Element;
var aCommonMethods = ["renderControl", "cleanupControlWithoutRendering", "accessibilityState", "icon"];
/**
* @deprecated As of version 1.92, the string rendering methods are deprecated
*/
var aStrInterfaceMethods = ["write", "writeEscaped", "writeAcceleratorKey", "writeControlData", "writeElementData",
"writeAttribute", "writeAttributeEscaped", "addClass", "writeClasses", "addStyle", "writeStyles",
"writeAccessibilityState", "writeIcon", "translate", "getConfiguration", "getHTML"];
var aDomInterfaceMethods = ["openStart", "voidStart", "attr", "class", "style", "openEnd", "voidEnd", "text", "unsafeHtml", "close"];
var aNonRendererMethods = ["render", "flush", "destroy"];
var oTemplate = document.createElement("template");
var ATTR_STYLE_KEY_MARKER = "data-sap-ui-stylekey";
/**
* An attribute marker that is set on a DOM element of a control or element to indicate
* that the rendering cannot be skipped and always should be executed.
*
* The attribute is set for the root DOM element of a control when the control's renderer
* does not support apiVersion 4. For controls that support apiVersion 4, this attribute is
* also set when
* - the control has at least one delegate that implements an `onAfterRendering`
* and/or `onBeforeRendering` event handler without also setting the `canSkipRendering` flag.
* See {@link sap.ui.core.Element#addEventDelegate Element#addEventDelegate} for more information.
* - the parent of the control implements the `enhanceAccessibilityState` method
* and does not set the `canSkipRendering` property in the enhanced accessibility state.
* See {@link sap.ui.core.Element#enhanceAccessibilityState Element#enhanceAccessibilityState} for more information.
*
* Controls define the apiVersion 4 contract only for their own rendering, therefore
* apiVersion 4 optimization only works when all child controls support apiVersion 4.
* This makes this attribute important for RM to determine apiVersion 4 optimization.
* @constant
* @private
*/
var ATTR_DO_NOT_SKIP_RENDERING_MARKER = "data-sap-ui-render";
/**
* Creates an instance of the RenderManager.
*
* Applications or controls must not call the <code>RenderManager</code> constructor on their own
* but should rely on the re-rendering initiated by the framework lifecycle based on invalidation.
* See {@link module:sap/ui/core/Element#invalidate} and {@link module:sap/ui/core/Control#invalidate}.
*
* @class A class that handles the rendering of controls.
*
* For the default rendering task of UI5, a shared RenderManager is created and owned by
* the framework.
*
* Controls or other code that want to render controls outside the default rendering task
* can create a private instance of RenderManager by calling the
* {@link sap.ui.core.Core#createRenderManager sap.ui.getCore().createRenderManager()} method.
* When such a private instance is no longer needed, it should be {@link #destroy destroyed}.
*
* Control renderers only have access to a subset of the public and protected instance methods of
* this class. The instance methods {@link #flush}, {@link #render} and {@link #destroy} are not part
* of that subset and are reserved to the owner of the corresponding RenderManager instance.
* Renderers will use the provided methods to create their HTML output. The RenderManager will
* collect the HTML output and inject the final HTML DOM at the desired location.
*
*
* <h3>Renderers</h3>
* When the {@link #renderControl} method of the RenderManager is invoked, it will retrieve
* the default renderer for that control. By convention, the default renderer is implemented in its
* own namespace (static class) which matches the name of the control's class with the additional
* suffix 'Renderer'. So for a control <code>sap.m.Input</code> the default renderer will be searched
* for under the global name <code>sap.m.Input<i>Renderer</i></code>.
*
* <h3>Semantic Rendering</h3>
* As of 1.67, <code>RenderManager</code> provides a set of new APIs to describe the structure of the DOM that can be used by the control renderers.
*
* <pre>
*
* myButtonRenderer.render = function(rm, oButton) {
*
* rm.openStart("button", oButton);
* rm.attr("tabindex", 1);
* rm.class("myButton");
* rm.style("width", oButton.getWidth());
* rm.openEnd();
* rm.text(oButton.getText());
* rm.close("button");
*
* };
*
* </pre>
*
* By default, when the control is invalidated (e.g. a property is changed, an aggregation is removed, or an
* association is added), it will be registered for rerendering. During the (re)rendering, the <code>render</code>
* method of the control renderer is executed via a specified <code>RenderManager</code> interface and the control
* instance.
*
* Traditional string-based rendering creates a new HTML structure of the control in every rendering cycle and removes
* the existing control DOM structure from the DOM tree.
*
* The set of new semantic <code>RenderManager</code> APIs lets us understand the structure of the DOM, walk along the
* live DOM tree, and figure out changes as new APIs are called. If there is a change, then <code>RenderManager</code>
* patches only the required parts of the live DOM tree. This allows control developers to remove their DOM-related
* custom setters.
*
* <b>Note:</b> To enable the new in-place rendering technology, the <code>apiVersion</code> property of the control
* renderer must be set to <code>2</code>. This property is not inherited by subclass renderers. It has to be set
* anew by each subclass to assure that the extended contract between framework and renderer is fulfilled (see next
* paragraph).
*
* <pre>
*
* var myButtonRenderer = {
* apiVersion: 2 // enable semantic rendering
* };
*
* myButtonRenderer.render = function(rm, oButton) {
*
* rm.openStart("button", oButton);
* ...
* ...
* rm.close("button");
*
* };
*
* </pre>
*
* <h3>Contract for Renderer.apiVersion 2</h3>
* To allow a more efficient in-place DOM patching and to ensure the compatibility of the control, the following
* prerequisites must be fulfilled for the controls using the new rendering technology:
*
* <ul>
* <li>Legacy control renderers must be migrated to the new semantic renderer API:
* {@link sap.ui.core.RenderManager#openStart openStart},
* {@link sap.ui.core.RenderManager#voidStart voidStart},
* {@link sap.ui.core.RenderManager#style style},
* {@link sap.ui.core.RenderManager#class class},
* {@link sap.ui.core.RenderManager#attr attr},
* {@link sap.ui.core.RenderManager#openEnd openEnd},
* {@link sap.ui.core.RenderManager#voidEnd voidEnd},
* {@link sap.ui.core.RenderManager#text text},
* {@link sap.ui.core.RenderManager#unsafeHtml unsafeHtml},
* {@link sap.ui.core.RenderManager#icon icon},
* {@link sap.ui.core.RenderManager#accessibilityState accessibilityState},
* {@link sap.ui.core.RenderManager#renderControl renderControl},
* {@link sap.ui.core.RenderManager#cleanupControlWithoutRendering cleanupControlWithoutRendering}
* </li>
* <li>During the migration, restrictions that are defined in the API documentation of those methods must be taken
* into account, e.g. tag and attribute names must be set in their canonical form.</li>
* <li>Fault tolerance of HTML5 markup is not applicable for the new semantic rendering API, e.g. except void tags,
* all tags must be closed; duplicate attributes within one HTML element must not exist.</li>
* <li>Existing control DOM structure will not be removed from the DOM tree; therefore all custom events, including
* the ones that are registered with jQuery, must be de-registered correctly at the <code>onBeforeRendering</code>
* and <code>exit</code> hooks.</li>
* <li>Classes and attribute names must not be escaped.</li>
* <li>Styles should be validated via types (e.g. <code>sap.ui.core.CSSSize</code>). But this might not be sufficient
* in all cases, e.g. validated URL values can contain harmful content; in this case
* {@link module:sap/base/security/encodeCSS encodeCSS} can be used.</li>
* <li>To allow a more efficient DOM update, second parameter of the {@link sap.ui.core.RenderManager#openStart openStart}
* or {@link sap.ui.core.RenderManager#voidStart voidStart} methods must be used to identify elements, e.g. use
* <code>rm.openStart("div", oControl.getId() + "-suffix");</code> instead of
* <code>rm.openStart("div").attr("id", oControl.getId() + "-suffix");</code></li>
* <li>Controls that listen to the <code>focusin</code> event must double check their focus handling. Since DOM nodes
* are not removed and only reused, the <code>focusin</code> event might not be fired during rerendering.</li>
* </ul>
*
* <h3>Contract for Renderer.apiVersion 4</h3>
* The <code>apiVersion 4</code> marker of the control renderer lets the <code>RenderManager</code> know if a control's output is not affected by changes in the parent control.
* By default, if a property, an aggregation, or an association of a control is changed, then the control gets invalidated, and the rerendering process for that control and all of its
* children starts. That means child controls rerender together with their parent even though there is no DOM update necessary. If a control's output is only affected by its own
* properties, aggregations, or associations, then the <code>apiVersion 4</code> marker can help to reuse the control's DOM output and prevent child controls from rerendering unnecessarily
* while they are getting rendered by their parent. This can help to improve performance by reducing the number of re-renderings.<br>
* For example: A control called "ParentControl" has a child control called "ChildControl". ChildControl has its own properties, aggregations, and associations, and its output is only affected by them.
* The <code>apiVersion 4</code> marker is set in the renderer of ChildControl. Whenever a property of the ParentControl is changed during the re-rendering process, the <code>RenderManager</code>
* will check the <code>apiVersion</code> marker of the ChildControl's renderer, and if it's 4, the <code>RenderManager</code> will skip rendering of the ChildControl.<br>
*
* To allow a more efficient rerendering with an <code>apiVersion 4</code> marker, the following prerequisites must be fulfilled for the control to ensure compatibility:
*
* <ul>
* <li>All the prerequisites of the <code>apiVersion 2</code> marker must be fulfilled by the control.</li>
* <li>The behavior and rendering logic of the control must not rely on the assumption that it will always be re-rendered at the same time as its parent.</li>
* <li>The <code>onBeforeRendering</code> and <code>onAfterRendering</code> hooks of the control must not be used to manipulate or access any elements outside of the control's own DOM structure.</li>
* <li>The control renderer must maintain a proper rendering encapsulation and render only the properties, aggregations, and associations that are specific to the control. The renderer should not reference or depend on any state of the parent control or any other external element.</li>
* <li>If certain aggregations are dependent on the state of the parent control, they must always be rendered together with their parent. To accomplish this, the parent control must use the {@link sap.ui.core.Control#invalidate invalidate} method to signal to the child controls
* that they need to re-render whenever the dependent state of the parent control changes. This guarantees that the child controls are always in sync with the parent control, regardless of the <code>apiVersion</code> definition of their renderer.</li>
* </ul><br>
*
* <b>Note:</b> The rendering can only be skipped if the renderer of each descendant control has the <code>apiVersion 4</code> marker, and no <code>onBeforeRendering</code> or <code>onAfterRendering</code> event delegates are registered. However, while
* {@link sap.ui.core.Element#addEventDelegate adding the event delegate}, setting the <code>canSkipRendering</code> property to <code>true</code> on the event delegate object can be done to indicate that those delegate handlers are compliant with the
* <code>apiVersion:4</code> prerequisites and still allows for rendering optimization.<br>
* The <code>canSkipRendering</code> property can also be used for the controls that enhance the accessibility state of child controls with implementing the {@link sap.ui.core.Element#enhanceAccessibilityState enhanceAccessibilityState} method. In this case,
* setting the <code>canSkipRendering</code> property to <code>true</code> lets the <code>RenderManager</code> know that the parent control's accessibility enhancement is static and does not interfere with the child control's rendering optimization.
*
* @see sap.ui.core.Core
*
* @extends Object
* @author SAP SE
* @version 1.146.0
* @alias sap.ui.core.RenderManager
* @hideconstructor
* @public
*/
function RenderManager() {
var that = this,
aBuffer,
aRenderedControls,
aStyleStack,
bLocked,
sOpenTag = "", // stores the last open tag that is used for the validation
bVoidOpen = false, // specifies whether the last open tag is a void tag or not
bDomInterface, // specifies the rendering interface that is used by the control renderers
sLegacyRendererControlId = "", // stores the id of the control that has a legacy renderer while its parent has the new semantic renderer
oStringInterface = {}, // holds old string based rendering API and the string implementation of the new semantic rendering API
oDomInterface = {}, // semantic rendering API for the controls whose renderer provides apiVersion=2 marker
aRenderingStyles = [], // during string-based rendering, stores the styles that couldn't be set via style attribute due to CSP restrictions
oPatcher = new Patcher(), // the Patcher instance to handle in-place DOM patching
iOpenTagCount = 0, // the number of open tags, this is used to detect missing tags
sLastStyleMethod,
sLastClassMethod;
/**
* Reset all rendering related buffers.
*/
function reset() {
assert(!(sLastStyleMethod = sLastClassMethod = ""));
aBuffer = that.aBuffer = [];
aRenderedControls = that.aRenderedControls = [];
aStyleStack = that.aStyleStack = [{}];
bDomInterface = undefined;
bVoidOpen = false;
iOpenTagCount = 0;
sOpenTag = "";
}
function writeAttribute(sName, vValue) {
aBuffer.push(" ", sName, "=\"", vValue, "\"");
}
function writeClasses(oElement) {
var oStyle = aStyleStack[aStyleStack.length - 1];
// Custom classes are added by default from the currently rendered control. If an oElement is given, this Element's custom style
// classes are added instead. If oElement === false, no custom style classes are added.
var aCustomClasses;
if (oElement) {
aCustomClasses = oElement.aCustomStyleClasses;
} else if (oElement === false) {
aCustomClasses = [];
} else {
aCustomClasses = oStyle.aCustomStyleClasses;
}
if (oStyle.aClasses || aCustomClasses) {
var aClasses = [].concat(oStyle.aClasses || [], aCustomClasses || []);
if (aClasses.length) {
writeAttribute("class", aClasses.join(" "));
}
}
if (!oElement) {
oStyle.aCustomStyleClasses = null;
}
oStyle.aClasses = null;
}
/**
* Used by the string rendering APIs to write out the collected styles during writeStyles/openEnd/voidEnd
* @param {sap.ui.core.RenderManager} oRm The <code>RenderManager</code> instance
* @private
*/
function writeStyles() {
var oStyle = aStyleStack[aStyleStack.length - 1];
if (oStyle.aStyle && oStyle.aStyle.length) {
// Due to possible CSP restrictions we do not write styles into the HTML buffer. Instead, we store the styles in the aRenderingStyles array
// and add a ATTR_STYLE_KEY_MARKER attribute marker for which the value references the original style index in the aRenderingStyles array.
writeAttribute(ATTR_STYLE_KEY_MARKER, aRenderingStyles.push(oStyle.aStyle.join(" ")) - 1);
}
oStyle.aStyle = null;
}
//#################################################################################################
// Assertion methods for validating Semantic Rendering API calls
// These methods will be converted to inline asserts when assertion removal is supported
//#################################################################################################
function assertValidName(sName, sField) {
future.assertThrows(sName && typeof sName == "string" && /^[a-z_][a-zA-Z0-9_\-]*$/.test(sName), "The " + sField + " name provided '" + sName + "' is not valid; it must contain alphanumeric characters, hyphens or underscores");
}
function assertOpenTagHasStarted(sMethod) {
future.assertThrows(sOpenTag, "There is no open tag; '" + sMethod + "' must not be called without an open tag");
}
function assertOpenTagHasEnded(bCustomAssertion) {
var bAssertion = (bCustomAssertion === undefined) ? !sOpenTag : bCustomAssertion;
future.assertThrows(bAssertion, "There is an open tag; '" + sOpenTag + "' tag has not yet ended with '" + (bVoidOpen ? "voidEnd" : "openEnd") + "'");
}
function assertValidAttr(sAttr) {
assertValidName(sAttr, "attr");
future.assertThrows((sAttr != "class" || sLastClassMethod != "class" && (sLastClassMethod = "attr"))
&& (sAttr != "style" || sLastStyleMethod != "style" && (sLastStyleMethod = "attr")),
"Attributes 'class' and 'style' must not be written when the methods with the same name"
+ " have been called for the same element already");
}
function assertValidClass(sClass) {
future.assertThrows(sLastClassMethod != "attr" && (sLastClassMethod = "class"),
"Method class() must not be called after the 'class' attribute has been written for the same element");
future.assertThrows(typeof sClass == "string" && !/\s/.test(sClass) && arguments.length === 1, "Method 'class' must be called with exactly one class name");
}
function assertValidStyle(sStyle) {
future.assertThrows(sLastStyleMethod != "attr" && (sLastStyleMethod = "style"),
"Method style() must not be called after the 'style' attribute has been written for the same element");
future.assertThrows(sStyle && typeof sStyle == "string" && !/\s/.test(sStyle), "Method 'style' must be called with a non-empty string name");
}
//#################################################################################################
// Methods for 'Buffered writer' functionality... (all public)
// i.e. used methods in render-method of Renderers
//#################################################################################################
/**
* Write the given texts to the buffer.
* @param {...string|number} sText (can be a number too)
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @public
* @deprecated Since 1.92. Instead, use the {@link sap.ui.core.RenderManager Semantic Rendering API}.
* There is no 1:1 replacement for <code>write</code>. Typically, <code>write</code> is used to create
* a longer sequence of HTML markup (e.g. an element with attributes and children) in a single call.
* Such a markup sequence has to be split into the individual calls of the Semantic Rendering API.
*
* <br><br>Example:<br>
* oRm.write("<span id=\"" + oCtrl.getId() + "-outer\" class=\"myCtrlOuter\">"
* + "&nbsp;" + oResourceBundle.getText("TEXT_KEY") + "&nbsp;</span>");
* <br><br>
* has to be transformed to
* <br><br>
* oRm.openStart("span", oCtrl.getId() + "-outer").class("myCtrlOuter").openEnd().text("\u00a0" + oResourceBundle.getText("TEXT_KEY") + "\u00a0").close("span");
* <br><br>
* Note that "&nbsp;" was replaced with "\u00a0" (no-break-space). In general, HTML entities
* have to be replaced by the corresponding Unicode character escapes. A mapping table can be found
* at {@link https://html.spec.whatwg.org/multipage/named-characters.html#named-character-references}.
*
* @SecSink {*|XSS}
*/
this.write = function(/** string|number */ sText /* ... */) {
assert(( typeof sText === "string") || ( typeof sText === "number"), "sText must be a string or number");
aBuffer.push.apply(aBuffer, arguments);
return this;
};
/**
* Escape text for HTML and write it to the buffer.
*
* For details about the escaping refer to {@link sap/base/security/encodeXML}.
*
* @param {any} sText the text to escape
* @param {boolean} [bLineBreaks=false] Whether to convert line breaks into <br> tags
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @public
* @deprecated Since 1.92. Instead use {@link sap.ui.core.RenderManager#text} of the {@link sap.ui.core.RenderManager Semantic Rendering API}.
*/
this.writeEscaped = function(sText, bLineBreaks) {
if ( sText != null ) {
sText = encodeXML( String(sText) );
if (bLineBreaks) {
sText = sText.replace(/
/g, "<br>");
}
aBuffer.push(sText);
}
return this;
};
/**
* Writes the attribute and its value into the HTML.
*
* For details about the escaping refer to {@link sap/base/security/encodeXML}.
*
* @param {string} sName Name of the attribute
* @param {string | number | boolean} vValue Value of the attribute
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @public
* @deprecated Since 1.92. Instead use {@link sap.ui.core.RenderManager#attr} of the {@link sap.ui.core.RenderManager Semantic Rendering API}.
* @SecSink {0 1|XSS} Attributes are written to HTML without validation
*/
this.writeAttribute = function(sName, vValue) {
assert(typeof sName === "string", "sName must be a string");
assert(typeof vValue === "string" || typeof vValue === "number" || typeof vValue === "boolean", "value must be a string, number or boolean");
aBuffer.push(" ", sName, "=\"", vValue, "\"");
return this;
};
/**
* Writes the attribute and a value into the HTML, the value will be encoded.
*
* The value is properly encoded to avoid XSS attacks.
*
* @param {string} sName Name of the attribute
* @param {any} vValue Value of the attribute
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @public
* @deprecated Since 1.92. Instead use {@link sap.ui.core.RenderManager#attr} of the {@link sap.ui.core.RenderManager Semantic Rendering API}.
* @SecSink {0|XSS}
*/
this.writeAttributeEscaped = function(sName, vValue) {
assert(typeof sName === "string", "sName must be a string");
aBuffer.push(" ", sName, "=\"", encodeXML(String(vValue)), "\"");
return this;
};
/**
* Adds a style property to the style collection if the value is not empty or null
* The style collection is flushed if it is written to the buffer using {@link #writeStyle}
*
* @param {string} sName Name of the CSS property to write
* @param {string|float|int} vValue Value to write
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @public
* @deprecated Since 1.92. Instead use {@link sap.ui.core.RenderManager#style} of the {@link sap.ui.core.RenderManager Semantic Rendering API}.
* @SecSink {0 1|XSS} Styles are written to HTML without validation
*/
this.addStyle = function(sName, vValue) {
assert(typeof sName === "string", "sName must be a string");
if (vValue != null && vValue != "") {
assert((typeof vValue === "string" || typeof vValue === "number"), "value must be a string or number");
var oStyle = aStyleStack[aStyleStack.length - 1];
if (!oStyle.aStyle) {
oStyle.aStyle = [];
}
oStyle.aStyle.push(sName + ": " + vValue + ";");
}
return this;
};
/**
* Writes and flushes the style collection
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @public
* @deprecated Since 1.92. Not longer needed, when using the {@link sap.ui.core.RenderManager Semantic Rendering API}
* the actual writing of styles happens when {@link sap.ui.core.RenderManager#openEnd} or {@link sap.ui.core.RenderManager#voidEnd} are used.
*/
this.writeStyles = function() {
writeStyles();
return this;
};
/**
* Adds a class to the class collection if the name is not empty or null.
* The class collection is flushed if it is written to the buffer using {@link #writeClasses}
*
* @param {string} sName name of the class to be added; null values are ignored
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @public
* @deprecated Since 1.92. Instead use {@link sap.ui.core.RenderManager#class} of the {@link sap.ui.core.RenderManager Semantic Rendering API}.
* @SecSink {0|XSS} Classes are written to HTML without validation
*/
this.addClass = function(sName) {
if (sName) {
assert(typeof sName === "string", "sName must be a string");
var oStyle = aStyleStack[aStyleStack.length - 1];
if (!oStyle.aClasses) {
oStyle.aClasses = [];
}
oStyle.aClasses.push(sName);
}
return this;
};
/**
* Writes and flushes the class collection (all CSS classes added by "addClass()" since the last flush).
* Also writes the custom style classes added by the application with "addStyleClass(...)". Custom classes are
* added by default from the currently rendered control. If an oElement is given, this Element's custom style
* classes are added instead. If oElement === false, no custom style classes are added.
*
* @param {sap.ui.core.Element | boolean} [oElement] an Element from which to add custom style classes (instead of adding from the control itself)
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @public
* @deprecated Since 1.92. Not longer needed, when using the {@link sap.ui.core.RenderManager Semantic Rendering API}
* the actual writing of classes happens when {@link sap.ui.core.RenderManager#openEnd} or {@link sap.ui.core.RenderManager#voidEnd} are used.
*/
this.writeClasses = function(oElement) {
assert(!oElement || typeof oElement === "boolean" || BaseObject.isObjectA(oElement, 'sap.ui.core.Element'), "oElement must be empty, a boolean, or an sap.ui.core.Element");
writeClasses(oElement);
return this;
};
//#################################################################################################
// Semantic Rendering Interface for String Based Rendering
//#################################################################################################
/**
* Opens the start tag of an HTML element.
*
* This must be followed by <code>openEnd</code> and concluded with <code>close</code>.
* To allow a more efficient DOM update, all tag names have to be used in their canonical form.
* For HTML elements, {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element tag names} must all be set in lowercase.
* For foreign elements, such as SVG, {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element tag names} can be set in upper camel case (e.g. linearGradient).
*
* @param {string} sTagName Tag name of the HTML element
* @param {sap.ui.core.Element|sap.ui.core.ID} [vControlOrId] Control instance or ID to identify the element
* @returns {this} Reference to <code>this</code> in order to allow method chaining
*
* @public
* @since 1.67
*/
this.openStart = function(sTagName, vControlOrId) {
assertValidName(sTagName, "tag");
assertOpenTagHasEnded();
assert(!(sLastStyleMethod = sLastClassMethod = ""));
sOpenTag = sTagName;
iOpenTagCount++;
aBuffer.push("<" + sTagName);
if (vControlOrId) {
if (typeof vControlOrId == "string") {
this.attr("id", vControlOrId);
} else {
future.assertThrows(vControlOrId && BaseObject.isObjectA(vControlOrId, 'sap.ui.core.Element'), "vControlOrId must be an sap.ui.core.Element");
this.attr("id", vControlOrId.getId());
renderElementData(this, vControlOrId);
}
}
return this;
};
/**
* Ends an open tag started with <code>openStart</code>.
*
* This indicates that there are no more attributes to set to the open tag.
*
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @public
* @since 1.67
*/
this.openEnd = function(bExludeStyleClasses /* private */) {
assertOpenTagHasStarted("openEnd");
assertOpenTagHasEnded(!bVoidOpen);
future.assertThrows(bExludeStyleClasses === undefined || bExludeStyleClasses === true, "The private parameter bExludeStyleClasses must be true or omitted!");
sOpenTag = "";
writeClasses(bExludeStyleClasses === true ? false : undefined);
writeStyles();
aBuffer.push(">");
return this;
};
/**
* Closes an open tag started with <code>openStart</code> and ended with <code>openEnd</code>.
*
* This indicates that there are no more children to append to the open tag.
*
* @param {string} sTagName Tag name of the HTML element
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @public
* @since 1.67
*/
this.close = function(sTagName) {
assertValidName(sTagName, "tag");
assertOpenTagHasEnded();
iOpenTagCount--;
aBuffer.push("</" + sTagName + ">");
return this;
};
/**
* Starts a self-closing tag, such as <code>img</code> or <code>input</code>.
*
* This must be followed by <code>voidEnd</code>. For self-closing tags, the <code>close</code> method must not be called.
* To allow a more efficient DOM update, void tag names have to be set in lowercase.
* This API is specific for void elements and must not be used for foreign elements.
* For more information, see {@link https://www.w3.org/TR/html5/syntax.html#void-elements}.
*
* @param {string} sTagName Tag name of the HTML element
* @param {sap.ui.core.Element|sap.ui.core.ID} [vControlOrId] Control instance or ID to identify the element
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @public
* @since 1.67
*/
this.voidStart = function (sTagName, vControlOrId) {
this.openStart(sTagName, vControlOrId);
bVoidOpen = true;
return this;
};
/**
* Ends an open self-closing tag started with <code>voidStart</code>.
*
* This indicates that there are no more attributes to set to the open tag.
* For self-closing tags <code>close</code> must not be called.
*
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @public
* @since 1.67
*/
this.voidEnd = function (bExludeStyleClasses /* private */) {
assertOpenTagHasStarted("voidEnd");
assertOpenTagHasEnded(bVoidOpen || !sOpenTag);
bVoidOpen = false;
iOpenTagCount--;
sOpenTag = "";
writeClasses(bExludeStyleClasses ? false : undefined);
writeStyles();
aBuffer.push(">");
return this;
};
/**
* Sets the given HTML markup without any encoding or sanitizing.
*
* This must not be used for plain texts; use the <code>text</code> method instead.
*
* @param {string} sHtml Well-formed, valid HTML markup
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @public
* @since 1.67
* @SecSink {*|XSS}
*/
this.unsafeHtml = function(sHtml) {
assertOpenTagHasEnded();
aBuffer.push(sHtml);
return this;
};
/**
* Sets the text content with the given text.
*
* Line breaks are not supported by this method, use CSS
* {@link https://www.w3.org/TR/CSS2/text.html#white-space-prop white-space: pre-line}
* option to implement line breaks.
*
* HTML entities are not supported by this method,
* use unicode escaping or the unicode character to implement HTML entities.
* For further information see
* {@link https://html.spec.whatwg.org/multipage/named-characters.html#named-character-references}.
*
* @param {string} sText The text to be written
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @public
* @since 1.67
*/
this.text = function(sText) {
assertOpenTagHasEnded();
if ( sText != null ) {
sText = encodeXML( String(sText) );
aBuffer.push(sText);
}
return this;
};
/**
* Adds an attribute name-value pair to the last open HTML element.
*
* This is only valid when called between <code>openStart/voidStart</code> and <code>openEnd/voidEnd</code>.
* The attribute name must not be equal to <code>style</code> or <code>class</code>.
* Styles and classes must be set via dedicated <code>class</code> or <code>style</code> methods.
* To update the DOM correctly, all attribute names have to be used in their canonical form.
* For HTML elements, {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes attribute names} must all be set in lowercase.
* For foreign elements, such as SVG, {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute attribute names} can be set in upper camel case (e.g. viewBox).
*
* HTML entities are not supported by this method,
* use unicode escaping or the unicode character to implement HTML entities.
* For further information see
* {@link https://html.spec.whatwg.org/multipage/named-characters.html#named-character-references}.
*
* @param {string} sName Name of the attribute
* @param {*} vValue Value of the attribute
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @public
* @since 1.67
*/
this.attr = function(sName, vValue) {
assertValidAttr(sName);
if (sName == "style") {
aStyleStack[aStyleStack.length - 1].aStyle = [vValue];
} else {
aBuffer.push(" ", sName, "=\"", encodeXML(String(vValue)), "\"");
}
return this;
};
/**
* Adds a class name to the class collection of the last open HTML element.
*
* This is only valid when called between <code>openStart/voidStart</code> and <code>openEnd/voidEnd</code>.
* Class name must not contain any whitespace.
*
* @param {string} sClass Class name to be written
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @public
* @since 1.67
*/
this.class = function(sClass) {
if (sClass) {
assertValidClass.apply(this, arguments);
var oStyle = aStyleStack[aStyleStack.length - 1];
if (!oStyle.aClasses) {
oStyle.aClasses = [];
}
oStyle.aClasses.push(encodeXML(sClass));
}
return this;
};
/**
* Adds a style name-value pair to the style collection of the last open HTML element.
*
* This is only valid when called between <code>openStart/voidStart</code> and <code>openEnd/voidEnd</code>.
* To allow a more efficient DOM update, the CSS property names and values have to be used in their canonical form.
* In general, CSS properties are lower-cased in their canonical form, except for parts that are not under the control of CSS.
* For more information, see {@link https://www.w3.org/TR/CSS/#indices}.
*
* @param {string} sName Name of the style property
* @param {string|float|int} vValue Value of the style property
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @public
* @since 1.67
*/
this.style = function(sName, vValue) {
assertValidStyle(sName);
if (vValue != null && vValue != "") {
future.assertThrows((typeof vValue === "string" || typeof vValue === "number"), "value must be a string or number");
var oStyle = aStyleStack[aStyleStack.length - 1];
if (!oStyle.aStyle) {
oStyle.aStyle = [];
}
oStyle.aStyle.push(sName + ": " + vValue + ";");
}
return this;
};
//#################################################################################################
// Semantic Rendering Interface for DOM Based Rendering
//#################################################################################################
// @see sap.ui.core.RenderManager#openStart
oDomInterface.openStart = function(sTagName, vControlOrId) {
assertValidName(sTagName, "tag");
assertOpenTagHasEnded();
assert(!(sLastStyleMethod = sLastClassMethod = ""));
sOpenTag = sTagName;
iOpenTagCount++;
if (!vControlOrId) {
oPatcher.openStart(sTagName);
} else if (typeof vControlOrId == "string") {
oPatcher.openStart(sTagName, vControlOrId);
} else {
oPatcher.openStart(sTagName, vControlOrId.getId());
renderElementData(this, vControlOrId);
}
return this;
};
// @see sap.ui.core.RenderManager#voidStart
oDomInterface.voidStart = function(sTagName, vControlOrId) {
this.openStart(sTagName, vControlOrId);
bVoidOpen = true;
return this;
};
// @see sap.ui.core.RenderManager#attr
oDomInterface.attr = function(sName, vValue) {
assertValidAttr(sName);
assertOpenTagHasStarted("attr");
oPatcher.attr(sName, vValue);
return this;
};
// @see sap.ui.core.RenderManager#class
oDomInterface.class = function(sClass) {
if (sClass) {
assertValidClass.apply(this, arguments);
assertOpenTagHasStarted("class");
oPatcher.class(sClass);
}
return this;
};
// @see sap.ui.core.RenderManager#style
oDomInterface.style = function(sName, vValue) {
assertValidStyle(sName);
assertOpenTagHasStarted("style");
oPatcher.style(sName, vValue);
return this;
};
// @see sap.ui.core.RenderManager#openEnd
oDomInterface.openEnd = function(bExludeStyleClasses /* private */) {
if (bExludeStyleClasses !== true) {
var oStyle = aStyleStack[aStyleStack.length - 1];
var aStyleClasses = oStyle.aCustomStyleClasses;
if (aStyleClasses) {
aStyleClasses.forEach(oPatcher.class, oPatcher);
oStyle.aCustomStyleClasses = null;
}
}
assertOpenTagHasStarted("openEnd");
assertOpenTagHasEnded(!bVoidOpen);
future.assertThrows(bExludeStyleClasses === undefined || bExludeStyleClasses === true, "The private parameter bExludeStyleClasses must be true or omitted!");
sOpenTag = "";
oPatcher.openEnd();
return this;
};
// @see sap.ui.core.RenderManager#voidEnd
oDomInterface.voidEnd = function(bExludeStyleClasses /* private */) {
if (!bExludeStyleClasses) {
var oStyle = aStyleStack[aStyleStack.length - 1];
var aStyleClasses = oStyle.aCustomStyleClasses;
if (aStyleClasses) {
aStyleClasses.forEach(oPatcher.class, oPatcher);
oStyle.aCustomStyleClasses = null;
}
}
assertOpenTagHasStarted("voidEnd");
assertOpenTagHasEnded(bVoidOpen || !sOpenTag);
bVoidOpen = false;
iOpenTagCount--;
sOpenTag = "";
oPatcher.voidEnd();
return this;
};
// @see sap.ui.core.RenderManager#text
oDomInterface.text = function(sText) {
assertOpenTagHasEnded();
if (sText != null) {
oPatcher.text(sText);
}
return this;
};
// @see sap.ui.core.RenderManager#unsafeHtml
oDomInterface.unsafeHtml = function(sHtml) {
assertOpenTagHasEnded();
oPatcher.unsafeHtml(sHtml);
return this;
};
// @see sap.ui.core.RenderManager#close
oDomInterface.close = function(sTagName) {
assertValidName(sTagName, "tag");
assertOpenTagHasEnded();
iOpenTagCount--;
oPatcher.close(sTagName);
return this;
};
//Triggers the BeforeRendering event on the given Control
function triggerBeforeRendering(oControl){
bLocked = true;
try {
var oEvent = new jQuery.Event("BeforeRendering");
// store the element on the event (aligned with jQuery syntax)
oEvent.srcControl = oControl;
oControl._bOnBeforeRenderingPhase = true;
oControl._handleEvent(oEvent);
} finally {
oControl._bOnBeforeRenderingPhase = false;
bLocked = false;
}
}
/**
* Cleans up the rendering state of the given control without rendering it.
*
* A control is responsible for the rendering of all its child controls.
* But in some cases it makes sense that a control only renders a subset of its children
* based on some criterion. For example, a typical carousel control might, for performance
* reasons, only render the currently visible children (and maybe some child before and
* after the visible area to facilitate slide-in / slide-out animations), but not all children.
* This leads to situations where a child had been rendered before, but shouldn't be rendered
* anymore after an update of the carousel's position. The DOM related state of that child then
* must be cleaned up correctly, e.g. by de-registering resize handlers or native event handlers.
* <code>cleanupControlWithoutRendering</code> helps with that task by triggering the same
* activities that the normal rendering triggers before the rendering of a control
* (e.g. it fires the <code>BeforeRendering</code> event). It just doesn't call the renderer
* and the control will not receive an <code>AfterRendering</code> event.
*
* The following example shows how <code>renderControl</code> and <code>cleanupControlWithoutRendering</code>
* should be used:
*
* <pre>
* CarouselRenderer.render = function(rm, oCarousel){
*
* ...
*
* oCarousel.getPages().forEach( function( oPage ) {
* if ( oCarousel.isPageToBeRendered( oPage ) ) {
* rm.renderControl( oPage ); // onBeforeRendering, render, later onAfterRendering
* } else {
* rm.cleanupControlWithoutRendering( oPage ); // onBeforeRendering
* }
* });
*
* ...
*
* };
* </pre>
*
* <h3>DOM Removal</h3>
* The method does not remove the DOM of the given control. The caller of this method has
* to take care to remove it at some later point in time. It should indeed be <i>later</i>,
* not <i>before</i> as the <code>onBeforeRendering</code> hook of the control might need
* access to the old DOM for a proper cleanup.
*
* For parents which are rendered with the normal mechanism as shown in the example above,
* the removal of the old child DOM is guaranteed. The whole DOM of the parent control
* (including the DOM of the no longer rendered child) will be replaced with new DOM (no
* longer containing the child) when the rendering cycle finishes.
*
* <b>Note:</b>: the functionality of this method is different from the default handling for
* invisible controls (controls with <code>visible == false</code>). The standard rendering
* for invisible controls still renders a placeholder DOM. This allows rerendering of the
* invisible control once it becomes visible again without a need to render its parent, too.
* Children that are cleaned up with this method here, are supposed to have no more DOM at all.
* Rendering them later on therefore requires an involvement (typically: a rendering) of
* their parent.
*
* @param {sap.ui.core.Control} oControl Control that should be cleaned up
* @public
* @since 1.22.9
*/
this.cleanupControlWithoutRendering = function(oControl) {
future.assertThrows(!oControl || BaseObject.isObjectA(oControl, 'sap.ui.core.Control'), "oControl must be an sap.ui.core.Control or empty");
if (!oControl) {
return;
}
var oDomRef = oControl.getDomRef();
if (oDomRef) {
// Call beforeRendering to allow cleanup
triggerBeforeRendering(oControl);
// as children are not visited during rendering, their DOM has to be preserved here
RenderManager.preserveContent(oDomRef, /* bPreserveRoot */ false, /* bPreserveNodesWithId */ false);
// Preserved controls still need to be alive
if (!oDomRef.hasAttribute(ATTR_PRESERVE_MARKER)) {
oControl._bNeedsRendering = false;
oControl.bOutput = false;
}
}
};
/**
* Executes the control renderer with the valid rendering interface.
*
* @param {sap.ui.core.Control} oControl The control that should be rendered
* @param {boolean} bTriggerEvent Whether onBeforeRendering event should be triggered or not
* @private
*/
function executeRenderer(oControl, bTriggerEvent) {
// trigger onBeforeRendering hook of the control if needed
if (bTriggerEvent) {
triggerBeforeRendering(oControl);
}
// unbind any generically bound browser event handlers
if (oControl.bOutput == true) {
var aBindings = oControl.aBindParameters;
if (aBindings && aBindings.length > 0) {
var $Control = oControl.$();
aBindings.forEach(function(mParams) {
$Control.off(mParams.sEventType, mParams.fnProxy);
});
}
}
// if the control uses default visible property then use the InvisibleRenderer, otherwise the renderer of the control
var oRenderer = getCurrentRenderer(oControl);
if (oRenderer == InvisibleRenderer) {
// invoke the InvisibleRenderer in case the control uses the default visible property
InvisibleRenderer.render(bDomInterface ? oDomInterface : oStringInterface, oControl);
// if an invisible placeholder was rendered, mark with invisible marker
oControl.bOutput = "invisible";
} else if (oRenderer && typeof oRenderer.render === "function") {
// before the control rendering get custom style classes of the control
var oControlStyles = {};
if (oControl.aCustomStyleClasses && oControl.aCustomStyleClasses.length > 0) {
oControlStyles.aCustomStyleClasses = oControl.aCustomStyleClasses;
}
// push them to the style stack that will be read by the first writeClasses/openEnd/voidEnd call to append additional classes
aStyleStack.push(oControlStyles);
// mark that the rendering phase has been started
oControl._bRenderingPhase = true;
// execute the control renderer according to rendering interface
if (bDomInterface) {
// remember the cursor of the Patcher before the control renderer is executed
var oCurrentNode = oPatcher.getCurrentNode();
// let the rendering happen with DOM rendering interface
oRenderer.render(oDomInterface, oControl);
// determine whether an output is produced
if (oPatcher.getCurrentNode() == oCurrentNode) {
// during the rendering the cursor of the Patcher should move to the next element when openStart or voidStart is called
// compare after rendering cursor with before rendering cursor to determine whether the control produced any output
// we need to remove the control DOM if there is no output produced
oPatcher.unsafeHtml("", oControl.getId());
oControl.bOutput = false;
} else {
// the cursor of the patcher is moved so the output is produced
oControl.bOutput = true;
}
} else {
// remember the buffer size before the control renderer is executed
var iBufferLength = aBuffer.length;
// let the rendering happen with DOM rendering interface
oRenderer.render(oStringInterface, oControl);
// compare after rendering buffer size with the before rendering buffer size to determine whether the control produced any output
oControl.bOutput = (aBuffer.length != iBufferLength);
}
// mark that the rendering phase is over
oControl._bRenderingPhase = false;
// pop from the style stack after rendering for the next control
aStyleStack.pop();
} else {
Log.error("The renderer for class " + oControl.getMetadata().getName() + " is not defined or does not define a render function! Rendering of " + oControl.getId() + " will be skipped!");
}
// store the rendered control
aRenderedControls.push(oControl);
// clear the controls dirty marker
oControl._bNeedsRendering = false;
// let the UIArea know that this control has been rendered
var oUIArea = oControl.getUIArea();
if (oUIArea) {
oUIArea._onControlRendered(oControl);
}
}
/**
* Turns the given control into its HTML representation and appends it to the
* rendering buffer.
*
* If the given control is undefined or null, then nothing is rendered.
*
* @param {sap.ui.core.Control} oControl the control that should be rendered
* @returns {this} Reference to <code>this</code> in order to allow method chaining
* @public
*/
this.renderControl = function(oControl) {
future.assertThrows(!oControl || BaseObject.isObjectA(oControl, 'sap.ui.core.Control'), "oControl must be an sap.ui.core.Control or empty");
if (!oControl) {
return this;
}