@openui5/sap.m
Version:
OpenUI5 UI Library sap.m
727 lines (652 loc) • 31.2 kB
JavaScript
/* eslint-disable no-loop-func */
/*!
* 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(["./PluginBase", "sap/base/Log", "sap/base/strings/formatMessage", "sap/base/security/encodeXML", "sap/m/OverflowToolbarButton", "../library", "sap/ui/core/Element", "sap/ui/core/Lib", "sap/ui/Device"], function(PluginBase, Log, formatTemplate, encodeXML, OverflowToolbarButton, mLibrary, Element, coreLib, Device) {
"use strict";
const CopyPreference = mLibrary.plugins.CopyPreference;
/**
* Constructor for a new CopyProvider plugin that can be used to copy table rows to the clipboard.
*
* @param {string} [sId] ID for the new <code>CopyProvider</code>, generated automatically if no ID is given
* @param {object} [mSettings] Initial settings for the <code>CopyProvider</code>
*
* @class
* Provides copy to clipboard capabilities for the selected rows of the table.
* This plugin can copy individual cells if the {@link sap.m.plugins.CellSelector CellSelector} plugin is also enabled for that table.<br>
*
* <b>Note:</b> If a <code>sap.ui.table.Table</code> control is used together with server-side models, the {@link sap.ui.table.plugins.MultiSelectionPlugin MultiSelectionPlugin}
* must be set for that table to make this plugin work property with range selections. See also the {@link sap.ui.table.plugins.MultiSelectionPlugin#getLimit limit} property of
* the <code>MultiSelectionPlugin</code>.<br>
* <b>Note:</b> This plugin requires a secure origin, either HTTPS or localhost, in order to access the browser's clipboard API.
* For more information, see {@link https://w3c.github.io/webappsec-secure-contexts/}.
* It is recommended to check whether the application executes in a secure context before adding the <code>CopyProvider</code> plugin and
* related functionality, such as the {@link #sap.m.plugins.CellSelector}.
*
* @example <caption>Check secure context</caption>
* <pre>
* if (window.isSecureContext) {
* oTable.addDependent(new CopyProvider());
* }
* </pre>
*
* @extends sap.ui.core.Element
* @author SAP SE
* @version 1.146.0
*
* @public
* @since 1.110
* @alias sap.m.plugins.CopyProvider
* @borrows sap.m.plugins.PluginBase.findOn as findOn
*/
const CopyProvider = PluginBase.extend("sap.m.plugins.CopyProvider", /** @lends sap.m.plugins.CopyProvider.prototype */ { metadata: {
library: "sap.m",
properties: {
/**
* Callback function to extract the cell data that is copied to the clipboard.
* <ul>
* <li>If an array is returned, then each array value will be copied as a separate cell into the clipboard.</li>
* <li>If <code>undefined</code> or <code>null</code> is returned, then the cell will be excluded from copying.</li>
* <li>If an object is returned, then it must have the following properties:
* <ul>
* <li><code>text</code>: (mandatory) The cell data to be copied to the clipboard as <code>text/plain</code> MIME type.</li>
* <li><code>html</code>: (optional) The cell data to be copied to the clipboard as <code>text/html</code> MIME type.</li>
* </ul>
* </li>
* </ul>
*
* <b>Note:</b> The <code>CopyProvider</code> uses the <code>text/html</code> MIME type to display the merged cell data shown in a UI5 table as a single cell in the clipboard. This allows users
* in applications supporting <code>text/html</code> MIME type, such as <code>Spreadsheet</code>, to preserve the cell data format that appears in a UI5 table.
* The <code>CopyProvider</code> also uses the <code>text/plain</code> MIME type to display the merged cell data shown in a UI5 table as separate clipboard cells. This allows users
* to edit plain data with applications like <code>SpreadSheet</code>, then copy and paste the data back into a UI5 table, preserving data integrity without in-cell formatting.<br>
* Spreadsheet-like applications supporting <code>text/html</code> MIME type typically prioritize <code>text/html</code> clipboard data during paste. This means that
* the data format copied from a UI5 table is preserved with the default paste operation. Users wanting to make edits can access the individual and unformatted cell data in the clipboard,
* which is stored in the text/plain MIME type, by selecting the "Paste Special" option and then choosing "Unicode Text" in spreadsheet applications.<br>
*
* <b>Note:</b> Using <code>text/html</code> MIME type as a clipboard item might not be supported on all platforms. In such cases, the <code>CopyProvider</code> writes only <code>text/plain</code> data
* to the clipboard. Refer to the <code>bIncludeHtmlMimeType</code> parameter and do not return the object type if this value is <code>false</code>.<br>
*
* <b>Note:</b> Even if the user is on a platform supporting <code>text/html</code> MIME type as a clipboard item, currently, any HTML tags are not allowed; all data is encoded.
*
* @callback sap.m.plugins.CopyProvider.extractDataHandler
* @param {sap.ui.model.Context|sap.m.ColumnListItem} oContextOrRow The binding context of the selected row or the row instance if there is no binding
* @param {sap.m.Column|sap.ui.table.Column|sap.ui.mdc.table.Column} oColumn The related column instance of selected cells
* @param {boolean} bIncludeHtmlMimeType Indicates whether writing <code>text/html</code> MIME type to the clipboard is supported
* @returns {*|{text: *, html: *}|Array.<*>|undefined|null} The cell data to be copied to the clipboard
* @public
*/
/**
* Defines a {@link sap.m.plugins.CopyProvider.extractDataHandler callback function} that gets called for each selected cell to extract the cell data that is copied to the clipboard.
*
* The callback function gets called with the binding context of the selected row and the column instance parameters.<br>
* For the <code>sap.ui.table.Table</code> control, the row context parameter can also be the context of an unselectable row in case of a range selection, for example the context of grouping or sub-total row.<br>
* For the <code>sap.m.Table</code> control, if the <code>items</code> aggregation of the table is not bound then the callback function gets called with the row instance instead of the binding context.<br>
* The callback function must return the cell data that is then stringified and copied to the clipboard.<br>
* If an array is returned from the callback function, then each array value will be copied as a separate cell into the clipboard.<br>
* If a column should not be copied to the clipboard, then the callback function must return <code>undefined</code> or <code>null</code> for each cell of the same column.<br>
* <br>
* <b>Note:</b> This property is mandatory to make the <code>CopyProvider</code> plugin work, and it must be set in the constructor.
*/
extractData: { type: "function", invalidate: false },
/**
* Determines whether unselected rows that are located between the selected rows are copied to the clipboard as an empty row.
*
* This can be useful for maintaining the original structure of the data when it is pasted into a new location (e.g. spreadsheets).
*
* <b>Note:</b> Sparse copying must not be enabled in combination with <code>sap.ui.table.plugins.ODataV4MultiSelection</code> or the
* <code>sap.ui.mdc.Table</code> with the <code>sap.ui.mdc.odata.v4.TableDelegate</code>.
*/
copySparse: { type: "boolean", defaultValue: false, invalidate: false },
/**
* Callback function to exclude certain contexts from being copied to the clipboard.
*
* @callback sap.m.plugins.CopyProvider.excludeContextHandler
* @param {sap.ui.model.Context|sap.m.ColumnListItem} oContextOrRow The binding context of the selected row or the row instance if there is no binding
* @returns {boolean} <code>true</code> to exclude the context, <code>false</code> otherwise
* @public
*/
/**
* Defines a {@link sap.m.plugins.CopyProvider.excludeContextHandler callback function} which gets called to exclude certain contexts from being copied to the clipboard.
*
* This callback function gets called with the binding context or the row instance if there is no binding.
* Return <code>true</code> to exclude the context, <code>false</code> otherwise.
*/
excludeContext: { type: "function", invalidate: false },
/**
* Defines the visibility of the Copy button created with the {@link #getCopyButton} API.
*
* @since 1.114
*/
visible: { type: "boolean", defaultValue: true, invalidate: false },
/**
* This property determines the copy preference when performing a copy operation.
*
* If the property is set to <code>Full</code>, all selected content is copied. This includes selected rows and cells.
*
* If the property is set to <code>Cells</code>, cell selection takes precedence during copying. If cells are selected along
* with rows, only the cell selection is copied.
* If no cells are selected, the row selection is copied.
*
* @since 1.119
*/
copyPreference: { type: "sap.m.plugins.CopyPreference", defaultValue: CopyPreference.Cells, invalidate: false }
},
events: {
/**
* This event is fired if there is a selection, and the user triggers the copy action.
*
* This can be done with the standard paste keyboard shortcut when the focus is on a selected row or cell. Also the {@link #copySelectionData} API can be called,
* for example, from the press handler of a copy button in a table toolbar to start the copy action synthetically, which might cause this event to be fired.
* To avoid writing the selection to the clipboard, call <code>preventDefault</code> on the event instance.
*/
copy: {
allowPreventDefault: true,
parameters: {
/**
* Two-dimensional mutable array of selection data to be copied to the clipboard.
* The first dimension represents the selected rows, and the second dimension represents the cells of the selected rows.
*/
data: { type: "any[][]" }
}
}
}
}});
function isHtmlMimeTypeAllowed() {
return Boolean(Device.system.desktop && window.ClipboardItem && navigator.clipboard?.write);
}
function isCellDataCopyable(vCellData) {
return vCellData != null;
}
function pushCellDataTo(vCellData, aArray) {
if (isCellDataCopyable(vCellData)) {
aArray.push(...[].concat(vCellData));
}
}
function stringifyForHtmlMimeType(vCellData) {
if (!isCellDataCopyable(vCellData)) {
return "";
}
const sCellData = String(vCellData).replaceAll("\r\n", "\n").replaceAll("\t", " ");
return encodeXML(sCellData).replaceAll(" ", " ").replaceAll("
", "<br>");
}
function stringifyForTextMimeType(vCellData) {
if (!isCellDataCopyable(vCellData)) {
return "";
}
const sCellData = String(vCellData);
return /\n|\r|\t/.test(sCellData) ? '"' + sCellData.replaceAll('"', '""') + '"' : sCellData;
}
CopyProvider.findOn = PluginBase.findOn;
CopyProvider.prototype._shouldManageExtractData = function() {
const oControl = this.getControl();
const oParent = this.getParent();
return (oControl !== oParent && oParent.indexOfDependent(this) == -1);
};
CopyProvider.prototype.isApplicable = function() {
if (this._shouldManageExtractData()){
if (this.getExtractData()) {
throw new Error("extractData property must not be defined for " + this);
}
if (!this.getParent().getColumnClipboardSettings) {
throw new Error("getColumnClipboardSettings method must be defined for " + this.getParent());
}
} else if (!this.getExtractData()) {
throw new Error("extractData property must be defined for " + this);
}
return true;
};
CopyProvider.prototype.onActivate = function(oControl) {
this._oDelegate = { onkeydown: this.onkeydown, onBeforeRendering: this.onBeforeRendering };
oControl.addEventDelegate(this._oDelegate, this);
this._shouldManageExtractData() && this.setExtractData(this._extractData.bind(this));
this._handleCellSelectorSelectionChange();
this._handleControlSelectionChange();
this._updateCopyButtonVisibility();
this._updateCopyButtonEnabled();
};
CopyProvider.prototype.onDeactivate = function(oControl) {
oControl.removeEventDelegate(this._oDelegate, this);
this._oDelegate = null;
this._shouldManageExtractData() && this.setExtractData();
this._handleCellSelectorSelectionChange();
this._handleControlSelectionChange();
this._updateCopyButtonEnabled();
};
CopyProvider.prototype.setVisible = function(bVisible) {
this.setProperty("visible", bVisible, true);
this._updateCopyButtonVisibility();
return this;
};
CopyProvider.prototype.setParent = function() {
PluginBase.prototype.setParent.apply(this, arguments);
if (!this.getParent()) {
this._destroyCopyButton();
}
};
CopyProvider.prototype.exit = function() {
PluginBase.prototype.exit.call(this);
this._mColumnClipboardSettings = null;
this._destroyCopyButton();
};
/**
* Creates and returns a Copy button that can be used to trigger a copy action, for example, from the table toolbar.
*
* <b>Note:</b> The <code>visible</code> and <code>enabled</code> properties of the Copy button must be managed
* through this plugin's own <code>visible</code> and <code>enabled</code> properties.
*
* @param {object} [mSettings] The settings of the button control
* @returns {sap.m.OverflowToolbarButton} The button instance
* @since 1.114
* @public
*/
CopyProvider.prototype.getCopyButton = function(mSettings) {
if (!this._oCopyButton) {
const oBundle = coreLib.getResourceBundleFor("sap.m");
const sText = oBundle.getText("COPYPROVIDER_COPY");
this._oCopyButton = new OverflowToolbarButton({
icon: "sap-icon://copy",
enabled: this._getEffectiveEnabled(),
visible: this._getEffectiveVisible(),
text: sText,
tooltip: sText,
press: this.copySelectionData.bind(this, true),
...mSettings
});
sap.ui.require(["sap/ui/core/ShortcutHintsMixin"], (ShortcutHintsMixin) => {
if (this._oCopyButton) { // Button might be destroyed in the meantime, esp. in tests
ShortcutHintsMixin.addConfig(this._oCopyButton, {
message: oBundle.getText(Device.os.macintosh ? "COPYPROVIDER_SHORTCUT_MAC" : "COPYPROVIDER_SHORTCUT_WIN")
}, this.getParent());
}
});
}
return this._oCopyButton;
};
/**
* Returns the extracted selection data as a two-dimensional array. This includes individual cell selections
* if the {@link sap.m.plugins.CellSelector CellSelector} plugin is also enabled for the table.
* <b>Note: </b> The returned array might be a sparse array if the {@link #getCopySparse copySparse} property is <code>true</code>.
*
* @param {boolean} bIncludeHtmlMimeType Determines whether the selection data to be returned includes <code>text/html</code> MIME type values, if the platform supports <code>text/html</code> MIME type as a clipboard item
* @returns {Array.<Array.<*>>|{text: Array.<Array.<*>>, html: Array.<Array.<*>>}} Two-dimensional data extracted from the selection, or an object with <code>text</code> and <code>html</code> keys, each with two-dimensional data extracted from the selection if <code>bIncludeHtmlMimeType</code> parameter is <code>true</code> and the platform supports <code>text/html</code> MIME type as a clipboard item.
* @public
*/
CopyProvider.prototype.getSelectionData = function(bIncludeHtmlMimeType = false) {
const oControl = this.getControl();
const fnExtractData = this.getExtractData();
if (!oControl || !fnExtractData) {
return [];
}
let aSelectableColumns = this.getConfig("selectableColumns", oControl);
if (!aSelectableColumns.length) {
return [];
}
if (oControl.getParent().isA("sap.ui.mdc.Table")) {
aSelectableColumns = aSelectableColumns.map(function(oSelectableColumn) {
return Element.getElementById(oSelectableColumn.getId().replace(/\-innerColumn$/, ""));
});
}
let aSelectedRowContexts = [];
const aAllSelectedRowContexts = [];
const bCopySparse = this.getCopySparse();
const fnExludeContext = this.getExcludeContext();
const oCellSelectorPlugin = PluginBase.getPlugin(this.getParent(), "sap.m.plugins.CellSelector") ?? PluginBase.getPlugin(oControl, "sap.m.plugins.CellSelector");
const mCellSelectionRange = oCellSelectorPlugin && oCellSelectorPlugin.getSelectionRange();
const aCellSelectorRowContexts = mCellSelectionRange ? oCellSelectorPlugin.getSelectedRowContexts() : [];
const bCellSelectorRowContextsMustBeMerged = Boolean(aCellSelectorRowContexts.length);
const bSelectedRowContextsMustBeSparse = bCellSelectorRowContextsMustBeMerged || bCopySparse;
this._iSelectedRows = 0;
this._iSelectedCells = 0;
if (this.getCopyPreference() == CopyPreference.Full || !bCellSelectorRowContextsMustBeMerged) {
aSelectedRowContexts = this.getConfig("selectedContexts", oControl, bSelectedRowContextsMustBeSparse);
Object.assign(aAllSelectedRowContexts, aSelectedRowContexts);
this._iSelectedRows = aSelectedRowContexts.filter(Boolean).length;
}
if (bCellSelectorRowContextsMustBeMerged) {
Object.assign(aAllSelectedRowContexts, Array(mCellSelectionRange.from.rowIndex).concat(aCellSelectorRowContexts));
this._iSelectedCells = aCellSelectorRowContexts.length * (Math.abs(mCellSelectionRange.to.colIndex - mCellSelectionRange.from.colIndex) + 1);
}
const aHtmlSelectionData = [];
const aTextSelectionData = [];
let bHtmlMimeTypeProvided = false;
if (bIncludeHtmlMimeType && !isHtmlMimeTypeAllowed()) {
bIncludeHtmlMimeType = false;
}
for (let iContextIndex = 0; iContextIndex < aAllSelectedRowContexts.length; iContextIndex++) {
const oRowContext = aAllSelectedRowContexts[iContextIndex];
if (!oRowContext) {
if (bCopySparse) {
if (aTextSelectionData.length) {
aTextSelectionData.push(Array(aTextSelectionData[0].length));
}
if (bHtmlMimeTypeProvided && aHtmlSelectionData.length) {
aHtmlSelectionData.push(Array(aHtmlSelectionData[0].length));
}
}
} else if (fnExludeContext && fnExludeContext(oRowContext)) {
continue;
} else {
const aHtmlRowData = [];
const aTextRowData = [];
const bContextFromSelectedRows = (oRowContext == aSelectedRowContexts[iContextIndex]);
aSelectableColumns.forEach((oColumn, iColumnIndex) => {
if (bContextFromSelectedRows || (iColumnIndex >= mCellSelectionRange?.from.colIndex && iColumnIndex <= mCellSelectionRange?.to.colIndex)) {
const vCellData = fnExtractData(oRowContext, oColumn, bIncludeHtmlMimeType);
if (!isCellDataCopyable(vCellData)) {
return;
}
if (bIncludeHtmlMimeType && vCellData.hasOwnProperty("html")) {
bHtmlMimeTypeProvided = true;
pushCellDataTo(vCellData.html, aHtmlRowData);
}
if (bHtmlMimeTypeProvided && vCellData.hasOwnProperty("text")) {
pushCellDataTo(vCellData.text, aTextRowData);
} else {
pushCellDataTo(vCellData, aTextRowData);
}
} else if (aSelectedRowContexts.length) {
aTextRowData.push(undefined);
aHtmlRowData.push(undefined);
}
});
if (bHtmlMimeTypeProvided && (bCopySparse || aHtmlRowData.some(isCellDataCopyable))) {
aHtmlSelectionData.push(aHtmlRowData);
}
if (bCopySparse || aTextRowData.some(isCellDataCopyable)) {
aTextSelectionData.push(aTextRowData);
}
}
}
return (bHtmlMimeTypeProvided) ? {
text: aTextSelectionData,
html: aHtmlSelectionData
} : aTextSelectionData;
};
/**
* Writes the selection data to the system clipboard and returns a <code>Promise</code> which resolves once the clipboard's content has been updated.
*
* <b>Note:</b> The user has to interact with the page or a UI element when this API gets called.
* <b>Note:</b> This plugin requires a secure context in order to access the browser's clipboard API.
* @param {boolean} [bFireCopyEvent=false] Whether the <code>copy</code> event should be triggered or not
* @returns {Promise} A <code>Promise</code> that is resolved after the selection data has been written to the clipboard
* @throws {Error} If the <code>CopyProvider</code> is used in a non-secure context.
* @public
*/
CopyProvider.prototype.copySelectionData = function(bFireCopyEvent) {
const vSelectionData = this.getSelectionData(true);
const aTextSelectionData = vSelectionData.text || vSelectionData;
if (!aTextSelectionData.length || bFireCopyEvent && !this.fireCopy({data: aTextSelectionData}, true)) {
return Promise.resolve();
}
if (!navigator.clipboard) {
throw new Error(this + " requires a secure context in order to access the clipboard API.");
}
const aHtmlSelectionData = vSelectionData.html || [];
const sClipboardText = aTextSelectionData.map((aRows) => {
return aRows.map(stringifyForTextMimeType).join("\t");
}).join("\n");
if (!aHtmlSelectionData.length) {
return navigator.clipboard.writeText(sClipboardText).then(() => {
this._notifyUser();
});
}
const sHtmlMimeType = "text/html";
const sTextMimeType = "text/plain";
const sClipboardHtml = "<table><tr>" + aHtmlSelectionData.map((aRows) => {
return "<td>" + aRows.map(stringifyForHtmlMimeType).join("</td><td>") + "</td>";
}).join("</tr><tr>") + "</tr></table>";
const oClipboardItem = new ClipboardItem({
[sTextMimeType]: new Blob([sClipboardText], {type: sTextMimeType}),
[sHtmlMimeType]: new Blob([sClipboardHtml], {type: sHtmlMimeType})
});
return navigator.clipboard.write([oClipboardItem]).then(() => {
this._notifyUser();
});
};
/**
* This hook gets called by the CellSelector when the selectable state is changed.
*
* @param {sap.m.plugins.CellSelector} oCellSelector The CellSelector instance
* @private
* @ui5-restricted sap.m.plugins.CellSelector
*/
CopyProvider.prototype.onCellSelectorSelectableChange = function(oCellSelector) {
this._handleCellSelectorSelectionChange(oCellSelector);
this._updateCopyButtonVisibility();
};
CopyProvider.prototype.onBeforeRendering = function() {
this._handleControlSelectionChange();
this._updateCopyButtonVisibility();
};
CopyProvider.prototype.onkeydown = function(oEvent) {
if (oEvent.isMarked() ||
oEvent.code != "KeyC" ||
!(oEvent.ctrlKey || oEvent.metaKey) ||
!oEvent.target.matches(this.getConfig("allowForCopySelector")) ||
!this._isControlSelectable()) {
return;
}
const oSelection = window.getSelection();
if (oSelection.toString() && oSelection.containsNode(oEvent.target, true)) {
return;
}
oEvent.setMarked();
oEvent.preventDefault();
this.copySelectionData(true);
};
CopyProvider.prototype._handleControlSelectionChange = function() {
const oControl = this.getControl();
this.getConfig("detachSelectionChange", oControl, this._updateCopyButtonEnabled, this);
if (this.isActive() && this.getConfig("isSelectable", oControl)) {
this.getConfig("attachSelectionChange", oControl, this._updateCopyButtonEnabled, this);
}
};
CopyProvider.prototype._handleCellSelectorSelectionChange = function(oCellSelector) {
oCellSelector ??= this.getPlugin("sap.m.plugins.CellSelector");
if (!oCellSelector) {
return;
}
oCellSelector.detachEvent("selectionChange", this._updateCopyButtonEnabled, this);
if (this.isActive() && oCellSelector.isSelectable()) {
oCellSelector.attachEvent("selectionChange", this._updateCopyButtonEnabled, this);
}
};
CopyProvider.prototype._isControlSelectable = function() {
return Boolean(
this.getConfig("isSelectable", this.getControl()) ||
this.getPlugin("sap.m.plugins.CellSelector")?.isSelectable()
);
};
CopyProvider.prototype._hasControlSelection = function() {
return Boolean(
this.getConfig("hasSelection", this.getControl()) ||
this.getPlugin("sap.m.plugins.CellSelector")?.hasSelection()
);
};
CopyProvider.prototype._destroyCopyButton = function() {
if (this._oCopyButton) {
this._oCopyButton.destroy();
this._oCopyButton = null;
}
};
CopyProvider.prototype._getEffectiveVisible = function() {
return this.getVisible() ? this._isControlSelectable() : false;
};
CopyProvider.prototype._updateCopyButtonVisibility = function() {
this._oCopyButton?.setVisible(this._getEffectiveVisible());
};
CopyProvider.prototype._getEffectiveEnabled = function() {
return this.isActive() ? this._hasControlSelection() : false;
};
CopyProvider.prototype._updateCopyButtonEnabled = function() {
this._oCopyButton?.setEnabled(this._getEffectiveEnabled());
};
CopyProvider.prototype._extractData = function(oRowContext, oColumn, bIncludeHtmlMimeType) {
if (!this._mColumnClipboardSettings) {
this._mColumnClipboardSettings = new WeakMap();
}
let mColumnClipboardSettings = this._mColumnClipboardSettings.get(oColumn);
if (mColumnClipboardSettings === undefined) {
mColumnClipboardSettings = this.getParent().getColumnClipboardSettings(oColumn);
this._mColumnClipboardSettings.set(oColumn, mColumnClipboardSettings);
}
if (!mColumnClipboardSettings) {
return;
}
const aPropertyValues = mColumnClipboardSettings.properties.map(function(sProperty, iIndex) {
let vPropertyValue = oRowContext.getProperty(sProperty);
const oType = mColumnClipboardSettings.types[iIndex];
if (oType) {
try {
vPropertyValue = oType.formatValue(vPropertyValue, "string");
} catch (oError) {
Log.error(this + ': Formatting error during copy "' + oError.message + '"');
}
}
return isCellDataCopyable(vPropertyValue) ? vPropertyValue : "";
});
const fnUnitFormatter = mColumnClipboardSettings.unitFormatter;
if (fnUnitFormatter) {
aPropertyValues[0] = fnUnitFormatter(aPropertyValues[0], aPropertyValues[1]);
}
if (!bIncludeHtmlMimeType) {
return aPropertyValues;
}
let sExtractValue = aPropertyValues.some(String) ? formatTemplate(mColumnClipboardSettings.template, aPropertyValues).trim() : "";
if (sExtractValue[0] == "(" && /^\([0-9]+\)$/.test(sExtractValue)) {
// Spreadsheets format "(123)" as "-123" for this specific case we remove parenthesis
sExtractValue = sExtractValue.slice(1, -1);
}
return {
text: aPropertyValues,
html: sExtractValue
};
};
/**
* Shows the user a notification message about the result of the copy action.
*
* @returns {Promise}
* @private
*/
CopyProvider.prototype._notifyUser = function() {
const iRows = this._iSelectedRows;
const iCells = this._iSelectedCells;
const bPreferCells = this.getCopyPreference() === "Cells";
return new Promise((resolve) => {
sap.ui.require(["sap/m/MessageToast"], (MessageToast) => {
let sBundleKey;
if (iRows && !iCells) {
sBundleKey = (iRows == 1) ? "ROW_SINGLE" : "ROW_MULTI";
} else if (iCells && (!iRows || bPreferCells)) {
sBundleKey = (iCells == 1) ? "CELL_SINGLE" : "CELL_MULTI";
} else if (iRows > 0 && iCells > 0) {
sBundleKey = "ROW_AND_CELL";
}
if (sBundleKey) {
const oBundle = coreLib.getResourceBundleFor("sap.m");
const sMessage = oBundle.getText("COPYPROVIDER_SELECT_" + sBundleKey + "_MSG");
MessageToast.show(sMessage);
}
resolve();
});
});
};
/**
* Plugin-specific control configurations.
*/
PluginBase.setConfigs({
"sap.m.Table": {
_oWM: new WeakMap(),
allowForCopySelector: ".sapMLIBFocusable,.sapMLIBSelectM,.sapMLIBSelectS,.sapMListTblCell",
selectedContexts: function(oTable, bSparse) {
const aSelectedContexts = [];
const oBindingInfo = oTable.getBindingInfo("items");
oTable.getItems(true).forEach(function(oItem, iIndex) {
if (oItem.isSelectable() && oItem.getVisible()) {
if (oItem.getSelected()) {
const oContextOrItem = oBindingInfo ? oItem.getBindingContext(oBindingInfo.model) : oItem;
const iSparseOrDenseIndex = bSparse ? iIndex : aSelectedContexts.length;
aSelectedContexts[iSparseOrDenseIndex] = oContextOrItem;
}
}
});
return aSelectedContexts;
},
selectableColumns: function(oTable) {
return oTable.getRenderedColumns();
},
isSelectable: function(oTable) {
return oTable.getMode().includes("Select");
},
hasSelection: function(oTable) {
return Boolean(oTable.getSelectedItem());
},
attachSelectionChange: function(oTable, fnHandler, oListener) {
// removal of the selected item might cause a selection change
const oDelegate = { onBeforeRendering: fnHandler };
this._oWM.set(oTable, oDelegate);
oTable.addEventDelegate(oDelegate, oListener);
// the binding update might cause a selection change
oTable.attachUpdateFinished(fnHandler, oListener);
oTable.attachEvent("itemSelectedChange", fnHandler, oListener);
},
detachSelectionChange: function(oTable, fnHandler, oListener) {
const oDelegate = this._oWM.get(oTable);
oTable.removeEventDelegate(oDelegate, oListener);
oTable.detachUpdateFinished(fnHandler, oListener);
oTable.detachEvent("itemSelectedChange", fnHandler, oListener);
}
},
"sap.ui.table.Table": {
allowForCopySelector: ".sapUiTableCell",
selectedContexts: function(oTable, bSparse) {
const oSelectionOwner = PluginBase.getPlugin(oTable, "sap.ui.table.plugins.SelectionPlugin") || oTable;
if (oSelectionOwner.getSelectedContexts) {
return oSelectionOwner.getSelectedContexts();
}
const aSelectedContexts = [];
oSelectionOwner.getSelectedIndices().forEach(function(iSelectedIndex) {
const oContext = oTable.getContextByIndex(iSelectedIndex);
if (oContext) {
const iSparseOrDenseIndex = bSparse ? iSelectedIndex : aSelectedContexts.length;
aSelectedContexts[iSparseOrDenseIndex] = oContext;
}
});
return aSelectedContexts;
},
selectableColumns: function(oTable) {
return oTable.getColumns().filter(function(oColumn) {
return oColumn.getDomRef();
});
},
isSelectable: function(oTable) {
return oTable.getSelectionMode() != "None";
},
hasSelection: function(oTable) {
return oTable._getSelectionPlugin().getSelectedCount() > 0;
},
attachSelectionChange: function(oTable, fnHandler, oListener) {
oTable._getSelectionPlugin().attachSelectionChange(fnHandler, oListener);
oTable.attachRowsUpdated(fnHandler, oListener);
},
detachSelectionChange: function(oTable, fnHandler, oListener) {
oTable._getSelectionPlugin()?.detachSelectionChange(fnHandler, oListener);
oTable.detachRowsUpdated(fnHandler, oListener);
}
}
}, CopyProvider);
/**
* Clipboard settings of a column to be used by the CopyProvider to extract the data.
*
* @typedef sap.m.plugins.CopyProvider.ColumnClipboardSettings
* @type {object}
* @property {string[]} properties Binding properties
* @property {sap.ui.model.Type[]} types Model type instances of properties
* @property {string} template Placeholders of properties
* @property {function} [unitFormatter] Unit formatter function
* @private
*/
return CopyProvider;
});