@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
331 lines (291 loc) • 11.2 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 default renderer for all web components
sap.ui.define([
"../Element",
"../Control",
"sap/base/strings/hyphenate"
],
function(Element, Control, hyphenate) {
"use strict";
/**
* WebComponent renderer.
*
* @namespace
* @alias sap.ui.core.webc.WebComponentRenderer
* @since 1.138.0
* @static
*/
var WebComponentRenderer = {
apiVersion: 2
};
/**
* Renders the HTML for the given control, using the provided {@link sap.ui.core.RenderManager}.
* @param {sap.ui.core.RenderManager} oRm the RenderManager that can be used for writing to the Render-Output-Buffer
* @param {sap.ui.core.webc.WebComponent} oWebComponent an object representation of the control that should be rendered
*/
WebComponentRenderer.render = function(oRm, oWebComponent){
var sTag = oWebComponent.getMetadata().getTag();
// Opening custom element tag
oRm.openStart(sTag, oWebComponent);
// Properties with mapping="property"
this.renderAttributeProperties(oRm, oWebComponent);
// Properties with mapping="style"
this.renderStyleProperties(oRm, oWebComponent);
// Properties, managed by associations
this.renderAssociationProperties(oRm, oWebComponent);
// Tooltip aggregation
this.renderTooltipAggregation(oRm, oWebComponent);
// Hook for customization
this.customRenderInOpeningTag(oRm, oWebComponent);
// Attributes/Styles that the component sets internally
this.preserveUnmanagedAttributes(oRm, oWebComponent);
// Styles that the component sets internally
this.preserveUnmanagedStyles(oRm, oWebComponent);
oRm.openEnd();
// Properties with mapping="textContent"
this.renderTextContentProperties(oRm, oWebComponent);
// Properties with mapping="slot"
this.renderSlotProperties(oRm, oWebComponent);
// Aggregations
this.renderAggregations(oRm, oWebComponent);
// Hook for customization (additional children)
this.customRenderInsideTag(oRm, oWebComponent);
// Closing custom element tag
oRm.close(sTag);
};
/**
* Renders attributes, based on the control's properties
* @private
* @param oRm
* @param oWebComponent
*/
WebComponentRenderer.renderAttributeProperties = function(oRm, oWebComponent) {
var oAttrProperties = oWebComponent.getMetadata().getPropertiesByMapping("property");
var aPropsToAlwaysSet = ["enabled"].concat(Object.entries(oWebComponent.getMetadata().getPropertyDefaults()).map(([key, value]) => {
return value !== undefined && value !== false ? key : null;
})); // some properties can be initial and still have a non-default value due to side effects (e.g. EnabledPropagator)
for (var sPropName in oAttrProperties) {
var oPropData = oAttrProperties[sPropName];
if (oWebComponent.isPropertyInitial(sPropName) && !aPropsToAlwaysSet.includes(sPropName)) {
continue; // do not set attributes for properties that were not explicitly set or bound
}
var vPropValue = oPropData.get(oWebComponent);
if (oPropData.type === "object" || typeof vPropValue === "object") {
continue; // Properties of type "object" and custom-type properties with object values are set during onAfterRendering
}
var sAttrName = oPropData._sMapTo ? oPropData._sMapTo : hyphenate(sPropName);
if (oPropData._fnMappingFormatter) {
vPropValue = oWebComponent[oPropData._fnMappingFormatter].call(oWebComponent, vPropValue);
}
if (oPropData.type === "boolean") {
if (vPropValue) {
oRm.attr(sAttrName, "");
}
} else {
if (vPropValue != null) {
oRm.attr(sAttrName, vPropValue);
}
}
}
};
/**
* Preserves attributes that the component set on itself internally (such as private attributes and the attribute that mimics the tag, e.g. "ui5-button")
* This is necessary as otherwise Patcher.js will remove them upon each re-rendering
* @private
* @param oRm
* @param oWebComponent
*/
WebComponentRenderer.preserveUnmanagedAttributes = function(oRm, oWebComponent) {
var oDomRef = oWebComponent.getDomRef();
if (!oDomRef) {
return; // First rendering - the unmanaged attributes haven't been set yet
}
var aAttributes = oDomRef.getAttributeNames();
var aSkipList = ["id", "data-sap-ui", "style", "class", "__is-busy"];
aAttributes.forEach(function(sAttr) {
if (aSkipList.indexOf(sAttr) !== -1) {
return; // Skip attributes, set by the framework
}
if (oWebComponent.getMetadata().isManagedAttribute(sAttr)) {
return;
}
var sValue = oDomRef.getAttribute(sAttr); // Repeat the value from DOM
if (sValue !== null) {
oRm.attr(sAttr, sValue);
}
});
};
/**
* Preserves styles that the component set on itself internally (such as position top, left and CSS Variables)
* This is necessary as otherwise Patcher.js will remove them upon each re-rendering
* @private
* @param oRm
* @param oWebComponent
*/
WebComponentRenderer.preserveUnmanagedStyles = function(oRm, oWebComponent) {
var oDomRef = oWebComponent.getDomRef();
if (!oDomRef) {
return; // First rendering - the unmanaged styles haven't been set yet
}
var aSetStyles = Array.prototype.slice.apply(oDomRef.style);
if (aSetStyles.length === 0) {
return; // No styles set at all
}
var oStyleProperties = oWebComponent.getMetadata().getPropertiesByMapping("style");
var aManagedStyles = [];
for (var sPropName in oStyleProperties) {
var oPropData = oStyleProperties[sPropName];
var sStyleName = oPropData._sMapTo ? oPropData._sMapTo : hyphenate(sPropName);
aManagedStyles.push(sStyleName);
}
aSetStyles.forEach(function(sStyle) {
if (aManagedStyles.indexOf(sStyle) !== -1) {
return; // Do not preserve any managed styles
}
var sValue = sStyle.startsWith("--") ? window.getComputedStyle(oDomRef).getPropertyValue(sStyle) : oDomRef.style[sStyle]; // CSS Values can only be read from getComputedStyle
oRm.style(sStyle, sValue);
});
};
/**
* Renders styles, based on the control's properties
* @private
* @param oRm
* @param oWebComponent
*/
WebComponentRenderer.renderStyleProperties = function(oRm, oWebComponent) {
var oStyleProperties = oWebComponent.getMetadata().getPropertiesByMapping("style");
for (var sPropName in oStyleProperties) {
var oPropData = oStyleProperties[sPropName];
var sStyleName = oPropData._sMapTo ? oPropData._sMapTo : hyphenate(sPropName);
var vPropValue = oPropData.get(oWebComponent);
if (oPropData._fnMappingFormatter) {
vPropValue = oWebComponent[oPropData._fnMappingFormatter].call(oWebComponent, vPropValue);
}
if (vPropValue != null) {
oRm.style(sStyleName, vPropValue);
}
}
};
/**
* Renders properties, controlled by associations
* @private
* @param oRm
* @param oWebComponent
*/
WebComponentRenderer.renderAssociationProperties = function(oRm, oWebComponent) {
var oAssociations = oWebComponent.getMetadata().getAssociationsWithMapping();
for (var sAssocName in oAssociations) {
var oAssocData = oAssociations[sAssocName];
var vAssocValue = oAssocData.get(oWebComponent);
var sAttrName = hyphenate(oAssocData._sMapTo); // The name of the attribute to be set with the association's ID value
if (oAssocData._fnMappingFormatter) {
vAssocValue = oWebComponent[oAssocData._fnMappingFormatter].call(oWebComponent, vAssocValue);
}
if (!oAssocData.multiple && vAssocValue && typeof vAssocValue === "object") {
vAssocValue = vAssocValue.getId(); // The value will be the control ID, held by the association
}
if (vAssocValue) { // Only set the property, if the association is set
oRm.attr(sAttrName, vAssocValue);
}
}
};
/**
* Transforms the tooltip aggregation to a tooltip attribute - components that support this attribute will use it
* @private
* @param oRm
* @param oWebComponent
*/
WebComponentRenderer.renderTooltipAggregation = function(oRm, oWebComponent) {
var sTooltipText = oWebComponent.getTooltip_Text();
if (sTooltipText) {
oRm.attr("tooltip", sTooltipText);
}
};
/**
* Renders text inside the component, if it has a property of type textContent
* Normally a single property of this type is expected (such as button text), but if more than one are set, they are all rendered
* @private
* @param oRm
* @param oWebComponent
*/
WebComponentRenderer.renderTextContentProperties = function(oRm, oWebComponent) {
var oTextContentProperties = oWebComponent.getMetadata().getPropertiesByMapping("textContent");
for (var sPropName in oTextContentProperties) {
var oPropData = oTextContentProperties[sPropName];
var vPropValue = oPropData.get(oWebComponent);
if (oPropData._fnMappingFormatter) {
vPropValue = oWebComponent[oPropData._fnMappingFormatter].call(oWebComponent, vPropValue);
}
oRm.text(vPropValue);
}
};
/**
* Renders properties as slotted text inside a div/span or another tag
* This is mostly useful for value state message as UI5 Web Components get the value state message as slotted text
* @private
* @param oRm
* @param oWebComponent
*/
WebComponentRenderer.renderSlotProperties = function(oRm, oWebComponent) {
var oSlotProperties = oWebComponent.getMetadata().getPropertiesByMapping("slot");
for (var sPropName in oSlotProperties) {
var oPropData = oSlotProperties[sPropName];
var vPropValue = oPropData.get(oWebComponent);
if (oPropData._fnMappingFormatter) {
vPropValue = oWebComponent[oPropData._fnMappingFormatter].call(oWebComponent, vPropValue);
}
// WebComponentMetadata defines the render output, e.g. { mapping: { slotName: "valueStateMessage", to: "div", ... } }
var sTag = oPropData._sMapTo ? oPropData._sMapTo : "span";
const sSlotName = oPropData._sSlotName || sPropName;
if (vPropValue) {
oRm.openStart(sTag);
oRm.attr("slot", sSlotName);
oRm.openEnd();
oRm.text(vPropValue);
oRm.close(sTag);
}
}
};
/**
* Render children.
* Note: for each child, RenderManager.js will set the "slot" attribute automatically
* @private
* @param oRm
* @param oWebComponent
*/
WebComponentRenderer.renderAggregations = function(oRm, oWebComponent) {
var oAggregations = oWebComponent.getMetadata().getAllAggregations();
for (var sAggName in oAggregations) {
if (Element.getMetadata().getAggregations().hasOwnProperty(sAggName) || Control.getMetadata().getAggregations().hasOwnProperty(sAggName)) {
continue; // Skip aggregations, derived from Element.js / Control.js such as dependents and layoutData
}
var aggData = oAggregations[sAggName];
var aggValue = aggData.get(oWebComponent);
if (aggData.multiple) {
aggValue.forEach(oRm.renderControl, oRm);
} else {
if (aggValue) {
oRm.renderControl(aggValue);
}
}
}
};
/**
* Hook. For future use.
* @protected
* @param oRm
* @param oWebComponent
*/
WebComponentRenderer.customRenderInOpeningTag = function(oRm, oWebComponent) {};
/**
* Hook. For future use.
* @param oRm
* @param oWebComponent
*/
WebComponentRenderer.customRenderInsideTag = function(oRm, oWebComponent) {};
return WebComponentRenderer;
});