@openui5/sap.m
Version:
OpenUI5 UI Library sap.m
470 lines (416 loc) • 16.7 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2009-2023 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
sap.ui.define([
"sap/m/library",
"sap/ui/core/Core",
"sap/ui/core/Theming",
"sap/ui/core/theming/Parameters",
"sap/m/IllustratedMessage",
"sap/m/Button",
"sap/ui/core/InvisibleMessage"
], function(MLibrary, Core, Theming, ThemeParameters, IllustratedMessage, Button, InvisibleMessage) {
"use strict";
/*global Intl*/
/**
* Provides utility functions for tables.
*
* @namespace
* @alias module:sap/m/table/Util
* @author SAP SE
* @version 1.117.4
* @since 1.96.0
* @private
* @ui5-restricted sap.fe, sap.ui.mdc, sap.ui.comp
*/
var Util = {};
// local privates
var sDefaultFont = "";
var fBaseFontSize = parseFloat(MLibrary.BaseFontSize);
var oSelectAllNotificationPopover = null;
var pGetSelectAllPopover = null;
/**
* Measures the given text width in a canvas and returns the value in rem.
*
* @param {string} sText The text to be measured
* @param {string} [sFont] The font. When not given, uses theme parameters: sapMFontMediumSize and sapUiFontFamily.
* @returns {float} The text width converted to rem
* @private
*/
Util.measureText = (function() {
var fEpsilon = 0.05;
var oCanvasContext = document.createElement("canvas").getContext("2d");
var fnSetDefaultFont = function() {
sDefaultFont = [
parseFloat(ThemeParameters.get({ name: "sapMFontMediumSize" }) || "0.875rem") * fBaseFontSize + "px",
ThemeParameters.get({ name: "sapUiFontFamily" }) || "Arial"
].join(" ");
return sDefaultFont;
};
Theming.attachApplied(fnSetDefaultFont);
return function(sText, sFont) {
oCanvasContext.font = sFont || sDefaultFont || fnSetDefaultFont();
return oCanvasContext.measureText(sText || "").width / fBaseFontSize + fEpsilon;
};
})();
/**
* Calculates the width of a given ODataType that is used in tables.
*
* @param {sap.ui.model.odata.type.ODataType} oType The ODataType instance
* @param {object} [mSettings] The settings object
* @param {int} [mSettings.maxWidth=19] The maximum content width of the field in rem
* @param {int} [mSettings.defaultWidth=8] The default column content width when type check fails
* @param {float} [mSettings.gap=0] The additional content width in rem
* @returns {float} The calculated width of the ODataType converted to rem
* @private
*/
Util.calcTypeWidth = (function() {
var fBooleanWidth = 0;
var aDateParameters = [2023, 9, 26, 22, 47, 58, 999];
var oUTCDate = new Date(Date.UTC.apply(0, aDateParameters));
var oLocalDate = new (Function.prototype.bind.apply(Date, [null].concat(aDateParameters)))();
var mNumericLimits = { Byte: 3, SByte: 3, Int16: 5, Int32: 9, Int64: 12, Single: 6, Float: 12, Double: 13, Decimal: 15, Integer: 9 };
Theming.attachApplied(function() { fBooleanWidth = 0; });
return function(oType, mSettings) {
var sType = oType.getMetadata().getName().split(".").pop();
var iMaxWidth = mSettings && mSettings.maxWidth || 19;
var iGap = mSettings && mSettings.gap || 0;
var applySettings = function(fCalculatedWidth) {
return Math.min(fCalculatedWidth + iGap, iMaxWidth);
};
if (sType == "Boolean") {
if (!fBooleanWidth) {
var oResourceBundle = Core.getLibraryResourceBundle("sap.ui.core");
var fYesWidth = Util.measureText(oResourceBundle.getText("YES"));
var fNoWidth = Util.measureText(oResourceBundle.getText("NO"));
fBooleanWidth = Math.max(fYesWidth, fNoWidth);
}
return applySettings(fBooleanWidth);
}
if (sType == "String" || oType.isA("sap.ui.model.odata.type.String")) {
var iMaxLength = parseInt(oType.getConstraints().maxLength) || 0;
if (!iMaxLength || iMaxLength * 0.25 > iMaxWidth) {
return iMaxWidth;
}
var fSampleWidth = Util.measureText("A".repeat(iMaxLength));
if (iMaxLength < iMaxWidth || iMaxWidth < 10) {
return applySettings(fSampleWidth);
}
var fWidth = Math.log(fSampleWidth - iMaxWidth * 0.16) / Math.log(iMaxWidth / 3) * (iMaxWidth / 2) * Math.pow(iMaxWidth / 19, 1 / fSampleWidth);
return applySettings(Math.min(fWidth, fSampleWidth));
}
if (sType.startsWith("Date") || sType.startsWith("Time")) {
var mFormatOptions = oType.getFormatOptions();
var oDate = mFormatOptions.UTC ? oUTCDate : oLocalDate;
var sSample = oDate.toLocaleDateString();
if (sType == "TimeOfDay") {
sSample = new Intl.DateTimeFormat("de", {hour: "numeric", minute: "numeric", second: "numeric"}).format(oDate);
sSample = oType.formatValue(sSample, "string");
} else if (oType.isA("sap.ui.model.odata.type.Time")) {
sSample = oType.formatValue({ __edmType: "Edm.Time", ms: oUTCDate.valueOf() }, "string");
} else {
sSample = oType.formatValue(mFormatOptions.interval ? [oDate, new Date(oDate * 1.009)] : oDate, "string");
((oType.oFormat && oType.oFormat.oFormatOptions && oType.oFormat.oFormatOptions.pattern) || "").replace(/[MELVec]{3,4}/, function(sWideFormat) {
sSample += (sWideFormat.length == 4 ? "---" : "-");
});
}
return applySettings(Util.measureText(sSample));
}
if (mNumericLimits[sType]) {
var iScale = parseInt(oType.getConstraints().scale) || 0;
var iPrecision = parseInt(oType.getConstraints().precision) || 20;
iPrecision = Math.min(iPrecision, mNumericLimits[sType]);
var sNumber = 2 * Math.pow(10, iPrecision - iScale - 1);
sNumber = oType.formatValue(sNumber, "string");
return applySettings(Util.measureText(sNumber));
}
return mSettings && mSettings.defaultWidth || 8;
};
})();
/**
* Calculates the width of the column header width according to calculated cell content and min/max width restrictions.
*
* @param {string} sHeader The header text
* @param {float} [fContentWidth] The calculated width of the cell in rem
* @param {int} [iMaxWidth=19] The maximum header width in rem
* @param {int} [iMinWidth=2] The minimum header width in rem
* @param {boolean} [bRequired=false] Determines whether the given column header is marked as required
* @returns {float} The calculated header width in rem
* @private
* @ui5-restricted sap.ui.comp
*/
Util.calcHeaderWidth = (function() {
var sHeaderFont = "";
var sRequiredFont = "";
var fnGetHeaderFont = function() {
if (!sHeaderFont) {
sHeaderFont = [ThemeParameters.get({ name: "sapUiColumnHeaderFontWeight" }) || "normal", sDefaultFont].join(" ");
}
return sHeaderFont;
};
var fnGetRequiredFont = function() {
if (!sRequiredFont) {
sRequiredFont = [ThemeParameters.get({ name: "sapMFontLargeSize" }) || "normal", sDefaultFont].join(" ");
}
return sRequiredFont;
};
Theming.attachApplied(function() {
sHeaderFont = "";
sRequiredFont = "";
});
return function(sHeader, fContentWidth, iMaxWidth, iMinWidth, bRequired) {
var iHeaderLength = sHeader.length;
var fRequired = 0;
iMaxWidth = iMaxWidth || 19;
iMinWidth = iMinWidth || 2;
if (fContentWidth > iMaxWidth) {
return iMaxWidth;
}
if (iMinWidth > iHeaderLength) {
return iMinWidth;
}
if (bRequired) {
fRequired = 0.125 /* margin */ + Util.measureText("*", fnGetRequiredFont());
}
if (!fContentWidth) {
var fHeaderWidth = Util.measureText(sHeader, fnGetHeaderFont());
return fHeaderWidth + fRequired;
}
fContentWidth = Math.max(fContentWidth, iMinWidth);
if (fContentWidth > iHeaderLength) {
return fContentWidth;
}
var fOrigHeaderWidth = Util.measureText(sHeader, fnGetHeaderFont());
fOrigHeaderWidth = Math.min(fOrigHeaderWidth, iMaxWidth * 0.7);
var fContentHeaderRatio = Math.max(1, 1 - (Math.log(Math.max(fContentWidth - 1.7, 0.2)) / Math.log(iMaxWidth * 0.5)) + 1);
var fMaxHeaderWidth = fContentHeaderRatio * fContentWidth;
var fHeaderWidthDiff = Math.max(0, fOrigHeaderWidth - fMaxHeaderWidth);
var fHeaderWidth = (fHeaderWidthDiff < 0.15) ? fOrigHeaderWidth : fMaxHeaderWidth + fHeaderWidthDiff * (1 - 1 / fContentWidth) / Math.E;
return fHeaderWidth + fRequired;
};
})();
/**
* Calculates the width of a table column.
*
* @param {sap.ui.model.odata.type.ODataType[]} vTypes The ODataType instances
* @param {string} [sHeader] The header of the column
* @param {object} [mSettings] The settings object
* @param {int} [mSettings.minWidth=2] The minimum content width of the field in rem
* @param {int} [mSettings.maxWidth=19] The maximum content width of the field in rem
* @param {int} [mSettings.padding=1.0625] The sum of column padding(1rem) and border(1px) in rem
* @param {float} [mSettings.gap=0] The additional content width in rem
* @param {boolean} [mSettings.headerGap=false] Whether icons in the header should be taken into account
* @param {boolean} [mSettings.truncateLabel=true] Whether the header of the column can be truncated or not
* @param {boolean} [mSettings.verticalArrangement=false] Whether the fields are arranged vertically
* @param {boolean} [mSettings.required=false] Indicates the state of the column header as defined by the <code>required</code> property
* @param {int} [mSettings.defaultWidth=8] The default column content width when type check fails
* @param {int} [mSettings.treeColumn=false] Determines whether the first column of the tree table
* @returns {string} The calculated width of the column
* @private
* @ui5-restricted sap.fe, sap.ui.mdc, sap.ui.comp
* @since 1.101
*/
Util.calcColumnWidth = function(vTypes, sHeader, mSettings) {
if (!Array.isArray(vTypes)) {
vTypes = [vTypes];
}
mSettings = Object.assign({
minWidth: 2,
maxWidth: 19,
defaultWidth: 8,
truncateLabel: true,
padding: 1.0625,
gap: 0
}, mSettings);
var fHeaderWidth = 0;
var iMinWidth = Math.max(1, mSettings.minWidth);
var iMaxWidth = Math.max(iMinWidth, mSettings.maxWidth);
var fTreeColumnGap = mSettings.treeColumn ? 3 : 0;
var fContentWidth = mSettings.gap + fTreeColumnGap + vTypes.reduce(function(fInnerWidth, vType) {
var oType = vType, oTypeSettings = {
defaultWidth: mSettings.defaultWidth,
maxWidth: mSettings.maxWidth
};
if (Array.isArray(vType)) {
// for internal usage (mdc/Table) every field can provide own width settings
// in this case we get [<TypeInstance>, <TypeSettings>][] instead of <TypeInstance>[]
oType = vType[0];
oTypeSettings = vType[1] || oTypeSettings;
}
var fTypeWidth = Util.calcTypeWidth(oType, oTypeSettings);
return mSettings.verticalArrangement ? Math.max(fInnerWidth, fTypeWidth) : fInnerWidth + fTypeWidth + (fInnerWidth && 0.5);
}, 0);
if (sHeader) {
fHeaderWidth = Util.calcHeaderWidth(sHeader, (mSettings.truncateLabel ? fContentWidth : 0), iMaxWidth, iMinWidth, mSettings.required);
fHeaderWidth += mSettings.headerGap ? (8 /* padding */ + 14 /* icon width */) / fBaseFontSize : 0;
}
fContentWidth = Math.max(iMinWidth, fContentWidth, fHeaderWidth);
fContentWidth = Math.min(fContentWidth, iMaxWidth);
fContentWidth = Math.ceil(fContentWidth * 100) / 100;
return fContentWidth + mSettings.padding + "rem";
};
/**
* Returns an instance of <code>sap.m.IllustratedMessage</code> in case there are no visible columns in the table.
*
* @returns {sap.m.IllustratedMessage} The message to be displayed when the table has no visible columns
* @param {function} [fnAddColumn] The handler to be executed when the additional add column button is pressed
* @private
*/
Util.getNoColumnsIllustratedMessage = function(fnAddColumn) {
var oResourceBundle = Core.getLibraryResourceBundle("sap.m");
var oIllustratedMessage = new IllustratedMessage({
illustrationType: MLibrary.IllustratedMessageType.AddColumn,
title: oResourceBundle.getText("TABLE_NO_COLUMNS_TITLE"),
description: oResourceBundle.getText("TABLE_NO_COLUMNS_DESCRIPTION")
});
if (fnAddColumn) {
var oAddButton = new Button({
icon: "sap-icon://action-settings",
press: fnAddColumn
});
oIllustratedMessage.addAdditionalContent(oAddButton);
}
return oIllustratedMessage;
};
/**
* Creates or returns the existing popover
*
* @returns {Promise} A promise that resolves when the popover is created
* @private
* @since 1.109
*/
Util.getSelectAllPopover = function() {
if (pGetSelectAllPopover) {
return pGetSelectAllPopover;
}
pGetSelectAllPopover = Promise.all(
[new Promise(function(fnResolve) {
sap.ui.require([
"sap/m/Popover",
"sap/m/Bar",
"sap/m/HBox",
"sap/m/Title",
"sap/ui/core/library",
"sap/m/Text"
], function(Popover, Bar, HBox, Title, coreLib, Text) {
fnResolve({
Popover: Popover,
Bar: Bar,
HBox: HBox,
Title: Title,
coreLib: coreLib,
Text: Text
});
});
}),
Core.getLibraryResourceBundle('sap.m', true)
]).then(function(aResult) {
var oModules = aResult[0];
var oResourceBundle = aResult[1];
var sIconColor = oModules.coreLib.IconColor.Critical,
sTitleLevel = oModules.coreLib.TitleLevel.H2;
oSelectAllNotificationPopover = new oModules.Popover({
customHeader: new oModules.Bar({
contentMiddle: [
new oModules.HBox({
items: [
new oModules.coreLib.Icon({src: "sap-icon://message-warning", color: sIconColor})
.addStyleClass("sapUiTinyMarginEnd"),
new oModules.Title({text: oResourceBundle.getText("TABLE_SELECT_LIMIT_TITLE"), level: sTitleLevel})
],
renderType: "Bare",
justifyContent: "Center",
alignItems: "Center"
})
]
}),
content: [new oModules.Text()]
}).addStyleClass("sapUiContentPadding");
return {
oSelectAllNotificationPopover: oSelectAllNotificationPopover,
oResourceBundle: oResourceBundle
};
});
return pGetSelectAllPopover;
};
/**
* Opens the Popover and sets the appropriate message.
*
* @param {int} iLimit The selectable items length
* @param {object} oSelectAllDomRef Control object where the popOver is opened
* @private
* @since 1.109
*/
Util.showSelectionLimitPopover = function(iLimit, oSelectAllDomRef) {
Util.getSelectAllPopover().then(function(oResult) {
var oPopover = oResult.oSelectAllNotificationPopover;
var oResourceBundle = oResult.oResourceBundle;
var sMessage = oResourceBundle.getText("TABLE_SELECT_LIMIT", [iLimit]);
oPopover.getContent()[0].setText(sMessage); //Content contains a single text element
if (oSelectAllDomRef) {
oPopover.openBy(oSelectAllDomRef);
}
});
};
/**
* Closes the popover if opened
*
* @private
* @since 1.109
*/
Util.hideSelectionLimitPopover = function() {
if (oSelectAllNotificationPopover && oSelectAllNotificationPopover.isOpen()) {
oSelectAllNotificationPopover.close();
}
};
/**
* Provides an additional announcement for the screen reader to inform the user that the table
* has been updated.
*
* @param {string} sText The header text to be announced
* @param {int|undefined} iRowCount The row count. If not provided, the row count will not be announced
* @private
* @ui5-restricted sap.fe, sap.ui.mdc, sap.ui.comp
* @since 1.114
*/
Util.announceTableUpdate = function(sText, iRowCount) {
var oInvisibleMessage = InvisibleMessage.getInstance();
if (oInvisibleMessage) {
var oResourceBundle = Core.getLibraryResourceBundle("sap.m");
if (iRowCount == undefined) {
oInvisibleMessage.announce(oResourceBundle.getText("table.ANNOUNCEMENT_TABLE_UPDATED", [sText]));
} else if (iRowCount > 1) {
oInvisibleMessage.announce(oResourceBundle.getText("table.ANNOUNCEMENT_TABLE_UPDATED_MULT", [sText, iRowCount]));
} else if (iRowCount === 1) {
oInvisibleMessage.announce(oResourceBundle.getText("table.ANNOUNCEMENT_TABLE_UPDATED_SING", [sText, iRowCount]));
} else {
oInvisibleMessage.announce(oResourceBundle.getText("table.ANNOUNCEMENT_TABLE_UPDATED_NOITEMS", [sText]));
}
}
};
/**
* Checks whether the table is empty.
*
* @param {sap.ui.model.Binding} oRowBinding The row binding
* @returns {boolean} Whether the table is empty or not
* @private
* @ui5-restricted sap.ui.mdc, sap.ui.comp
* @since 1.114
*/
Util.isEmpty = function(oRowBinding) {
if (!oRowBinding) {
return true;
}
var iRowCount = oRowBinding.getLength();
if (iRowCount === 1 && oRowBinding.isA('sap.ui.model.analytics.AnalyticalBinding')) {
var bHasGrandTotal = oRowBinding ? oRowBinding.providesGrandTotal() && oRowBinding.hasTotaledMeasures() : false;
if (bHasGrandTotal) {
iRowCount = 0;
}
}
return iRowCount <= 0;
};
return Util;
});