UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

727 lines (652 loc) 31.2 kB
/* 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("&#x20;", "&nbsp;").replaceAll("&#xa;", "<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; });