UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

593 lines (530 loc) 18.8 kB
/*! * OpenUI5 * (c) Copyright 2026 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ sap.ui.define([ "./library", "sap/ui/core/Control", "sap/m/TextArea", "sap/m/Button", "./FeedInputRenderer", "sap/ui/thirdparty/jquery", "sap/base/security/URLListValidator", "sap/base/security/sanitizeHTML", "sap/m/Avatar", "sap/m/AvatarShape", "sap/m/AvatarSize", "sap/ui/core/Lib", "sap/base/Log" ], function(library, Control, TextArea, Button, FeedInputRenderer, jQuery, URLListValidator, sanitizeHTML0, Avatar, AvatarShape, AvatarSize, CoreLib, Log) { "use strict"; // shortcut for sap.m.ButtonType var ButtonType = library.ButtonType; var MAX_ROWS = 15, MIN_ROWS = 2, UNLIMITED_ROWS = 0; /** * Constructor for a new FeedInput. * * @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 Feed Input allows the user to enter text for a new feed entry and then post it. * @extends sap.ui.core.Control * * @author SAP SE * @version 1.146.0 * * @constructor * @public * @since 1.22 * @alias sap.m.FeedInput */ var FeedInput = Control.extend("sap.m.FeedInput", /** @lends sap.m.FeedInput.prototype */ { metadata : { library : "sap.m", designtime: "sap/m/designtime/FeedInput.designtime", properties : { /** * Set this flag to "false" to disable both text input and post button. */ enabled : {type : "boolean", group : "Behavior", defaultValue : true}, /** * Defines the number of visible text lines for the control. * <b>Note:</b> Minimum value is 2, maximum value is 15. */ rows : {type : "int", group : "Appearance", defaultValue : 2}, /** * Determines whether the characters, exceeding the maximum allowed character count, are visible in the input field. * * If set to <code>false</code>, the user is not allowed to enter more characters than what is set in the <code>maxLength</code> property. * If set to <code>true</code>, the characters exceeding the <code>maxLength</code> value are selected on paste and the counter below * the input field displays their number. */ showExceededText: {type: "boolean", group: "Behavior", defaultValue: false}, /** * The maximum length (the maximum number of characters) for the feed's input value. By default this is not limited. */ maxLength : {type : "int", group : "Behavior", defaultValue : 0}, /** * Indicates the ability of the control to automatically grow and shrink dynamically with its content. */ growing : {type : "boolean", group : "Behavior", defaultValue : false}, /** * Defines the maximum number of lines that the control can grow. * Value is set to 0 by default, which means an unlimited numbers of rows. * <b>Note:</b> Minimum value to set is equal to the <code>rows</code> property value, maximum value is 15. */ growingMaxLines : {type : "int", group : "Behavior", defaultValue : 0}, /** * The placeholder text shown in the input area as long as the user has not entered any text value. */ placeholder : {type : "string", group : "Appearance", defaultValue : "Post something here"}, /** * The text value of the feed input. As long as the user has not entered any text the post button is disabled */ value : {type : "string", group : "Data", defaultValue : null}, /** * Icon to be displayed as a graphical element within the feed input. This can be an image or an icon from the icon font. */ icon : {type : "sap.ui.core.URI", group : "Data", defaultValue : null}, /** * Defines the shape of the icon. * @since 1.88 */ iconDisplayShape: { type: "sap.m.AvatarShape", defaultValue: AvatarShape.Circle}, /** * Defines the initials of the icon. * @since 1.88 */ iconInitials: { type: "string", defaultValue: "" }, /** * Defines the size of the icon. * @since 1.88 */ iconSize: { type: "sap.m.AvatarSize", defaultValue: AvatarSize.M}, /** * If set to "true" (default), icons will be displayed. In case no icon is provided the standard placeholder will be displayed. if set to "false" icons are hidden */ showIcon : {type : "boolean", group : "Behavior", defaultValue : true}, /** * Some mobile devices support higher resolution images while others do not. Therefore, you should provide image resources for all relevant densities. * If the property is set to "true", one or more requests are sent to the server to try and get the perfect density version of an image. If an image of a certain density is not available, the image control falls back to the default image, which should be provided. * * If you do not have higher resolution images, you should set the property to "false" to avoid unnecessary round-trips. * * Please be aware that this property is relevant only for images and not for icons. * * @deprecated as of version 1.88. Image replaced by {@link sap.m.Avatar } */ iconDensityAware : {type : "boolean", group : "Appearance", defaultValue : true}, /** * Sets a new tooltip for Submit button. The tooltip can either be a simple string (which in most cases will be rendered as the title attribute of this element) * or an instance of sap.ui.core.TooltipBase. * If a new tooltip is set, any previously set tooltip is deactivated. * The default value is set language dependent. * @since 1.28 * @type {sap.ui.core.TooltipBase|string} */ buttonTooltip : {type : "any", group : "Accessibility", defaultValue : "Submit"}, /** * Text for Picture which will be read by screenreader. * If a new ariaLabelForPicture is set, any previously set ariaLabelForPicture is deactivated. * @deprecated as of version 1.88. This will not have any effect in code now. */ ariaLabelForPicture : {type : "string", group : "Accessibility", defaultValue : null} }, aggregations : { /** * Defines the inner avatar control. */ _avatar: { type: "sap.m.Avatar", multiple: false, visibility: "hidden" }, /** * Defines the actions that are displayed next to the text area. These buttons can be used to trigger actions, such as attaching a file. * This is a {@link sap.m.Button} * If the actions are not set, the post button is displayed as the last control in the feed input. * <b>Note:</b> Only <code>sap.m.Button</code> is supported for this aggregation. If another control is provided, an error is logged and actions are ignored. * @since 1.139 */ actions: {type: "sap.ui.core.Control", multiple: true} }, events : { /** * The Post event is triggered when the user has entered a value and pressed the post button. After firing this event, the value is reset. */ post : { parameters : { /** * The value of the feed input before reseting it. */ value : {type : "string"} } } } }, renderer: FeedInputRenderer }); /* * 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 }, // rules for the allowed tags ELEMENTS: { // Text Module Tags 'a': { cssClass: 'sapMLnk' }, 'abbr': 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 } }; /** * Holds the internal list of allowed and evaluated HTML elements and attributes * @private */ FeedInput.prototype._renderingRules = _defaultRenderingRules; /** * 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 attr, value, addTarget = tagName === "a"; // add UI5 specific classes when appropriate var cssClass = (this._renderingRules.ELEMENTS[tagName] && this._renderingRules.ELEMENTS[tagName].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]) { attribs[i + 1] = null; continue; } // sanitize hrefs if (attr == "href") { // a::href if (!URLListValidator.validate(value)) { 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) { return fnSanitizeAttribs.call(this, tagName, attribs); } /** * Sanitizes HTML tags and attributes according to a given policy. * * @param {string} sText The HTML to sanitize * @return {string} The sanitized HTML * @private */ FeedInput.prototype._sanitizeHTML = function (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; } } }); }; /////////////////////////////////// Lifecycle ///////////////////////////////////////////////////////// /** * Overrides sap.ui.core.Element.init */ FeedInput.prototype.init = function () { // override text defaults var oBundle = CoreLib.getResourceBundleFor("sap.m"); this.setProperty("placeholder", oBundle.getText("FEEDINPUT_PLACEHOLDER"), true); this.setProperty("buttonTooltip", oBundle.getText("FEEDINPUT_SUBMIT"), true); }; /** * Overrides sap.ui.core.Element.exit */ FeedInput.prototype.exit = function () { if (this._oTextArea) { this._oTextArea.destroy(); } if (this._oButton) { this._oButton.destroy(); } if (this.oAvatar) { this.oAvatar.destroy(); } }; /////////////////////////////////// Properties ///////////////////////////////////////////////////////// FeedInput.prototype.setRows = function (iRows) { var iMaxLines = this.getProperty("growingMaxLines"); if (iRows > MAX_ROWS) { iRows = MAX_ROWS; } else if (iRows < MIN_ROWS) { iRows = MIN_ROWS; } if (iRows > iMaxLines && iMaxLines !== 0) { this.setProperty("growingMaxLines", iRows, true); this._getTextArea().setGrowingMaxLines(iRows); } this.setProperty("rows", iRows, true); this._getTextArea().setRows(iRows); return this; }; FeedInput.prototype.setShowExceededText = function (bValue) { this.setProperty("showExceededText", bValue, true); this._getTextArea().setShowExceededText(bValue); return this; }; FeedInput.prototype.setMaxLength = function (iMaxLength) { this.setProperty("maxLength", iMaxLength, true); this._getTextArea().setMaxLength(iMaxLength); return this; }; FeedInput.prototype.setGrowing = function (bGrowing) { this.setProperty("growing", bGrowing, true); this._getTextArea().setGrowing(bGrowing); return this; }; FeedInput.prototype.setGrowingMaxLines = function (iMaxLines) { var iRows = this.getProperty("rows"); if (iMaxLines !== UNLIMITED_ROWS) { if (iMaxLines < iRows) { iMaxLines = iRows; } else if (iMaxLines > MAX_ROWS) { iMaxLines = MAX_ROWS; } } this.setProperty("growingMaxLines", iMaxLines, true); this._getTextArea().setGrowingMaxLines(iMaxLines); return this; }; FeedInput.prototype.setValue = function (sValue) { this.setProperty("value", sValue, true); this._getTextArea().setValue(sValue); this.enablePostButton(); return this; }; FeedInput.prototype.setPlaceholder = function (sValue) { this.setProperty("placeholder", sValue, true); this._getTextArea().setPlaceholder(sValue); return this; }; FeedInput.prototype.setEnabled = function (bEnabled) { this.setProperty("enabled", bEnabled, true); //Dynamically adding or removing the css if (this.getDomRef("outerContainer")) { if (bEnabled) { this.getDomRef("outerContainer").classList.remove("sapMFeedInDisabled"); } else { this.getDomRef("outerContainer").classList.add("sapMFeedInDisabled"); } } this._getTextArea().setEnabled(bEnabled); this.enablePostButton(); return this; }; FeedInput.prototype.setButtonTooltip = function (vButtonTooltip) { this.setProperty("buttonTooltip", vButtonTooltip, true); this._getPostButton().setTooltip(vButtonTooltip); return this; }; /////////////////////////////////// Private ///////////////////////////////////////////////////////// /** * Access and initialization for the text area * @returns {sap.m.TextArea} The text area */ FeedInput.prototype._getTextArea = function () { var that = this; if (!this._oTextArea) { this._oTextArea = new TextArea(this.getId() + "-textArea", { value : this.getValue(), maxLength : this.getMaxLength(), placeholder : this.getPlaceholder(), growing : this.getGrowing(), growingMaxLines : this.getGrowingMaxLines(), showExceededText : this.getShowExceededText(), rows : this.getRows(), liveChange : jQuery.proxy(function (oEvt) { var sValue = oEvt.getParameter("value"); this.setProperty("value", sValue, true); // update myself without re-rendering this.enablePostButton(); }, this) }); this._oTextArea.setParent(this); this._oTextArea.addEventDelegate({ onAfterRendering: function () { that.$("counterContainer").empty(); that.$("counterContainer").html(that._oTextArea.getAggregation("_counter").$()); } }); } return this._oTextArea; }; /** * Access and initialization for the button * @returns {sap.m.Button} The button */ FeedInput.prototype._getPostButton = function () { if (!this._oButton) { this._oButton = new Button(this.getId() + "-button", { enabled : false, type : ButtonType.Default, icon : "sap-icon://paper-plane", tooltip : this.getButtonTooltip(), press : jQuery.proxy(function () { this._oTextArea.focus(); this.firePost({ value : this._sanitizeHTML(this.getValue()) }); this.setValue(null); }, this) }); this._oButton.setParent(this); } return this._oButton; }; /** * Enables or disables the post button. * If a boolean parameter is provided, the post button's enabled state is set directly to that value. * @param {boolean} [bEnabled] - Sets the enabled state of the post button. */ FeedInput.prototype.enablePostButton = function(bEnabled) { var oButton = this._getPostButton(); // If bEnabled is a boolean, set the button's enabled state directly // Otherwise, check the control's validity to determine the button's enabled state if (typeof bEnabled === "boolean") { this._bEnablePostButton = bEnabled; oButton.setEnabled(bEnabled); // If the button is disabled and the current value is not empty, enable the button // This ensures that the button is enabled when the value is set but the button is disabled if (bEnabled == false && this.getValue()) { oButton.setEnabled(!bEnabled); } } else { var bPostButtonEnabled = this._isControlEnabled(); oButton.setEnabled(bPostButtonEnabled); } }; /** * Checks if the control is valid based on the enablePostButton flag and the value. * @param {boolean} bEnablePostButton * @returns {boolean} */ FeedInput.prototype._isControlEnabled = function() { var sValue = this.getValue(); if (this._bEnablePostButton) { // If bEnablePostButton is true, always return true regardless of value return true; } else { // If bEnablePostButton is false, only return true if there is a value return (typeof sValue === "string" || sValue instanceof String) && sValue.trim().length > 0; } }; /** * Returns the action button if it is a sap.m.Button, otherwise logs an error and returns null. * @returns {sap.m.Button|null} */ FeedInput.prototype._getActionButtonIfValid = function () { var oActionButton = this.getActions()[0]; // Get the first action button, if any if (oActionButton && oActionButton.isA("sap.m.Button")) { var sText = oActionButton.getText(); var sIcon = oActionButton.getIcon(); if (sIcon && (!sText || sText.trim() === "")) { return oActionButton; } else { Log.error("FeedInput: The actionButton should only have an icon and no text."); } } else if (oActionButton) { Log.error("FeedInput: The provided actionButton aggregation is not a sap.m.Button instance."); } return; }; /** * Lazy load feed icon image. * * @private * @returns {sap.m.Avatar} The Avatar control */ FeedInput.prototype._getAvatar = function() { var sIconSrc = this.getIcon(); var sId = this.getId() + '-icon'; this.oAvatar = this.getAggregation("_avatar"); if (!this.oAvatar) { this.oAvatar = new Avatar({ id: sId, src: sIconSrc, displayShape: this.getIconDisplayShape(), initials: this.getIconInitials(), displaySize: this.getIconSize() }).addStyleClass("sapMFeedInImage"); if (sIconSrc) { this.oAvatar.addStyleClass("sapMFeedInImageBgColor"); } } else { this.oAvatar .setSrc(sIconSrc) .setDisplayShape(this.getIconDisplayShape()) .setInitials(this.getIconInitials()) .setDisplaySize(this.getIconSize()); } this.setAggregation("_avatar", this.oAvatar); return this.oAvatar; }; return FeedInput; });