UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

421 lines (381 loc) 17.5 kB
/* eslint-disable no-loop-func */ /*! * 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(["./PluginBase", "sap/base/Log", "sap/ui/core/Core", "sap/base/strings/formatMessage", "sap/m/OverflowToolbarButton"], function(PluginBase, Log, Core, formatTemplate, OverflowToolbarButton) { "use strict"; /** * 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/}. * * @extends sap.ui.core.Element * @author SAP SE * @version 1.117.4 * * @public * @since 1.110 * @alias sap.m.plugins.CopyProvider */ var 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. * * @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 * @returns {*|Array.<*>|undefined|null} The cell data to be copied or array of cell data to be split into different cells in the clipboard. <code>undefined</code> or <code>null</code> to exclude the cell from copying. * @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 values 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). */ 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 } }, 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 isCellValueCopyable(vCellValue) { return vCellValue != null; } function stringifyForSpreadSheet(vCellValue) { if (isCellValueCopyable(vCellValue)) { var sCellValue = String(vCellValue); return /\n|\r|\t/.test(sCellValue) ? '"' + sCellValue.replaceAll('"', '""') + '"' : sCellValue; } else { return ""; } } function copyMatrixForSpreadSheet(aMatrix) { var sClipboardText = aMatrix.map(function(aRows) { return aRows.map(stringifyForSpreadSheet).join("\t"); }).join("\n"); return navigator.clipboard.writeText(sClipboardText); } CopyProvider.prototype._shouldManageExtractData = function() { var oControl = this.getControl(); var oParent = this.getParent(); return (oControl !== oParent && oParent.indexOfDependent(this) == -1); }; CopyProvider.prototype.isApplicable = function() { if (!navigator.clipboard) { throw new Error(this + " requires a secure context in order to access the clipboard API."); } 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 }; oControl.addEventDelegate(this._oDelegate, this); this._oCopyButton && this._oCopyButton.setEnabled(true); this._shouldManageExtractData() && this.setExtractData(this._extractData.bind(this)); }; CopyProvider.prototype.onDeactivate = function(oControl) { oControl.removeEventDelegate(this._oDelegate, this); this._oDelegate = null; this._oCopyButton && this._oCopyButton.setEnabled(false); this._shouldManageExtractData() && this.setExtractData(); }; CopyProvider.prototype.setVisible = function(bVisible) { this.setProperty("visible", bVisible, true); this._oCopyButton && this._oCopyButton.setVisible(this.getVisible()); return this; }; CopyProvider.prototype.setParent = function() { PluginBase.prototype.setParent.apply(this, arguments); if (!this.getParent() && this._oCopyButton) { this._oCopyButton.destroy(true); this._oCopyButton = null; } }; /** * Creates and returns a Copy button that can be used to trigger a copy action, for example, from the table toolbar. * * @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) { this._oCopyButton = new OverflowToolbarButton(Object.assign({ icon: "sap-icon://copy", visible: this.getVisible(), tooltip: Core.getLibraryResourceBundle("sap.m").getText("COPYPROVIDER_COPY"), press: this.copySelectionData.bind(this, true) }, mSettings)); } return this._oCopyButton; }; CopyProvider.prototype.exit = function() { if (this._oCopyButton) { this._oCopyButton.destroy(true); this._oCopyButton = null; } if (this._mColumnClipboardSettings) { this._mColumnClipboardSettings = null; } }; /** * 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>. * * @returns {Array.<Array.<*>>} Two-dimensional extracted data from the selection. * @public */ CopyProvider.prototype.getSelectionData = function() { var oControl = this.getControl(); var fnExtractData = this.getExtractData(); if (!oControl || !fnExtractData || !navigator.clipboard) { return []; } var aSelectableColumns = this.getConfig("selectableColumns", oControl); if (!aSelectableColumns.length) { return []; } if (oControl.getParent().isA("sap.ui.mdc.Table")) { aSelectableColumns = aSelectableColumns.map(function(oSelectableColumn) { return Core.byId(oSelectableColumn.getId().replace(/\-innerColumn$/, "")); }); } var aSelectionData = []; var aAllSelectedRowContexts = []; var bCopySparse = this.getCopySparse(); var fnExludeContext = this.getExcludeContext(); var oCellSelectorPlugin = PluginBase.getPlugin(oControl, "sap.m.plugins.CellSelector"); var mCellSelectionRange = oCellSelectorPlugin && oCellSelectorPlugin.getSelectionRange(); var aCellSelectorRowContexts = mCellSelectionRange ? oCellSelectorPlugin.getSelectedRowContexts() : []; var bCellSelectorRowContextsMustBeMerged = Boolean(aCellSelectorRowContexts.length); var bSelectedRowContextsMustBeSparse = bCellSelectorRowContextsMustBeMerged || bCopySparse; var aSelectedRowContexts = this.getConfig("selectedContexts", oControl, bSelectedRowContextsMustBeSparse); if (bCellSelectorRowContextsMustBeMerged) { aCellSelectorRowContexts = Array(mCellSelectionRange.from.rowIndex).concat(aCellSelectorRowContexts); Object.assign(aAllSelectedRowContexts, aSelectedRowContexts, aCellSelectorRowContexts); } else { aAllSelectedRowContexts = aSelectedRowContexts; } for (var iContextIndex = 0; iContextIndex < aAllSelectedRowContexts.length; iContextIndex++) { var oRowContext = aAllSelectedRowContexts[iContextIndex]; if (!oRowContext) { if (bCopySparse && aSelectionData.length) { aSelectionData.push(Array(aSelectionData[0].length)); } } else if (fnExludeContext && fnExludeContext(oRowContext)) { continue; } else { var aRowData = []; var bContextFromSelectedRows = (oRowContext == aSelectedRowContexts[iContextIndex]); aSelectableColumns.forEach(function(oColumn, iColumnIndex) { if (bContextFromSelectedRows || (iColumnIndex >= mCellSelectionRange.from.colIndex && iColumnIndex <= mCellSelectionRange.to.colIndex)) { var vCellData = fnExtractData(oRowContext, oColumn); if (isCellValueCopyable(vCellData)) { aRowData.push[Array.isArray(vCellData) ? "apply" : "call"](aRowData, vCellData); } } else if (aSelectedRowContexts.length) { aRowData.push(undefined); } }); if (bCopySparse || aRowData.some(isCellValueCopyable)) { aSelectionData.push(aRowData); } } } return aSelectionData; }; /** * 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. * * @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 * @public */ CopyProvider.prototype.copySelectionData = function(bFireCopyEvent) { var aSelectionData = this.getSelectionData(); if (!aSelectionData.length || bFireCopyEvent && !this.fireCopy({ data: aSelectionData }, true)) { return Promise.resolve(); } return copyMatrixForSpreadSheet(aSelectionData); }; CopyProvider.prototype.onkeydown = function(oEvent) { if (oEvent.isMarked() || oEvent.code != "KeyC" || !(oEvent.ctrlKey || oEvent.metaKey) || !oEvent.target.matches(this.getConfig("allowForCopySelector"))) { return; } oEvent.setMarked(); oEvent.preventDefault(); this.copySelectionData(true); }; CopyProvider.prototype._extractData = function(oRowContext, oColumn) { if (!this._mColumnClipboardSettings) { this._mColumnClipboardSettings = new WeakMap(); } var mColumnClipboardSettings = this._mColumnClipboardSettings.get(oColumn); if (mColumnClipboardSettings === undefined) { mColumnClipboardSettings = this.getParent().getColumnClipboardSettings(oColumn); this._mColumnClipboardSettings.set(oColumn, mColumnClipboardSettings); } if (!mColumnClipboardSettings) { return; } var aPropertyValues = mColumnClipboardSettings.properties.map(function(sProperty, iIndex) { var vPropertyValue = oRowContext.getProperty(sProperty); var oType = mColumnClipboardSettings.types[iIndex]; if (oType) { try { vPropertyValue = oType.formatValue(vPropertyValue, "string"); } catch (oError) { Log.error(this + ': Formatting error during copy "' + oError.message + '"'); } } return isCellValueCopyable(vPropertyValue) ? vPropertyValue : ""; }); var fnUnitFormatter = mColumnClipboardSettings.unitFormatter; if (fnUnitFormatter) { aPropertyValues[0] = fnUnitFormatter(aPropertyValues[0], aPropertyValues[1]); } var sExtractValue = formatTemplate(mColumnClipboardSettings.template, aPropertyValues).trim(); return sExtractValue; }; /** * Plugin-specific control configurations. */ PluginBase.setConfigs({ "sap.m.Table": { allowForCopySelector: ".sapMLIBFocusable,.sapMLIBSelectM,.sapMLIBSelectS", selectedContexts: function(oTable, bSparse) { var aSelectedContexts = []; var oBindingInfo = oTable.getBindingInfo("items"); oTable.getItems(true).forEach(function(oItem, iIndex) { if (oItem.isSelectable() && oItem.getVisible()) { if (oItem.getSelected()) { var oContextOrItem = oBindingInfo ? oItem.getBindingContext(oBindingInfo.model) : oItem; var iSparseOrDenseIndex = bSparse ? iIndex : aSelectedContexts.length; aSelectedContexts[iSparseOrDenseIndex] = oContextOrItem; } } }); return aSelectedContexts; }, selectableColumns: function(oTable) { return oTable.getRenderedColumns(); } }, "sap.ui.table.Table": { allowForCopySelector: ".sapUiTableCell", selectedContexts: function(oTable, bSparse) { var oSelectionOwner = PluginBase.getPlugin(oTable, "sap.ui.table.plugins.SelectionPlugin") || oTable; if (oSelectionOwner.getSelectedContexts) { return oSelectionOwner.getSelectedContexts(); } var aSelectedContexts = []; oSelectionOwner.getSelectedIndices().forEach(function(iSelectedIndex) { var oContext = oTable.getContextByIndex(iSelectedIndex); if (oContext) { var iSparseOrDenseIndex = bSparse ? iSelectedIndex : aSelectedContexts.length; aSelectedContexts[iSparseOrDenseIndex] = oContext; } }); return aSelectedContexts; }, selectableColumns: function(oTable) { return oTable.getColumns().filter(function(oColumn) { return oColumn.getDomRef(); }); } } }, 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; });