UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

1,308 lines (1,194 loc) 84.4 kB
/*! * 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/ui/core/Element", "sap/base/Log", "sap/ui/core/Lib", "sap/ui/unified/FileUploader", "sap/m/upload/UploaderHttpRequestMethod", "sap/m/upload/UploadItem", "sap/base/util/deepEqual", "sap/m/library", "sap/m/IllustratedMessageType", "sap/m/IllustratedMessage", "sap/m/IllustratedMessageSize", "sap/m/upload/UploaderTableItem", "sap/ui/core/dnd/DragDropInfo", "sap/ui/core/dnd/DropInfo", "sap/m/upload/FilePreviewDialog", "sap/ui/base/Event", "sap/m/Dialog", "sap/m/Label", "sap/m/Input", "sap/m/MessageBox", "sap/m/Button", "sap/ui/core/Core", "sap/m/Text", "sap/m/VBox", "sap/ui/model/BindingMode" ], function (PluginBase, Element, Log, Library1, FileUploader, UploaderHttpRequestMethod, UploadItem, deepEqual, Library, IllustratedMessageType, IllustratedMessage, IllustratedMessageSize, Uploader, DragDropInfo, DropInfo, FilePreviewDialog, EventBase, Dialog, Label, Input, MessageBox, Button, Core, TextField, VBox, BindingMode) { "use strict"; /** * Constructor for a new UploadSetwithTable plugin. * * @param {string} [sId] ID for the new <code>UploadSetwithTable</code>, generated automatically if no id is given * @param {object} [mSettings] Initial settings for the new <code>UploadSetwithTable</code> * * @class * The <code>UploadSetwithTable</code> plugin enables uploading within a table when it is added as a dependent to the table control. * <br> This plugin provides an Upload button to upload files from the local file system. To do so, the user of the plugin must place {@link sap.m.upload.ActionsPlaceholder Actions Placeholder} control at the desired table area and then associate the actions of the plugin with the placeholder. * <br> The plugin provides the ability to upload files from the local system as well as from cloud file picker and includes other notable features such as validation, file preview, download. * * <br> The following controls support this plugin: * <ul> * <li>{@link sap.ui.mdc.Table MDC Table}</li> * <li>{@link sap.m.Table Responsive Table}</li> * <li>{@link sap.ui.table.Table Grid Table}</li> * <li>{@link sap.ui.table.TreeTable Tree Table}</li> * </ul> * * <caption>Consider the following before using the plugin: </caption> * <ul> * <li>It gets activated when it is added as a dependent to the table control. It gets deactivated when it is removed from the table control or when the table control is destroyed.</li> * <li>It fires onActivated and onDeactivated events when it is activated and deactivated, respectively.</li> * <li>Configuring the rowConfiguration aggregation (type {@link sap.m.upload.UploadItemConfiguration UploadItemConfiguration}) of this plugin is mandatory to use the features such as file preview, download etc.</li> * <li>For the plugin to work with the tree table control, the isDirectoryPath property of the rowConfiguration aggregation must be set. This indicates if the context of the row is a directory or a file. It helps the plugin with the file preview feature.</li> * <li>It works only with the table control when the table is bound to the model to perform the operations such as rename, download etc.</li> * </ul> * * @example <caption>Connecting the plugin to table control</caption> * <pre> * oTable.addDependent(new UploadSetwithTable({ * uploadUrl: "uploadUrl", * fileTypes: ["jpg", "jpeg", "png"], * actions: "uploadButton" // Associating the actions with the ID of the ActionPlaceholder control in the table, where the upload button should be rendered. * })); * * // example of sap.m.upload.ActionsPlaceholder control placed in the table toolbar for rendering the upload button. * new ActionPlaceHolder({ * id: "uploadButton", // ID of the ActionPlaceholder control to be associated with the plugin actions. * placeholderFor: UploadSetwithTableActionPlaceHolder.UploadButtonPlaceholder * }); * </pre> * * @extends sap.ui.core.Element * @version 1.146.0 * @author SAP SE * @public * @since 1.124 * @alias sap.m.plugins.UploadSetwithTable * @borrows sap.m.plugins.PluginBase.findOn as findOn */ var UploadSetwithTable = PluginBase.extend("sap.m.plugins.UploadSetwithTable", /** @lends sap.m.plugins.UploadSetwithTable.prototype */ { metadata: { library: "sap.m", properties: { /** * File types that are allowed to be uploaded. * <br>If this property is not set, any file can be uploaded. */ fileTypes: {type: "string[]", defaultValue: null}, /** * Defined maximum length for a name of files that are to be uploaded. * <br>If set to <code>null</code> or <code>0</code>, any file can be uploaded regardless length of its name. */ maxFileNameLength: {type: "int", defaultValue: null}, /** * Defined size limit in megabytes for files that are to be uploaded. * <br>If set to <code>null</code> or <code>0</code>, files of any size can be uploaded. */ maxFileSize: {type: "float", defaultValue: null}, /** * Media types of files that are allowed to be uploaded. * <br>If this property is not set, any file can be uploaded. */ mediaTypes: {type: "string[]", defaultValue: null}, /** * Url where the uploaded files are stored. */ uploadUrl: {type: "string", defaultValue: null}, /** * HTTP request method chosen for file upload. */ httpRequestMethod: {type: "sap.m.upload.UploaderHttpRequestMethod", defaultValue: UploaderHttpRequestMethod.Post}, /** * Lets the user select multiple files from the same folder and then upload them. * * If multiple property is set to false, the plugin shows an error message if more than one file is chosen for drag & drop. */ multiple: {type: "boolean", group: "Behavior", defaultValue: false}, /** * If set to true, the button used for uploading files becomes invisible. */ uploadButtonInvisible: {type: "boolean", group: "Appearance", defaultValue: false}, /** * Defines whether the upload action is allowed. */ uploadEnabled: {type: "boolean", defaultValue: true}, /** Callback function to perform additional validations or configurations for the item queued up for upload and to finally trigger the upload. * @callback sap.m.plugins.UploadSetwithTable.itemValidationHandler * @param {sap.m.plugins.UploadSetwithTable.ItemInfo} oItemInfo The info of the item queued for upload. * @returns {Promise<sap.m.upload.UploadItem>} oPromise, once resolved the UploadSetWithTable plugin initiates the upload. * @public **/ /** * @typedef {object} sap.m.plugins.UploadSetwithTable.ItemInfo * @description Item info object sent as paramter to {@link sap.m.plugins.UploadSetwithTable.itemValidationHandler itemValidationHandler callback} * @property {sap.m.upload.UploadItem} oItem Current item queued for upload. * @property {number} iTotalItemsForUpload Total count of items queued for upload. * @property {sap.m.plugins.UploadSetwithTable} oSource Source on which the callback was invoked. * @public **/ /** * Defines a {@link sap.m.plugins.UploadSetwithTable.itemValidationHandler callback function} that is invoked when each UploadItem is queued up for upload. * This callback is invoked with {@link sap.m.plugins.UploadSetwithTable.ItemInfo parameters} and the callback is expected to return a promise to the plugin. Once the promise is resolved, the plugin initiates the upload process. * Configure this property only when any additional configuration or validations are to be performed before the upload of each item. * The upload process is triggered manually by resolving the promise returned to the plugin. **/ itemValidationHandler: {type: "function", defaultValue: null}, /** * Lets the user upload entire files from directories and sub directories. */ directory: {type: "boolean", group: "Behavior", defaultValue: false}, /** * Enables CloudFile picker feature to upload files from cloud. */ cloudFilePickerEnabled: { type: "boolean", group: "Behavior", defaultValue: false }, /** * Url of the FileShare OData V4 service supplied for CloudFile picker control. */ cloudFilePickerServiceUrl: { type: "sap.ui.core.URI", group: "Data", defaultValue: "" }, /** * The text of the CloudFile picker button. The default text is "Upload from cloud" (translated to the respective language). */ cloudFilePickerButtonText: { type: 'string', defaultValue: "" }, /** * @typedef {object} sap.m.plugins.UploadSetwithTable.FilenameValidationConfigMode * @description Key property of {@link sap.m.plugins.UploadSetwithTable.FilenameValidationConfig FileNameValidationConfig}. Used to determine the mode for file name validation. * @property {string} mode - The file name validation mode. The allowed values are 'include', 'exclude', or 'override'. * <br> <br> If the mode is 'include', the specified characters are added to the default restricted character set. * <br> If the mode is 'exclude', the specified characters are excluded from the default resrtricted character set. * <br> If the mode is 'override', the specified characters replace the entire default restricted character set. * <br> If the mode is not set, the default restricted file name character set is used. * @public * @since 1.136 */ /** * @typedef {object} sap.m.plugins.UploadSetwithTable.FilenameValidationConfig An object type that represents the file name validation configuration. * @description This property type is used to define the file name validation configuration. Object is passed to {@link sap.m.plugins.UploadSetwithTable fileNameValidationConfig property} * @property {sap.m.plugins.UploadSetwithTable.FilenameValidationConfigMode} mode The file name validation config mode. * @property {string} characters The file name validation configuration characters. * <br> <br> The default restricted filename character set is: \:/*?"<>|[]{}@#$ * @public * @since 1.136 **/ /** * File name validation configuration. * <br> Set this property to configure the file name validation characters and the validation mode. * <br> This configuration is used to validate the file name when a file is selected for renaming. * <br> For the plugin to pick up this configuration, mode and characters of the property must be set to validate the file name. * <br> see {@link sap.m.plugins.UploadSetwithTable.FilenameValidationConfigMode mode} to configure the file name validation mode. * <br> <br> The default restricted filename character set is: \:/*?"<>|[]{}@#$ * @type {sap.m.plugins.UploadSetwithTable.FilenameValidationConfig} * @public * @since 1.136 */ fileNameValidationConfig: { type: 'object', defaultValue: null } }, aggregations: { /** * Defines the uploader to be used. If not specified, the default implementation is used. */ uploader: {type: "sap.m.upload.UploaderTableItem", multiple: false}, /** * Header fields to be included in the header section of an XHR request. */ headerFields: {type: "sap.ui.core.Item", multiple: true, singularName: "headerField"}, /** * Row configuration information for each uploadItem in the model. */ rowConfiguration: {type: "sap.m.upload.UploadItemConfiguration", multiple: false}, /** * An illustrated message is displayed when no data is loaded. */ noDataIllustration: { type: "sap.m.IllustratedMessage", multiple: false } }, defaultAggregation: "rowConfiguration", associations: { /** * Dialog with a carousel to preview files uploaded. * <br>If it is not defined, the plugin creates and uses the instance of {@link sap.m.upload.FilePreviewDialog FilePreviewDialog}. */ previewDialog: {type: "sap.m.upload.FilePreviewDialog", multiple: false}, /** * Actions provided by the plugin. * <br> {@link sap.m.UploadSetwithTableActionPlaceHolder UploadSetwithTableActionPlaceHolder} enum is used to determine the action control to be rendered. * <br> Action buttons are rendered instead of the placeholder. * <br> For example, if the "placeholderFor" property is set to UploadButtonPlaceholder, the Upload button is rendered. * <br> Note: The action buttons are rendered only when the association to the placeholder control is set. */ actions: {type: "sap.m.upload.ActionsPlaceholder", multiple: true} }, events: { /** * The event is triggered when the file name is changed. */ itemRenamed: { parameters: { /** * The renamed UI element is of UploadItem type. */ item: {type: "sap.m.upload.UploadItem"} } }, /** * The event is triggered when the file renaming process is canceled. * @since 1.142 */ itemRenameCanceled: { parameters: { /** * The renamed UI element is of UploadItem type. */ item: {type: "sap.m.upload.UploadItem"} } }, /** * This event is fired right before the upload process begins. */ beforeUploadStarts: { parameters: { /** * The file whose upload is just about to start. */ item: {type: "sap.m.upload.UploadItem"} }, allowPreventDefault: true }, /** * This event is fired right after the upload process is finished. * <br>Based on the backend response of the application, listeners can use the parameters to determine if the upload was successful or if it failed. */ uploadCompleted: { parameters: { /** * The file whose upload has just been completed. */ item: {type: "sap.m.upload.UploadItem"}, /** * Response message that comes from the server. * * On the server side this response has to be put within the &quot;body&quot; tags of the response * document of the iFrame. It can consist of a return code and an optional message. This does not * work in cross-domain scenarios. */ response : {type : "string"}, /** * ReadyState of the XHR request. * * Required for receiving a <code>readyState</code> is to set the property <code>sendXHR</code> * to true. This property is not supported by Internet Explorer 9. */ readyState : {type : "string"}, /** * Status of the XHR request. * * Required for receiving a <code>status</code> is to set the property <code>sendXHR</code> to true. * This property is not supported by Internet Explorer 9. */ status : {type : "string"}, /** * Http-Response which comes from the server. * * Required for receiving <code>responseXML</code> is to set the property <code>sendXHR</code> to true. * * This property is not supported by Internet Explorer 9. */ responseXML : {type : "string"}, /** * Http-Response which comes from the server. * * Required for receiving <code>responseText</code> is to set the property <code>sendXHR</code> to true. * * This property is not supported by Internet Explorer 9. */ responseText : {type : "string"}, /** * Http-Response-Headers which come from the server. * * Provided as a JSON-map, i.e. each header-field is reflected by a property in the <code>headers</code> * object, with the property value reflecting the header-field's content. * * Required for receiving <code>headers</code> is to set the property <code>sendXHR</code> to true. * This property is not supported by Internet Explorer 9. */ headers : {type : "object"} } }, /** * This event is fired in either of the following cases: * <ul> * <li>When a file that is selected to be uploaded fails to meet the file type restriction * (<code>fileType</code> property).</li> * <li>When the file type restriction changes, and the file to be uploaded fails to meet the new * restriction.</li> * </ul> */ fileTypeMismatch: { parameters: { /** * The file that fails to meet the file type restriction specified in the * <code>fileType</code> property. */ item: {type: "sap.m.upload.UploadItem"} } }, /** * This event is fired in either of the following cases: * <ul> * <li>When a file that is selected to be uploaded fails to meet the file name length restriction specified in the * <code>maxFileNameLength</code> property.</li> * <li>When the file name length restriction changes, and the file to be uploaded fails to meet the new * restriction.</li> * </ul> */ fileNameLengthExceeded: { parameters: { /** * The file that fails to meet the file name length restriction specified in the * <code>maxFileNameLength</code> property. */ item: {type: "sap.m.upload.UploadItem"} } }, /** * This event is fired in either of the following cases: * <ul> * <li>When a file that is selected to be uploaded fails to meet the file size restriction specified in the * <code>maxFileSize</code> property.</li> * <li>When the file size restriction changes, and the file to be uploaded fails to meet the new * restriction.</li> * </ul> */ fileSizeExceeded: { parameters: { /** * The file that fails to meet the file size restriction specified in the * <code>maxFileSize</code> property. */ item: {type: "sap.m.upload.UploadItem"} } }, /** * This event is fired in either of the following cases: * <ul> * <li>When a file that is selected to be uploaded fails to meet the media type restriction specified in the * <code>mediaTypes</code> property.</li> * <li>When the media type restriction changes, and the file to be uploaded fails to meet the new * restriction.</li> * </ul> */ mediaTypeMismatch: { parameters: { /** * The file that fails to meet the media type restriction specified in the * <code>mediaTypes</code> property. */ item: {type: "sap.m.upload.UploadItem"} } }, /** * This event is fired just before initiating the file upload process when a file is selected to be uploaded. * Use this event to set additional info dynamically, specific for each item before upload process is initiated. */ beforeInitiatingItemUpload: { parameters: { /** * Items in ready state for upload process */ item: {type: "sap.m.upload.UploadItem"} } }, /** * This event is fired when plugin is activated. */ onActivated: { parameters: { /** * The activated plugin instance. */ oPlugin: {type: "sap.m.plugins.UploadSetwithTable"} } }, /** * This event is fired when plugin is deactivated. */ onDeactivated: { parameters: { /** * Control to which the plugin was connected. */ control: {type: "sap.ui.core.Control"} } } } } }); var UploadState = Library.UploadState; var UploadSetwithTableActionPlaceHolder = Library.UploadSetwithTableActionPlaceHolder; UploadSetwithTable.findOn = PluginBase.findOn; UploadSetwithTable.DEFAULT_INVALID_FILENAME_CHARACTERSET = "\\:/*?\"<>|[]{}@#$"; /** * Event Delegate that containts events, that need to be executed after control events. */ const EventDelegate = { onAfterRendering: function() { this.getConfig("setIsTableBound", this.getControl()); this.getConfig("setModelName", this.getControl()); } }; UploadSetwithTable.prototype.init = function () { this._oRb = Library1.getResourceBundleFor("sap.m"); setPluginBaseConfigs(); // Set PluginBase configurations }; UploadSetwithTable.prototype.onActivate = function (oControl) { this._filesTobeUploaded = []; this._filePreviewDialogControl = null; oControl.addDelegate(EventDelegate, false, this); this.getConfig("setPluginDefaultSettings", this.getControl(), this); this._setActions(); this.fireOnActivated({oPlugin: this}); }; UploadSetwithTable.prototype.onDeactivate = function (oControl) { this.getConfig("cleanupPluginInstanceSettings", oControl, this); this.fireOnDeactivated({control: oControl}); }; // Overriden Setter methods UploadSetwithTable.prototype.setFileTypes = function (aNewTypes) { var aTypes = aNewTypes || null; if (typeof aTypes === "string") { aTypes = aTypes.split(","); } aTypes = (aTypes || []).map(function (s) { return s ? s.toLowerCase() : ""; }); if (!deepEqual(this.getFileTypes(), aTypes)) { this.setProperty("fileTypes", aTypes, true); this.getDefaultFileUploader().setFileType(aTypes); } return this; }; UploadSetwithTable.prototype.setMaxFileNameLength = function (iNewMax) { if (this.getMaxFileNameLength() !== iNewMax) { this.setProperty("maxFileNameLength", iNewMax, true); this.getDefaultFileUploader().setMaximumFilenameLength(iNewMax); } return this; }; UploadSetwithTable.prototype.setMaxFileSize = function (iNewMax) { if (this.getMaxFileSize() !== iNewMax) { this.setProperty("maxFileSize", iNewMax, true); this.getDefaultFileUploader().setMaximumFileSize(iNewMax); } return this; }; UploadSetwithTable.prototype.setMediaTypes = function (aNewTypes) { var aTypes = aNewTypes || null; if (typeof aTypes === "string") { aTypes = aTypes.split(","); } aTypes = (aTypes || []).map(function (s) { return s ? s.toLowerCase() : ""; }); if (!deepEqual(this.getMediaTypes(), aTypes)) { this.setProperty("mediaTypes", aTypes, true); this.getDefaultFileUploader().setMimeType(aTypes); } return this; }; UploadSetwithTable.prototype.setUploadButtonInvisible = function (bUploadButtonInvisible) { if (bUploadButtonInvisible !== this.getUploadButtonInvisible()) { var bVisible = !bUploadButtonInvisible; this.getDefaultFileUploader().setVisible(bVisible); this.setProperty("uploadButtonInvisible", bUploadButtonInvisible, true); } return this; }; UploadSetwithTable.prototype.setMultiple = function (bMultiple) { if (this.getMultiple() !== bMultiple) { this.setProperty("multiple", bMultiple); this.getDefaultFileUploader().setMultiple(bMultiple); } return this; }; UploadSetwithTable.prototype.setUploadEnabled = function (bEnable) { if (bEnable !== this.getUploadEnabled()) { this.getDefaultFileUploader().setEnabled(bEnable); if (bEnable && !this._oDragDropConfig) { this.getConfig("setDragDropConfig", this.getControl(), this); } else if (!bEnable) { this.getConfig("resetDragDropConfig", this.getControl(), this); } this.setProperty("uploadEnabled", bEnable, false); } return this; }; UploadSetwithTable.prototype.setDirectory = function (bDirectory) { if (this.getDirectory() !== bDirectory) { this.setProperty("directory", bDirectory); this.getDefaultFileUploader().setDirectory(bDirectory); if (bDirectory) { this.setProperty("multiple", false); // disable multiple files selection when directory selection is enabled. } } return this; }; UploadSetwithTable.prototype.setNoDataIllustration = function(oNoDatIllustration) { this._vNoDataIllustration = oNoDatIllustration; this.getConfig("setDefaultIllustrations", this.getControl(), this); return this; }; UploadSetwithTable.prototype.getNoDataIllustration = function() { return this._vNoDataIllustration; }; UploadSetwithTable.prototype.setFileNameValidationConfig = function (oConfig) { // set property to null if no config is passed if (oConfig === undefined || oConfig === null) { this.setProperty("fileNameValidationConfig", null); return this; } // Validate that it's an object if (typeof oConfig !== 'object') { throw new Error("fileNameValidationConfig must be a non-null object with mode and characters properties"); } // Extract keys from the object const keys = Object.keys(oConfig); const expectedKeys = ['mode', 'characters']; // Check for exact match in keys (no more, no less) const missingKeys = expectedKeys.filter((key) => !keys.includes(key)); const extraKeys = keys.filter((key) => !expectedKeys.includes(key)); if (missingKeys.length > 0 || extraKeys.length > 0) { throw new Error( `fileNameValidationConfig must contain only the following properties: ${expectedKeys.join(', ')}. ` + (missingKeys.length > 0 ? `Missing: ${missingKeys.join(', ')}. ` : '') + (extraKeys.length > 0 ? `Unexpected: ${extraKeys.join(', ')}.` : '') ); } // Validate `mode` is a string and one of the allowed values const validModes = ['include', 'exclude', 'override']; if (typeof oConfig.mode !== 'string' || !validModes.includes(oConfig.mode)) { throw new Error(`fileNameValidationConfig.mode must be one of ${validModes.join(', ')}`); } // Validate `characters` is a string if (typeof oConfig.characters !== 'string') { throw new Error("fileNameValidationConfig.characters must be a string."); } // Call the default property setter (important!) this.setProperty("fileNameValidationConfig", oConfig); return this; }; // Public API's /** * Returns an instance of the default <code>sap.ui.unified.FileUploader</code> icon/button, used for adding files * from the open file dialog of the operating system. It can be customized, for example made invisible or assigned a different icon. * @return {sap.ui.unified.FileUploader} Instance of the default <code>sap.ui.unified.FileUploader</code>. * @public */ UploadSetwithTable.prototype.getDefaultFileUploader = function () { var sTooltip = this._oRb.getText("UPLOADSET_WITH_TABLE_UPLOADBUTTON_TEXT"); if (!this._oFileUploader) { this._oFileUploader = new FileUploader(this.getId() + "-uploader", { buttonOnly: true, buttonText: sTooltip, tooltip: sTooltip, iconOnly: false, enabled: this.getUploadEnabled(), icon: "", iconFirst: false, style: "Transparent", name: "UploadSetwithTableFileUploader", sameFilenameAllowed: true, fileType: this.getFileTypes(), mimeType: this.getMediaTypes(), maximumFilenameLength: this.getMaxFileNameLength(), maximumFileSize: this.getMaxFileSize(), multiple: this.getDirectory() ? false : this.getMultiple(), useMultipart: false, sendXHR: true, change: [this._onFileUploaderChange, this], typeMissmatch: [this._fireFileTypeMismatch, this], fileSizeExceed: [this._fireFileSizeExceed, this], filenameLengthExceed: [this._fireFilenameLengthExceed, this], visible: !this.getUploadButtonInvisible(), directory: this.getDirectory() }); } return this._oFileUploader; }; /** * Returns sap icon based on the passed mediaType and filename * @param {string} mediaType The media type of the selected file * @param {string} fileName The name of the selected file * @public * @returns {string} sap icon. */ UploadSetwithTable.getIconForFileType = function (mediaType, fileName) { return UploadItem._getIconByMimeType(mediaType, fileName); }; /** * Attaches all necessary handlers to the given uploader instance, so that the progress and status of the upload can be * displayed and monitored. * This is necessary in case when custom uploader is used. * @param {sap.m.upload.UploaderTableItem} oUploader Instance of <code>sap.m.upload.UploaderTableItem</code> to which the default request handlers are attached. * @public */ UploadSetwithTable.prototype.registerUploaderEvents = function (oUploader) { oUploader.attachUploadStarted(this._onUploadStarted.bind(this)); oUploader.attachUploadCompleted(this._onUploadCompleted.bind(this)); oUploader.attachUploadProgressed(this._onUploadProgressed.bind(this)); oUploader.attachUploadTerminated(this._onUploadAborted.bind(this)); }; /** * Invokes native files selection handler. * @public */ UploadSetwithTable.prototype.fileSelectionHandler = function() { var oUploaderInstance = this.getDefaultFileUploader(); if (oUploaderInstance && oUploaderInstance.oFileUpload && oUploaderInstance.oFileUpload.click) { oUploaderInstance.oFileUpload.click(); } }; /** * API to determine the unit for file size in KB/MB/GB. * API recommended for file size formatting purpose. * @param {int} iFileSize fileSize to determine units * @public * @returns {string} sFileSizeWithUnit file size in KB/MB/GB default unit is KB */ UploadSetwithTable.getFileSizeWithUnits = function(iFileSize) { var iKilobyte = 1024; var iMegabyte = iKilobyte * 1024; var iGigabyte = iMegabyte * 1024; if (typeof iFileSize === "number") { if (iFileSize < iMegabyte) { return (iFileSize / iKilobyte).toFixed(2) + " KB"; } else if (iFileSize < iGigabyte) { return (iFileSize / iMegabyte).toFixed(2) + " MB"; } else { return (iFileSize / iGigabyte).toFixed(2) + " GB"; } } return iFileSize; }; /** * API to upload file using URL * @param {string} sName file name to be set for the file that is to be uploaded. * @param {string} sUrl Url for the file. * @param {Promise} oPromise Promise when resolved, the control initiates the upload process. * @returns {sap.m.upload.UploadItem} oItem, UploadItem instance created with the file object. * @public */ UploadSetwithTable.prototype.uploadItemViaUrl = function (sName, sUrl, oPromise) { var oFileObject = new File([new Blob([])], sName); var oItem = new UploadItem({ uploadState: UploadState.Ready }); oItem._setFileObject(oFileObject); oItem.setFileName(oFileObject.name); oItem.setUrl(sUrl); oPromise .then(() => this._initateItemUpload(oItem).bind(this)) .catch(() => oItem.destroy()); // cancelling the upload. return oItem; }; /** * API to upload Item without file * @param {Promise} oPromise Promise when resolved, control initiates the upload process. * @return {sap.m.upload.UploadItem} oItem, UploadItem instance created with the file object. * @public */ UploadSetwithTable.prototype.uploadItemWithoutFile = function (oPromise) { var oFileObject = new File([new Blob([])], '-'); var oItem = new UploadItem({ uploadState: UploadState.Ready }); oItem._setFileObject(oFileObject); oItem.setFileName(oFileObject.name); oPromise .then(() => this._initateItemUpload(oItem)) .catch(() => oItem.destroy()); // cancelling the upload. return oItem; }; /** * Previews file. * @param {sap.ui.model.Context} oBindingContext Context of the row containing the file to be previewed. * @public */ UploadSetwithTable.prototype.openFilePreview = function (oBindingContext) { const oRowConfiguration = this.getRowConfiguration(); if (!oRowConfiguration) { Log.error("Row configuration is not set for the plugin. File preview is not possible."); return; } this.getConfig("openFilePreview", oBindingContext, this.getControl(), this); }; /** * Downloads the file. Only possible when the context passed has a valid URL specified. * @param {sap.ui.model.Context} oBindingContext Context of the item to be downloaded. * @param {boolean} bAskForLocation Whether to ask for a location where to download the file or not. * @public */ UploadSetwithTable.prototype.download = function (oBindingContext, bAskForLocation) { const oRowConfiguration = this.getRowConfiguration(); if (!oRowConfiguration) { Log.error("Row configuration is not set for the plugin. Download is not possible."); return; } this.getConfig("download", { oBindingContext: oBindingContext, bAskForLocation: bAskForLocation }, this, this.getControl()); }; /** * API to rename the document of an item. * @param {sap.ui.model.Context} oBindingContext Context of the item to be renamed. * @public */ UploadSetwithTable.prototype.renameItem = async function (oBindingContext) { const oRowConfiguration = this.getRowConfiguration(); if (!oRowConfiguration) { Log.error("Row configuration is not set for the plugin. Rename action is not possible."); return; } if (oBindingContext) { const oItem = await this.getItemForContext(oBindingContext, true); const oDialog = this._getFileRenameDialog(oItem); oDialog.open(); } }; // Private API's /** * Internal API return the dialog for document rename. * @param {sap.m.upload.UploadItem} oItem item to be renamed. * @private * @returns {sap.m.Dialog} oDialog, created dialog instance */ UploadSetwithTable.prototype._getFileRenameDialog = function(oItem) { const oSplit = UploadItem._splitFileName(oItem.getFileName()); let iMaxLength = this.getMaxFileNameLength(); const iFileExtensionLength = oSplit.extension ? oSplit.extension.length + 1 : 0; iMaxLength = iMaxLength ? iMaxLength : 0; let iNameMaxLength = iMaxLength - iFileExtensionLength; iNameMaxLength = iNameMaxLength < 0 ? 0 : iNameMaxLength; // Input field const oInput = new Input({ type: Library.InputType.Text, value: oSplit.name, width: "75%", maxLength: iNameMaxLength, liveChange: [this._handleItemNameValidation, this] }); oInput.addStyleClass("sapUiTinyMarginTop"); oInput.addStyleClass("sapUiMediumMarginBegin"); // Test field for extension const sExtension = oSplit.extension ? `.${oSplit.extension}` : ""; const oTextField = new TextField({ text: sExtension }); oTextField.addStyleClass("sapUiTinyMarginBegin"); oTextField.addStyleClass("sapUiTinyMarginTop"); oInput.addAriaDescribedBy(oTextField.getId()); // Label for Input const oLabel = new Label({ text: this._oRb.getText("UPLOADSET_WITH_TABLE_DOCUMENT_RENAME_INPUT_LABEL"), labelFor: oInput.getId(), required: true }); oLabel.addStyleClass("sapUiMediumMarginTop"); oLabel.addStyleClass("sapUiMediumMarginBegin"); oLabel.addStyleClass("sapUiSmallMarginEnd"); const oVBox = new VBox({ items: [oLabel] }); // Dialog creation var oDialog = new Dialog({ title: this._oRb.getText("UPLOADSET_WITH_TABLE_DOCUMENT_RENAME_DIALOG_TEXT"), contentWidth: "33.375rem", contentHeight: "10.125rem", content: [oVBox, oInput, oTextField], beginButton: new Button({ type: Library.ButtonType.Emphasized, text: this._oRb.getText("UPLOADSET_WITH_TABLE_DOCUMENT_RENAME_APPLY_BUTTON_TEXT"), press: this._handleItemRenameConfirmation.bind(this), enabled: oInput.getValueState() !== "Error" }), endButton: new Button({ text: this._oRb.getText("UPLOADSET_WITH_TABLE_CANCELBUTTON_TEXT"), press: this._handleItemRenameCancel.bind(this) }), customData: { key: "item", value: oItem }, afterClose: function () { oDialog.destroy(); }, escapeHandler: (oPromise) => { oDialog.close(); this.fireItemRenameCanceled({item: oItem}); oPromise?.reject(); } }); return oDialog; }; /** * Handler for item rename cancel operation. * @param {object} oEvent cancel button click event. * @private */ UploadSetwithTable.prototype._handleItemRenameCancel = function(oEvent) { const oDialog = oEvent.getSource().getParent(); const oInput = oDialog.getContent()[1]; const oItem = oDialog && oDialog.data ? oDialog.data().item : null; const oSplit = UploadItem._splitFileName(oItem.getFileName()); // Check if there are changes made to the existing file name. if (oItem && oInput && oSplit.name !== oInput.getValue()) { MessageBox.warning(this._oRb.getText("UPLOADSET_WITH_TABLE_DOCUMENT_RENAME_DISCARD_POPUP_CHANGES_TEXT"), { actions: [this._oRb.getText("UPLOADSET_WITH_TABLE_DOCUMENT_RENAME_SAVE_BUTTON_TEXT"), this._oRb.getText("UPLOADSET_WITH_TABLE_DOCUMENT_RENAME_DISCARD_CHANGES_BUTTON_TEXT")], emphasizedAction: this._oRb.getText("UPLOADSET_WITH_TABLE_DOCUMENT_RENAME_SAVE_BUTTON_TEXT"), onClose: (sAction) => { if (sAction !== this._oRb.getText("UPLOADSET_WITH_TABLE_DOCUMENT_RENAME_SAVE_BUTTON_TEXT")) { oDialog.close(); this.fireItemRenameCanceled({item: oItem}); } else { // fire beginbutton event to save the filename var oBeginButton = oDialog.getBeginButton(); var oEvent = new EventBase("click", oBeginButton); oBeginButton.firePress(oEvent); } } }); } else { oDialog.close(); this.fireItemRenameCanceled({item: oItem}); } }; /** * Handler for item rename confirm operation. * @param {object} oEvent confirm button click event. * @private */ UploadSetwithTable.prototype._handleItemRenameConfirmation = function(oEvent) { const oDialog = oEvent.getSource().getParent(); const oInput = oDialog.getContent()[1]; if (oInput && oInput.getValueState() === "Error") { oInput.focus(oInput); oInput.setShowValueStateMessage(true); return; } const oItem = oDialog && oDialog.data ? oDialog.data().item : null; const oSplit = UploadItem._splitFileName(oItem.getFileName()); // update only if there is change if (oItem && oSplit.name !== oInput.getValue()) { // const oContext = oItem.data("context"); if (oSplit && oSplit.extension) { oItem.setFileName(oInput.getValue() + "." + oSplit.extension); } else { oItem.setFileName(oInput.getValue()); } oDialog.close(); this.fireItemRenamed({item: oItem}); } else { oDialog.close(); this.fireItemRenameCanceled({item: oItem}); } }; /** * Handler for file name validation. * @param {object} oEvent Input keyevent. * @private */ UploadSetwithTable.prototype._handleItemNameValidation = function(oEvent) { const oInput = oEvent.getSource(); let sValue = oInput.getValue(); sValue = sValue.trim(); // empty file validation if (sValue === "") { oInput.setProperty("valueState", "Error", true); oInput.setValueStateText(this._oRb.getText("UPLOADSET_WITH_TABLE_DOCUMENT_RENAME_EMPTY_NAME_VALIDATION_ERROR_MESSAGE")); oInput.setShowValueStateMessage(true); return; } const sValidationCharacterSet = this._getFileNameValidationChracters(); const sEscapedSpecialCharcters = sValidationCharacterSet.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); const oCharacterRegex = new RegExp(`[${sEscapedSpecialCharcters}]`); if (oCharacterRegex.test(sValue)) { oInput.setShowValueStateMessage(true); oInput.setProperty("valueState", "Error", true); oInput.setValueStateText(this._oRb.getText("UPLOADSET_WITH_TABLE_DOCUMENT_RENAME_SPLC_VALIDATION_ERROR_MESSAGE", [sValidationCharacterSet])); } else { oInput.setShowValueStateMessage(false); oInput.setProperty("valueState", "None", true); } }; UploadSetwithTable.prototype._getFileNameValidationChracters = function() { const {mode, characters} = this.getFileNameValidationConfig() || {}; switch (mode) { case "include": { // remove duplicate characters and the concatenate with default invalid characters const aUniqueChars = [...new Set((UploadSetwithTable.DEFAULT_INVALID_FILENAME_CHARACTERSET + characters).split(""))]; const sUniqueChars = aUniqueChars.join(""); return sUniqueChars; } case "exclude": { const aExcludeChars = [...new Set(characters.split(""))]; const sExcludeChars = aExcludeChars.join(""); // remove duplicate characters and the concatenate with default invalid characters return excludeCharacters(UploadSetwithTable.DEFAULT_INVALID_FILENAME_CHARACTERSET, sExcludeChars); } case "override": return characters; default: return UploadSetwithTable.DEFAULT_INVALID_FILENAME_CHARACTERSET; } function excludeCharacters(inputStr, charsToRemove) { for (var char of charsToRemove) { inputStr = inputStr.split(char).join(""); } return inputStr; } }; UploadSetwithTable.prototype._onFileUploaderChange = function (oEvent) { var oFiles = oEvent.getParameter("files"); if (oFiles && oFiles.length) { // var aSelectedItems = this.getConfig("getSelectedItems", oControl); // var oSelectedItem = aSelectedItems && aSelectedItems.length == 1 ? aSelectedItems[0] : null; // var bEmptyFileSelected = oSelectedItem ? oSelectedItem && oSelectedItem.getFileName && oSelectedItem.getFileName() === "-" : false; // // update existing file after upload // if (bEmptyFileSelected) { // this._oItemToUpdate = oFiles[0]; // } this._processSelectedFileObjects(oFiles); } }; UploadSetwithTable.prototype._processSelectedFileObjects = function (oFiles) { var aFiles = []; // Need to explicitly copy the file list, FileUploader deliberately resets its form completely // along with 'files' parameter when it (mistakenly) thinks that all is done. for (var i = 0; i < oFiles.length; i++) { aFiles.push(oFiles[i]); } aFiles.forEach((oFile) => { var oItem = new UploadItem({ uploadState: UploadState.Ready }); this._setInitialFileSettings({ item: oItem, file: oFile, fileList: aFiles }); if (this.getItemValidationHandler() && typeof this.getItemValidationHandler() === "function" ) { const oItemInfo = { oItem: oItem, iTotalItemsForUpload: aFiles.length, oSource: this }; var oPromise = this.getItemValidationHandler()(oItemInfo); try { if (oPromise && typeof oPromise?.then === "function") { oPromise .then((item) => { if (item instanceof UploadItem) { this._initateItemUpload(item); } }) .catch((item) => { // Reset variable to avoid update if upload rejected. if (item && this._oItemToUpdate && item instanceof UploadItem && item.getId() === this._oItemToUpdate.getId()) { this._oItemToUpdate = null; } }); } else { oItem.destroy(); // if promise is not returned to the ItemValidation hook log error and destroy the item Log.error("Invalid usage, missing Promise: ItemValidationHandler callback expects Promise to be returned."); } } catch (error) { oItem.destroy(); // If the ItemValidationHandler throws an error, log it and destroy the item Log.error("Invalid usage, missing Promise: ItemValidationHandler callback expects Promise to be returned.", error); } } else { /* if no validation handler is provided control continues with normal upload else waits for the application to manually trigger the upload by resolving the promise */ this._initateItemUpload(oItem); } }); }; // Private method to set the initial file settings UploadSetwithTable.prototype._setInitialFileSettings = function(oSettings) { const { item } = oSettings; let file = oSettings.file; const sFileName = file.name; const sMimeType = file.type; if (!sMimeType) { const sDocument = UploadItem._splitFileName(sFileName, true); const sFileExtension = sDocument?.extension; switch (sFileExtension) { case UploadItem.FILETYPES.VDS: // Create a new file object with the correct MIME type file = new File([file] , sFileName, {type: UploadItem.MEDIATYPES.VDS}); // programatically set the MIME type to the item item.setMediaType(UploadItem.MEDIATYPES.VDS); break; default: break; } } item.setParent(this); // setting the parent as UploadSetwithTable for file validations item._setFileObject(file); item.setFileName(sFileName); }; UploadSetwithTable.prototype._fireFileTypeMismatch = function (oItem) { var aMediaTypes = this.getMediaTypes(); var aFileTypes = this.getFileTypes(); var sFileType = oItem.getParameter("fileType"); var sMediaType = oItem.getParameter("mimeType"); var bMediaRestricted = (!!aMediaTypes && (aMediaTypes.length > 0) && !!sMediaType && aMediaTypes.indexOf(sMediaType) === -1); var bFileRestricted = (!!aFileTypes && (aFileTypes.length > 0) && !!sFileType && aFileTypes.indexOf(sFileType) === -1); var parts = [new Blob([])]; var oFileMetaData = { type: oItem.getParameter('mimeType'), webkitRelativePath: '', name: oItem.getParameter('fileName') }; var oFileObject = new File(parts, oItem.getParameter('fileName'), oFileMetaData); var oMismatchItem = new UploadItem(); oMismatchItem._setFileObject(oFileObject); oMismatchItem.setFileName(oFileObject.name); if (bMediaRestricted){ this.fireMediaTypeMismatch({item: oMismatchItem}); } else if (bFileRestricted){ this.fireFileTypeMismatch({item: oMismatchItem}); } }; UploadSetwithTable.prototype._fireFilenameLengthExceed = function (oItem) { var oTargetItem = new UploadItem(); oTargetItem.setFileName(oItem.getParameter('fileName')); this.fireFileNameLengthExceeded({item: oTargetItem}); }; UploadSetwithTable.prototype._fireFileSizeExceed = function (oItem) { var oTargetItem = new UploadItem(); oTargetItem.setFileName(oItem.getParameter('fileName')); this.fireFileSizeExceeded({item: oTargetItem}); }; UploadSetwithTable.prototype._onUploadStarted = function (oEvent) { var oItem = oEvent.getParameter("item"); oItem.setUploadState(UploadState.Uploading); }; UploadSetwithTable.prototype._onUploadCompleted = function (oEvent) { var oItem = oEvent.getParameter("item"), oResponseXHRParams = oEvent.getParameter("responseXHR"), sResponse = null; if (oResponseXHRParams.responseXML) { sResponse = oResponseXHRParams.responseXML.documentElement.textContent; } var oXhrParams = { "item": oItem, "response": oResponseXHRParams.response, "responseXML": sResponse, "responseText": oResponseXHRParams.responseText, "readyState": oResponseXHRParams.readyState, "status": oResponseXHRParams.status, "headers": oResponseXHRParams.headers }; if (this._oItemToUpdate) { this._oItemToUpdate.setFileName(oItem.getFileName()); this._oItemToUpdate._setFileObject(oItem.getFileObject()); this._oItemToUpdate = null; } oItem.setUploadState(UploadState.Complete); this.fireUploadCompleted(oXhrParams); }; UploadSetwithTable.prototype._onUploadProgressed = function (oEvent) { var oItem = oEvent.getParameter("item"); oItem.fireUploadProgress({ loaded: oEvent.getParameter("loaded"), total: oEvent.getParameter("total") }); }; UploadSetwithTable.prototype._uploadItemIfGoodToGo = function (oItem) { if (oItem.getUploadState() === UploadState.Ready && !oItem._isRestricted()) { if (this.fireBeforeUploadStarts({item: oItem})) { const aHeaderFields = this.getHeaderFields()?.length ? this.getHeaderFields() : []; const aItemHeaderFields = oItem.getHeaderFields()?.length ? oItem.getHeaderFields() : []; const oHeaderFields = [...aHeaderFields, ...aItemHeaderFields]; //Merging headers for request. this._getActiveUploader().uploadItem(oItem, oHeaderFields); } } }; UploadSetwithTable.prototype._getActiveUploader = function () { return this.getUploader() || this._getImplicitUploader(); }; UploadSetwithTable.prototype._getImplicitUploader = function () { if (!this._oUploader) { this._oUploader = new Uploader({ httpRequestMethod : this.getHttpRequestMethod() }); this._oUploader.setUploadUrl(this.getUploadUrl()); this.registerUploaderEvents(this._oUploader); this.addDependent(this._oUploader); } return this._oUploader; }; UploadSetwithTable.prototype._initateItemUpload = function(oItem) { this.fireBeforeInitiatingItemUpload({item: oItem}); if (this._oItemToUpdate) { // Registering item to be update with selected file contents post successful upload. this._oItemToUpdate = oItem; } this._uploadItemIfGoodToGo(oItem); }; /** * Drag and drop of files implmentation subject to change depending on the thr UX feedback for folder upload scenarios and warning message display scenarios * @param {sap.ui.base.Event} oEvent Drop Event when file is dropped on the Table. * @private */ UploadSetwithTable.prototype._onDropFile = function (oEvent) { oEvent.preventDefault(); if (!this.getUploadEnabled()) { Log.error("Upload is not enabled, to continue uploading with drag and drop of files enable property 'UploadEnabled' "); return; } let oItems = oEvent.getParameter("browserEvent")?.dataTransfer?.items || []; oItems = Array.from(oItems); // Filtering out only webkitentries (files/folders system entries) by excluding non file / directory types. oItems = oItems.filter(function(item){ return item.webkitGetAsEntry() ? true : false; }); const aEntryTypes = oItems.map(function (oEntry) { const oWebKitEntry = oEntry.webkitGetAsEntry(); return { entryType: oWebKitEntry && oWebKitEntry.isFile ? 'File' : 'Directory' }; }); // handlding multiple property drag & drop scenarios if (oItems && oItems.length > 1 && !this.getMultiple() && !this.getDirectory()) { // Handling drag and drop of multiple files to upload with multiple property set const sMessage = this._oRb.getText("UPLOADSET_WITH_TABLE_MULTIPLE_RESTRICTED"); Log.warning("Multiple files upload is retsricted for this multiple property set"); MessageBox.error(sMessage); return; } else if (oItems && oItems.length > 1 && this.getMultiple() && !isFileOrFolderEntry('File', aEntryTypes)) { const sMessageDropFilesOnly = this._oRb.getText("UPLOADSET_WITH_TABLE_DIRECTORY_RESTRICTED"); Log.warning("Multiple files upload is retsricted, drag & drop only files"); MessageBox.error(sMessageDropFilesOnly); return; } // handling directory property drag & drop scenarios if (oItems && oItems.length && !this.getDirectory() && isFileOrFolderEntry('Directory', aEntryTypes)) { const sMessageDirectory = this._oRb.getText("UPLOADSET_WITH_TABLE_DIRECTORY_RESTR