UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

432 lines (386 loc) 13.1 kB
/*! * OpenUI5 * (c) Copyright 2009-2023 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ // Provides control sap.m.FormattedText. sap.ui.define([ './library', 'sap/ui/core/library', 'sap/ui/core/Control', './FormattedTextAnchorGenerator', './FormattedTextRenderer', "sap/base/Log", "sap/base/security/URLListValidator", "sap/base/security/sanitizeHTML", "sap/ui/util/openWindow", 'sap/ui/core/Core' ], function( library, coreLibrary, Control, FormattedTextAnchorGenerator, FormattedTextRenderer, Log, URLListValidator, sanitizeHTML0, openWindow, Core ) { "use strict"; // shortcut for sap.m.LinkConversion var LinkConversion = library.LinkConversion, TextDirection = coreLibrary.TextDirection, TextAlign = coreLibrary.TextAlign; /** * Constructor for a new FormattedText. * * @param {string} [sId] ID for the new control, generated automatically if no ID is given * @param {object} [mSettings] Initial settings for the new control * * @class * The FormattedText control allows the usage of a limited set of tags for inline display of formatted text in HTML format. * @extends sap.ui.core.Control * @version 1.117.4 * * @constructor * @public * @since 1.38.0 * @alias sap.m.FormattedText */ var FormattedText = Control.extend("sap.m.FormattedText", /** @lends sap.m.FormattedText.prototype */ { metadata: { library: "sap.m", properties: { /** * Text in HTML format. * The following tags are supported: * <ul> * <li><code>a</code></li> * <li><code>abbr</code></li> * <li><code>bdi</code></li> * <li><code>blockquote</code></li> * <li><code>br</code></li> * <li><code>cite</code></li> * <li><code>code</code></li> * <li><code>em</code></li> * <li><code>h1</code></li> * <li><code>h2</code></li> * <li><code>h3</code></li> * <li><code>h4</code></li> * <li><code>h5</code></li> * <li><code>h6</code></li> * <li><code>p</code></li> * <li><code>pre</code></li> * <li><code>strong</code></li> * <li><code>span</code></li> * <li><code>u</code></li> * <li><code>dl</code></li> * <li><code>dt</code></li> * <li><code>dd</code></li> * <li><code>ul</code></li> * <li><code>ol</code></li> * <li><code>li</code></li> * </ul> * <p><code>style, dir</code> and <code>target</code> attributes are allowed. * <p>If <code>target</code> is not set, links open in a new window by default. * <p>Only safe <code>href</code> attributes can be used. See {@link module:sap/base/security/URLListValidator URLListValidator}. * * <b>Note:</b> Keep in mind that not supported HTML tags and * the content nested inside them are both not rendered by the control. */ htmlText: {type: "string", group: "Misc", defaultValue: ""}, /** * Optional width of the control in CSS units. */ width: {type : "sap.ui.core.CSSSize", group : "Appearance", defaultValue : null}, /** * Determines whether strings that appear to be links will be converted to HTML anchor tags, * and what are the criteria for recognizing them. * @since 1.45.5 */ convertLinksToAnchorTags: {type: "sap.m.LinkConversion", group: "Behavior", defaultValue: LinkConversion.None}, /** * Determines the <code>target</code> attribute of the generated HTML anchor tags. * * <b>Note:</b> Applicable only if <code>ConvertLinksToAnchorTags</code> property is used with a value other than <code>sap.m.LinkConversion.None</code>. * Options are the standard values for the <code>target</code> attribute of the HTML anchor tag: * <code>_self</code>, <code>_top</code>, <code>_blank</code>, <code>_parent</code>, <code>_search</code>. * @since 1.45.5 */ convertedLinksDefaultTarget: {type: "string", group: "Behavior", defaultValue: "_blank"}, /** * Optional height of the control in CSS units. */ height: {type : "sap.ui.core.CSSSize", group : "Appearance", defaultValue : null}, /** * Defines the directionality of the text in the <code>FormattedText</code>, e.g. right-to-left(<code>RTL</code>) * or left-to-right (<code>LTR</code>). * * <b>Note:</b> This functionality if set to the root element. Use the <code>bdi</code> element and * the <code>dir</code> attribute to set explicit direction to an element. * * @since 1.86.0 */ textDirection: { type: "sap.ui.core.TextDirection", group: "Appearance", defaultValue: TextDirection.Inherit }, /** * Determines the text alignment in the text elements in the <code>FormattedText</code>. * * <b>Note:</b> This functionality if set to the root element. To set explicit alignment to an element * use the <code>style</code> attribute. * * @since 1.86.0 */ textAlign : {type : "sap.ui.core.TextAlign", group : "Appearance", defaultValue : TextAlign.Begin} }, aggregations: { /** * List of <code>sap.m.Link</code> controls that will be used to replace the placeholders in the text. * Placeholders are replaced according to their indexes. The placeholder with index %%0 will be replaced * by the first link in the aggregation, etc. */ controls: {type: "sap.m.Link", multiple: true, singularName: "control"} } }, renderer: FormattedTextRenderer }); /* * These are the rules for the FormattedText */ var _defaultRenderingRules = { // rules for the allowed attributes ATTRIBS: { 'style' : 1, 'class' : 1, 'a::href' : 1, 'a::target' : 1, 'dir' : 1 }, // rules for the allowed tags ELEMENTS: { // Text Module Tags 'a' : {cssClass: 'sapMLnk'}, 'abbr': 1, 'bdi' : 1, 'blockquote': 1, 'br': 1, 'cite': 1, 'code': 1, 'em': 1, 'h1': {cssClass: 'sapMTitle sapMTitleStyleH1'}, 'h2': {cssClass: 'sapMTitle sapMTitleStyleH2'}, 'h3': {cssClass: 'sapMTitle sapMTitleStyleH3'}, 'h4': {cssClass: 'sapMTitle sapMTitleStyleH4'}, 'h5': {cssClass: 'sapMTitle sapMTitleStyleH5'}, 'h6': {cssClass: 'sapMTitle sapMTitleStyleH6'}, 'p': 1, 'pre': 1, 'strong': 1, 'span': 1, 'u' : 1, // List Module Tags 'dl': 1, 'dt': 1, 'dd': 1, 'ol': 1, 'ul': 1, 'li': 1 } }, _limitedRenderingRules = { ATTRIBS: { 'a::href' : 1, 'a::target' : 1 }, ELEMENTS: { 'a' : {cssClass: 'sapMLnk'}, 'br': 1, 'em': 1, 'strong': 1, 'u': 1 } }; /** * Holds the internal list of allowed and evaluated HTML elements and attributes * @private */ FormattedText.prototype._renderingRules = _defaultRenderingRules; /** * Initialization hook for the FormattedText, which creates a list of rules with allowed tags and attributes. */ FormattedText.prototype.init = function () { }; /** * Sanitizes attributes on an HTML tag. * * @param {string} tagName An HTML tag name in lower case * @param {array} attribs An array of alternating names and values * @return {array} The sanitized attributes as a list of alternating names and values. Value <code>null</code> removes the attribute. * @private */ function fnSanitizeAttribs (tagName, attribs) { var sWarning; var attr, value, addTarget = tagName === "a"; // add UI5 specific classes when appropriate var cssClass = this._renderingRules.ELEMENTS[tagName].cssClass || ""; for (var i = 0; i < attribs.length; i += 2) { // attribs[i] is the name of the tag's attribute. // attribs[i+1] is its corresponding value. // (i.e. <span class="foo"> -> attribs[i] = "class" | attribs[i+1] = "foo") attr = attribs[i]; value = attribs[i + 1]; if (!this._renderingRules.ATTRIBS[attr] && !this._renderingRules.ATTRIBS[tagName + "::" + attr]) { sWarning = 'FormattedText: <' + tagName + '> with attribute [' + attr + '="' + value + '"] is not allowed'; Log.warning(sWarning, this); // to remove the attribute by the sanitizer, set the value to null attribs[i + 1] = null; continue; } // sanitize hrefs if (attr == "href") { // a::href if (!URLListValidator.validate(value)) { Log.warning("FormattedText: incorrect href attribute:" + value, this); attribs[i + 1] = "#"; addTarget = false; } } if (attr == "target") { // a::target already exists addTarget = false; } // add UI5 classes to the user defined if (cssClass && attr.toLowerCase() == "class") { attribs[i + 1] = cssClass + " " + value; cssClass = ""; } } if (addTarget) { attribs.push("target"); attribs.push("_blank"); } // add UI5 classes, if not done before if (cssClass) { attribs.push("class"); attribs.push(cssClass); } return attribs; } function fnPolicy (tagName, attribs) { if (this._renderingRules.ELEMENTS[tagName]) { return fnSanitizeAttribs.call(this, tagName, attribs); } else { var sWarning = '<' + tagName + '> is not allowed'; Log.warning(sWarning, this); } } /** * Sanitizes HTML tags and attributes according to a given policy. * * @param {string} sText The HTML to sanitize * @return {string} The sanitized HTML * @private */ function sanitizeHTML(sText) { return sanitizeHTML0(sText, { tagPolicy: fnPolicy.bind(this), uriRewriter: function (sUrl) { // by default, we use the URLListValidator to check the URLs if (URLListValidator.validate(sUrl)) { return sUrl; } } }); } // open links href using safe API function openLink (oEvent) { if (oEvent.originalEvent.defaultPrevented) { return; } oEvent.preventDefault(); var oLink = Core.byId(oEvent.currentTarget.id); if (oLink && oLink.isA('sap.m.Link') && (oLink.getAccessibleRole() === library.LinkAccessibleRole.Button || !oLink.getHref())) { return; } openWindow(oEvent.currentTarget.href, oEvent.currentTarget.target); } FormattedText.prototype.onAfterRendering = function () { this.$().find('a').on("click", openLink); var aLinks = this.getControls(), oTemplate; aLinks.forEach(function(oLink, iCurrentIndex) { oTemplate = this.getDomRef("$" + iCurrentIndex); if (oTemplate) { oTemplate.replaceWith(oLink.getDomRef()); } else { oLink.getDomRef().style.display = "none"; } }.bind(this)); this._sanitizeCSSPosition(this.getDomRef()); }; FormattedText.prototype.onBeforeRendering = function () { this.$().find('a').off("click", openLink); }; /** * Adds CSS static position to provided DOM reference internal HTML nodes. * * @param {Element} oDomRef DOM reference that should be sanitized * @private */ FormattedText.prototype._sanitizeCSSPosition = function(oDomRef) { if (!oDomRef) { return; } var oWalker = document.createTreeWalker( oDomRef, NodeFilter.SHOW_ELEMENT ), oCurrentNode = oWalker.nextNode(); while (oCurrentNode) { oCurrentNode.style.setProperty("position", "static", "important"); oCurrentNode = oWalker.nextNode(); } }; /** * Returns the HTML that should be displayed. * * IMPORTANT NOTE: When a HTML returned by this method is being placed in the page DOM, ALWAYS call _sanitizeCSSPosition * after it is rendered on the page DOM in order to sanitize the CSS position! * * @return {string} HTML that should be rendered * @private */ FormattedText.prototype._getDisplayHtml = function (){ var sText = this.getHtmlText(), sAutoGenerateLinkTags = this.getConvertLinksToAnchorTags(); if (sAutoGenerateLinkTags === LinkConversion.None) { return sText; } sText = FormattedTextAnchorGenerator.generateAnchors(sText, sAutoGenerateLinkTags, this.getConvertedLinksDefaultTarget()); return sanitizeHTML.call(this, sText); }; /** * Defines the HTML text to be displayed. * @param {string} sText HTML text as a string * @return {this} this for chaining * @public */ FormattedText.prototype.setHtmlText = function (sText) { return this.setProperty("htmlText", sanitizeHTML.call(this, sText)); }; /** * Sets should a limited list of rendering rules be used instead of the default one. This limited list * will evaluate only a small subset of the default HTML elements and attributes. * @param {boolean} bLimit Should the control use the limited list * @private * @ui5-restricted sap.m.MessageStrip */ FormattedText.prototype._setUseLimitedRenderingRules = function (bLimit) { this._renderingRules = bLimit ? _limitedRenderingRules : _defaultRenderingRules; }; FormattedText.prototype.getFocusDomRef = function () { return this.getDomRef() && this.getDomRef().querySelector("a"); }; return FormattedText; });