@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
1,177 lines (1,065 loc) • 118 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, string-based rendering is 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", "renderAndFlush", "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";
/**
* Converts an element to a specified namespace URI.
*
* This function recursively converts a DOM element and all its child elements to a target namespace.
* If an element already belongs to the target namespace, it is returned unchanged for performance reasons.
* All attributes and child nodes are preserved during the conversion.
*
* Non-element nodes (text nodes, comment nodes, etc.) are returned as-is without modification.
*
* @param {Element} oElement - The DOM element to be converted
* @param {string} sNamespaceURI - The target namespace URI (e.g., "http://www.w3.org/2000/svg")
*
* @returns {Element|Node} A new element with the target namespace URI, or the original element
* if it already belongs to the target namespace or is a non-element node
* @private
*/
function convertElementToNamespace(oElement, sNamespaceURI) {
if (oElement.nodeType !== Node.ELEMENT_NODE) {
return oElement;
}
const sCurrentNamespace = oElement.namespaceURI;
if (sCurrentNamespace === sNamespaceURI) {
return oElement;
}
const oNewElement = document.createElementNS(sNamespaceURI, oElement.tagName.toLowerCase());
for (const oAttr of oElement.attributes) {
oNewElement.setAttribute(oAttr.name, oAttr.value);
}
const aChildren = Array.from(oElement.childNodes);
for (const oChild of aChildren) {
oNewElement.appendChild(convertElementToNamespace(oChild, sNamespaceURI));
}
return oNewElement;
}
/**
* 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.147.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
bInitialized = false, // specifies whether the rendering process has been initialized by calling the render method or not
/**
* Specifies the rendering interface that is used by the control renderers
* @ui5-transform-hint replace-local true
*/
bDomInterface,
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 = ""));
/**
* @deprecated As of version 1.92, string-based rendering is deprecated
*/
aBuffer = that.aBuffer = [];
aRenderedControls = that.aRenderedControls = [];
aStyleStack = that.aStyleStack = [{}];
bDomInterface = undefined;
bVoidOpen = false;
bInitialized = 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;
};
/**
* 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
*
* @name sap.ui.core.RenderManager#openStart
* @function
* @public
* @since 1.67
*/
/**
* 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
*
* @name sap.ui.core.RenderManager#openEnd
* @function
* @public
* @since 1.67
*/
/**
* 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
*
* @name sap.ui.core.RenderManager#close
* @function
* @public
* @since 1.67
*/
/**
* 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
*
* @name sap.ui.core.RenderManager#voidStart
* @function
* @public
* @since 1.67
*/
/**
* 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
*
* @name sap.ui.core.RenderManager#voidEnd
* @function
* @public
* @since 1.67
*/
/**
* 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
*
* @name sap.ui.core.RenderManager#unsafeHtml
* @function
* @public
* @since 1.67
* @SecSink {*|XSS}
*/
/**
* 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
*
* @name sap.ui.core.RenderManager#text
* @function
* @public
* @since 1.67
*/
/**
* 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
*
* @name sap.ui.core.RenderManager#attr
* @function
* @public
* @since 1.67
*/
/**
* 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
*
* @name sap.ui.core.RenderManager#class
* @function
* @public
* @since 1.67
*/
/**
* 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
*
* @name sap.ui.core.RenderManager#style
* @function
* @public
* @since 1.67
*/
//#################################################################################################
// Semantic Rendering Interface for String Based Rendering
//#################################################################################################
/**
* @see sap.ui.core.RenderManager#openStart
* @deprecated As of version 1.92, string-based rendering is deprecated
*/
oStringInterface.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;
};
/**
* @see sap.ui.core.RenderManager#openEnd
* @deprecated As of version 1.92, string-based rendering is deprecated
*/
oStringInterface.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;
};
/**
* @see sap.ui.core.RenderManager#close
* @deprecated As of version 1.92, string-based rendering is deprecated
*/
oStringInterface.close = function(sTagName) {
assertValidName(sTagName, "tag");
assertOpenTagHasEnded();
iOpenTagCount--;
aBuffer.push("</" + sTagName + ">");
return this;
};
/**
* @see sap.ui.core.RenderManager#voidStart
* @deprecated As of version 1.92, string-based rendering is deprecated
*/
oStringInterface.voidStart = function (sTagName, vControlOrId) {
this.openStart(sTagName, vControlOrId);
bVoidOpen = true;
return this;
};
/**
* @see sap.ui.core.RenderManager#voidEnd
* @deprecated As of version 1.92, string-based rendering is deprecated
*/
oStringInterface.voidEnd = function (bExludeStyleClasses /* private */) {
assertOpenTagHasStarted("voidEnd");
assertOpenTagHasEnded(bVoidOpen || !sOpenTag);
bVoidOpen = false;
iOpenTagCount--;
sOpenTag = "";
writeClasses(bExludeStyleClasses ? false : undefined);
writeStyles();
aBuffer.push(">");
return this;
};
/**
* @see sap.ui.core.RenderManager#unsafeHtml
* @deprecated As of version 1.92, string-based rendering is deprecated
*/
oStringInterface.unsafeHtml = function(sHtml) {
assertOpenTagHasEnded();
aBuffer.push(sHtml);
return this;
};
/**
* @see sap.ui.core.RenderManager#text
* @deprecated As of version 1.92, string-based rendering is deprecated
*/
oStringInterface.text = function(sText) {
assertOpenTagHasEnded();
if ( sText != null ) {
sText = encodeXML( String(sText) );
aBuffer.push(sText);
}
return this;
};
/**
* @see sap.ui.core.RenderManager#attr
* @deprecated As of version 1.92, string-based rendering is deprecated
*/
oStringInterface.attr = function(sName, vValue) {
assertValidAttr(sName);
if (sName == "style") {
aStyleStack[aStyleStack.length - 1].aStyle = [vValue];
} else {
aBuffer.push(" ", sName, "=\"", encodeXML(String(vValue)), "\"");
}
return this;
};
/**
* @see sap.ui.core.RenderManager#class
* @deprecated As of version 1.92, string-based rendering is deprecated
*/
oStringInterface.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;
};
/**
* @see sap.ui.core.RenderManager#style
* @deprecated As of version 1.92, string-based rendering is deprecated
*/
oStringInterface.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 = ""));
// initialize patcher's root node if not yet done
if (!bInitialized) {
oPatcher.setRootNode();
bInitialized = true;
}
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) {
if (!bInitialized) {
oPatcher.setRootNode();
bInitialized = true;
}
// 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