@openui5/sap.m
Version:
OpenUI5 UI Library sap.m
453 lines (377 loc) • 12.6 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 control sap.m.ExpandableText
sap.ui.define([
'./library',
'sap/ui/core/Control',
"sap/ui/core/Lib",
'sap/ui/core/library',
'sap/ui/core/InvisibleText',
'sap/ui/Device',
'sap/ui/base/ManagedObject',
'sap/m/Link',
'sap/m/Text',
'sap/m/Button',
'sap/m/ResponsivePopover',
'sap/m/HyphenationSupport',
'./ExpandableTextRenderer'
],
function(library,
Control,
Library,
coreLibrary,
InvisibleText,
Device,
ManagedObject,
Link,
Text,
Button,
ResponsivePopover,
HyphenationSupport,
ExpandableTextRenderer) {
"use strict";
var oRb = Library.getResourceBundleFor("sap.m");
var TEXT_SHOW_MORE = oRb.getText("EXPANDABLE_TEXT_SHOW_MORE");
var TEXT_SHOW_LESS = oRb.getText("EXPANDABLE_TEXT_SHOW_LESS");
var CLOSE_TEXT = oRb.getText("MSGBOX_CLOSE");
// shortcut for sap.ui.core.TextAlign
var TextAlign = coreLibrary.TextAlign;
// shortcut for sap.ui.core.TextDirection
var TextDirection = coreLibrary.TextDirection;
// shortcut for sap.ui.core.aria.HasPopup
var AriaHasPopup = coreLibrary.aria.HasPopup;
// shortcut for sap.m.WrappingType
var WrappingType = library.WrappingType;
// shortcut for sap.m.PlacementType
var PlacementType = library.PlacementType;
// shortcut for sap.m.ExpandableTextOverflowMode
var ExpandableTextOverflowMode = library.ExpandableTextOverflowMode;
// shortcut for sap.m.EmptyIndicatorMode
var EmptyIndicatorMode = library.EmptyIndicatorMode;
// shortcut for sap.m.LinkAccessibleRole
var LinkAccessibleRole = library.LinkAccessibleRole;
function reduceWhitespace(sText) {
return sText.replace(/ {2,}/g, ' ').replace(/\t{2,}/g, ' ');
}
/**
* Constructor for a new ExpandableText.
*
* @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 <code>ExpandableText</code> control can be used to display long texts
* inside a table, list or form.
*
* <h3>Overview</h3>
* Only the first characters from the text field are shown initially and a "More" link which allows
* the full text to be displayed. The <code>overflowMode</code> property determines
* if the full text will be displayed expanded in place (default) or in a popover.
* If the text is expanded a "Less" link is displayed, which allows collapsing the text field.
*
* <h3>Usage</h3>
*
* <i>When to use</i>
* <ul>
* <li>You specifically have to deal with long texts/descriptions.</li>
* </ul>
*
* <i>When not to use</i>
* <ul>
* <li>Do not use long texts and descriptions
* if you can provide short and meaningful alternatives.</li>
* <li>The content is critical for the user.
* In this case use short descriptions that can fit in.</li>
* </ul>
*
* @extends sap.ui.core.Control
* @implements sap.ui.core.IFormContent, sap.m.IHyphenation, sap.ui.core.ILabelable
*
* @author SAP SE
* @version 1.146.0
*
* @constructor
* @public
* @since 1.87
* @alias sap.m.ExpandableText
*/
var ExpandableText = Control.extend("sap.m.ExpandableText", /** @lends sap.m.ExpandableText.prototype */ {
metadata: {
interfaces: [
"sap.ui.core.IFormContent",
"sap.m.IHyphenation",
"sap.ui.core.ILabelable"
],
library: "sap.m",
properties: {
/**
* Determines the text to be displayed.
*/
text: { type: "string", group: "Data", defaultValue: '', bindable: "bindable" },
/**
* Available options for the text direction are left-to-right (LTR) and right-to-left (RTL)
* By default the control inherits the text direction from its parent control.
*/
textDirection: { type: "sap.ui.core.TextDirection", group: "Appearance", defaultValue: TextDirection.Inherit },
/**
* Defines the type of text wrapping to be used (hyphenated or normal).
*/
wrappingType : {type: "sap.m.WrappingType", group : "Appearance", defaultValue : WrappingType.Normal},
/**
* Sets the horizontal alignment of the text.
*/
textAlign: { type: "sap.ui.core.TextAlign", group: "Appearance", defaultValue: TextAlign.Begin },
/**
* Specifies how whitespace and tabs inside the control are handled. If true, whitespace will be preserved by the browser.
*/
renderWhitespace: { type: "boolean", group: "Appearance", defaultValue: false },
/**
* Determines how the full text will be displayed - InPlace or Popover
*/
overflowMode: { type: "sap.m.ExpandableTextOverflowMode", group: "Appearance", defaultValue: ExpandableTextOverflowMode.InPlace },
/**
* Specifies the maximum number of characters from the beginning of the text field that are shown initially.
*/
maxCharacters: { type: "int", group: "Appearance", defaultValue: 100 },
/**
* Determines if the text is expanded.
* @private
*/
expanded: { type: "boolean", group: "Appearance", defaultValue: false, visibility: "hidden" },
/**
* Specifies if an empty indicator should be displayed when there is no text.
*
* @since 1.91
*/
emptyIndicatorMode: { type: "sap.m.EmptyIndicatorMode", group: "Appearance", defaultValue: EmptyIndicatorMode.Off }
},
aggregations: {
/**
* The "More" link.
* @private
*/
_showMoreLink: {type: 'sap.m.Link', multiple: false, visibility: "hidden"},
/**
* Screen Reader ariaLabelledBy
*/
_ariaLabelledBy: {type: "sap.ui.core.InvisibleText", multiple: false, visibility: "hidden"}
},
designtime: "sap/m/designtime/ExpandableText.designtime"
},
renderer: ExpandableTextRenderer
});
ExpandableText.prototype.init = function () {
this.setAggregation("_ariaLabelledBy", new InvisibleText());
};
ExpandableText.prototype.onBeforeRendering = function() {
this._updateAriaLabelledByText();
};
ExpandableText.prototype._onAfterLinkRendering = function() {
var oShowMoreLinkDomRef;
if (!this._isExpandable() ||
this.getOverflowMode() === ExpandableTextOverflowMode.Popover) {
return;
}
oShowMoreLinkDomRef = this._getShowMoreLink().getDomRef();
oShowMoreLinkDomRef.setAttribute("aria-expanded", this.getProperty("expanded"));
oShowMoreLinkDomRef.setAttribute("aria-controls", this.getId() + "-string");
};
/**
* Gets the text.
*
* @public
* @param {boolean} [bNormalize] Indication for normalized text.
* @returns {string} Text value.
*/
ExpandableText.prototype.getText = function (bNormalize) {
// returns the text value and normalize line-ending character for rendering
var sText = this.getProperty("text");
// handle line ending characters for renderer
if (bNormalize) {
return sText.replace(/\r\n|\n\r|\r/g, "\n");
}
return sText;
};
/**
* Returns the text node container's DOM reference.
* This can be different from <code>getDomRef</code> when inner wrapper is needed.
*
* @protected
* @returns {HTMLElement|null} DOM reference of the text.
*/
ExpandableText.prototype.getTextDomRef = function () {
if (!this.getVisible()) {
return null;
}
return this.getDomRef("string");
};
/**
* Returns if the control can be bound to a label
*
* @returns {boolean} <code>true</code> if the control can be bound to a label
* @public
*/
ExpandableText.prototype.hasLabelableHTMLElement = function () {
return false;
};
/**
* Returns if the text is expandable
*
* @returns {boolean} if the text is expandable
* @private
*/
ExpandableText.prototype._isExpandable = function () {
var sText = this.getText();
if (!this.getRenderWhitespace()) {
sText = reduceWhitespace(sText);
}
return sText.length > this._getMaxCharacters() + TEXT_SHOW_MORE.length;
};
/**
* Returns the maximum number of initially displayed characters.
*
* @private
*/
ExpandableText.prototype._getMaxCharacters = function () {
return Math.max(0, this.getMaxCharacters());
};
/**
* Returns the displayed text.
*
* @returns {string} the displayed text
* @private
*/
ExpandableText.prototype._getDisplayedText = function () {
var sText = this.getText(true);
if (this.getProperty("expanded") || !this._isExpandable()) {
return sText;
}
if (!this.getRenderWhitespace()) {
sText = reduceWhitespace(sText);
}
return sText.substring(0, this._getMaxCharacters());
};
ExpandableText.prototype._getShowMoreLink = function() {
var showMoreLink = this.getAggregation('_showMoreLink');
if (!showMoreLink) {
showMoreLink = new Link(this.getId() + '-showMoreLink', {
accessibleRole: LinkAccessibleRole.Button,
text: this.getProperty("expanded") ? TEXT_SHOW_LESS : TEXT_SHOW_MORE,
ariaLabelledBy: this.getAggregation("_ariaLabelledBy"),
press: function (oEvent) {
var oText,
bExpanded,
oPopover;
if (this.getOverflowMode() === ExpandableTextOverflowMode.InPlace) {
bExpanded = !this.getProperty("expanded");
showMoreLink.setText(bExpanded ? TEXT_SHOW_LESS : TEXT_SHOW_MORE);
this.setProperty("expanded", bExpanded);
} else {
oText = new Text({
text: ManagedObject.escapeSettingsValue(this.getText()),
textDirection: this.getTextDirection(),
wrappingType: this.getWrappingType(),
textAlign: this.getTextAlign(),
renderWhitespace: this.getRenderWhitespace()
}).addStyleClass("sapUiSmallMargin").addStyleClass("sapMExTextPopover");
oPopover = this._oPopover;
if (oPopover && oPopover.isOpen()) {
oPopover.close();
return;
}
if (!oPopover) {
oPopover = this._oPopover = new ResponsivePopover({
showHeader: false,
placement: PlacementType.HorizontalPreferredRight,
beforeClose: this._onPopoverBeforeClose.bind(this)
});
if (Device.system.phone) {
oPopover.setEndButton(new Button({
text: CLOSE_TEXT,
press: function () {
oPopover.close();
}
}));
}
this.addDependent(oPopover);
}
showMoreLink.setText(TEXT_SHOW_LESS);
oPopover.removeAllAriaLabelledBy();
oPopover.destroyContent();
oPopover.addAriaLabelledBy(oText);
oPopover.addContent(oText);
oPopover.openBy(oEvent.getSource());
this._updateAriaLabelledByText(true);
}
}.bind(this)
});
showMoreLink.addEventDelegate({
onAfterRendering: this._onAfterLinkRendering
}, this);
this.setAggregation("_showMoreLink", showMoreLink, true);
}
showMoreLink.setAriaHasPopup(this.getOverflowMode() === ExpandableTextOverflowMode.InPlace ? AriaHasPopup.None : AriaHasPopup.Dialog);
return showMoreLink;
};
ExpandableText.prototype._onPopoverBeforeClose = function () {
this._getShowMoreLink().setText(TEXT_SHOW_MORE);
this._updateAriaLabelledByText();
};
ExpandableText.prototype._updateAriaLabelledByText = function (bExpanded) {
var sAriaText = "";
bExpanded = bExpanded || this.getProperty("expanded");
if (this.getOverflowMode() === ExpandableTextOverflowMode.Popover) {
sAriaText = oRb.getText(bExpanded ? "EXPANDABLE_TEXT_SHOW_LESS_POPOVER_ARIA_LABEL" : "EXPANDABLE_TEXT_SHOW_MORE_POPOVER_ARIA_LABEL");
}
this.getAggregation("_ariaLabelledBy").setText(sAriaText);
};
/**
* Called when the control is destroyed.
*/
ExpandableText.prototype.exit = function () {
if (this._oPopover) {
this._oPopover.destroy();
this._oPopover = null;
}
};
/**
* Gets the accessibility information for the text.
*
* @protected
* @returns {sap.ui.core.AccessibilityInfo} Accessibility information for the text.
* @see sap.ui.core.Control#getAccessibilityInfo
*/
ExpandableText.prototype.getAccessibilityInfo = function () {
return {
description: this.getText()
};
};
/**
* Gets a map of texts which should be hyphenated.
*
* @private
* @returns {Object<string,string>} The texts to be hyphenated.
*/
ExpandableText.prototype.getTextsToBeHyphenated = function () {
return {
"main": this._getDisplayedText(true)
};
};
/**
* Gets the DOM refs where the hyphenated texts should be placed.
*
* @private
* @returns {map|null} The elements in which the hyphenated texts should be placed
*/
ExpandableText.prototype.getDomRefsForHyphenatedTexts = function () {
return {
"main": this.getTextDomRef()
};
};
// Add hyphenation to ExpandableText functionality
HyphenationSupport.mixInto(ExpandableText.prototype);
return ExpandableText;
});