UNPKG

devextreme

Version:

JavaScript/TypeScript Component Suite for Responsive Web Development

1,206 lines • 63.6 kB
/** * DevExtreme (esm/__internal/ui/file_uploader/file_uploader.js) * Version: 25.2.5 * Build date: Fri Feb 20 2026 * * Copyright (c) 2012 - 2026 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ import eventsEngine from "../../../common/core/events/core/events_engine"; import { addNamespace, isTouchEvent, normalizeKeyName } from "../../../common/core/events/utils/index"; import messageLocalization from "../../../common/core/localization/message"; import registerComponent from "../../../core/component_registrator"; import devices from "../../../core/devices"; import domAdapter from "../../../core/dom_adapter"; import Guid from "../../../core/guid"; import $ from "../../../core/renderer"; import Callbacks from "../../../core/utils/callbacks"; import { extend } from "../../../core/utils/extend"; import { getOffset, getOuterWidth, getWidth } from "../../../core/utils/size"; import { isDefined, isFunction, isNumeric } from "../../../core/utils/type"; import { getWindow } from "../../../core/utils/window"; import Button from "../../../ui/button"; import ProgressBar from "../../../ui/progress_bar"; import { current, isFluent, isMaterial } from "../../../ui/themes"; import { ICON_CLASS } from "../../core/utils/m_icon"; import Editor from "../../ui/editor/editor"; import { CustomChunksFileUploadStrategy } from "../../ui/file_uploader/file_upload_strategy.chunks.custom"; import { DefaultChunksFileUploadStrategy } from "../../ui/file_uploader/file_upload_strategy.chunks.default"; import { CustomWholeFileUploadStrategy } from "../../ui/file_uploader/file_upload_strategy.whole.custom"; import { DefaultWholeFileUploadStrategy } from "../../ui/file_uploader/file_upload_strategy.whole.default"; import { getFileIconName, getFileSize } from "../../ui/file_uploader/file_uploader.utils"; const window = getWindow(); export const FILEUPLOADER_CLASS = "dx-fileuploader"; const FILEUPLOADER_EMPTY_CLASS = "dx-fileuploader-empty"; const FILEUPLOADER_SHOW_FILE_LIST_CLASS = "dx-fileuploader-show-file-list"; const FILEUPLOADER_DRAGOVER_CLASS = "dx-fileuploader-dragover"; const FILEUPLOADER_WRAPPER_CLASS = "dx-fileuploader-wrapper"; const FILEUPLOADER_CONTAINER_CLASS = "dx-fileuploader-container"; const FILEUPLOADER_CONTENT_CLASS = "dx-fileuploader-content"; const FILEUPLOADER_INPUT_WRAPPER_CLASS = "dx-fileuploader-input-wrapper"; const FILEUPLOADER_INPUT_CONTAINER_CLASS = "dx-fileuploader-input-container"; const FILEUPLOADER_INPUT_LABEL_CLASS = "dx-fileuploader-input-label"; const FILEUPLOADER_INPUT_CLASS = "dx-fileuploader-input"; const FILEUPLOADER_FILES_CONTAINER_CLASS = "dx-fileuploader-files-container"; const FILEUPLOADER_FILE_CONTAINER_CLASS = "dx-fileuploader-file-container"; const FILEUPLOADER_FILE_INFO_CLASS = "dx-fileuploader-file-info"; const FILEUPLOADER_FILE_STATUS_MESSAGE_CLASS = "dx-fileuploader-file-status-message"; const FILEUPLOADER_FILE_CLASS = "dx-fileuploader-file"; const FILEUPLOADER_FILE_NAME_CLASS = "dx-fileuploader-file-name"; const FILEUPLOADER_FILE_SIZE_CLASS = "dx-fileuploader-file-size"; const FILEUPLOADER_FILE_ICON_CLASS = "dx-fileuploader-file-icon"; const FILEUPLOADER_BUTTON_CLASS = "dx-fileuploader-button"; const FILEUPLOADER_BUTTON_CONTAINER_CLASS = "dx-fileuploader-button-container"; export const FILEUPLOADER_CANCEL_BUTTON_CLASS = "dx-fileuploader-cancel-button"; export const FILEUPLOADER_CANCEL_BUTTON_POSITION_END_CLASS = "dx-fileuploader-cancel-button-position-end"; const FILEUPLOADER_UPLOAD_BUTTON_CLASS = "dx-fileuploader-upload-button"; const FILEUPLOADER_INVALID_CLASS = "dx-fileuploader-invalid"; const FILEUPLOADER_AFTER_LOAD_DELAY = 400; const DRAG_EVENT_DELTA = 1; const DIALOG_TRIGGER_EVENT_NAMESPACE = "dxFileUploaderDialogTrigger"; const keyUpEventName = "keyup"; const nativeClickEvent = "click"; const ENTER_KEY = "enter"; const SPACE_KEY = "space"; let renderFileUploaderInput = () => $("<input>").attr("type", "file"); const isFormDataSupported = () => !!window.FormData; class FileUploader extends Editor { _supportedKeys() { const click = e => { e.preventDefault(); const $selectButton = this._selectButton.$element(); eventsEngine.triggerHandler($selectButton, { type: "dxclick" }) }; return Object.assign({}, super._supportedKeys(), { space: click, enter: click }) } _setOptionsByReference() { super._setOptionsByReference(); extend(this._optionsByReference, { value: true }) } _getDefaultOptions() { return Object.assign({}, super._getDefaultOptions(), { chunkSize: 0, value: [], selectButtonText: messageLocalization.format("dxFileUploader-selectFile"), uploadButtonText: messageLocalization.format("dxFileUploader-upload"), labelText: messageLocalization.format("dxFileUploader-dropFile"), name: "files[]", multiple: false, accept: "", uploadUrl: "/", allowCanceling: true, showFileList: true, progress: 0, dialogTrigger: void 0, dropZone: void 0, readyToUploadMessage: messageLocalization.format("dxFileUploader-readyToUpload"), uploadedMessage: messageLocalization.format("dxFileUploader-uploaded"), uploadFailedMessage: messageLocalization.format("dxFileUploader-uploadFailedMessage"), uploadAbortedMessage: messageLocalization.format("dxFileUploader-uploadAbortedMessage"), uploadMode: "instantly", uploadMethod: "POST", uploadHeaders: {}, uploadCustomData: {}, onBeforeSend: null, onUploadStarted: null, onUploaded: null, onFilesUploaded: null, onFileValidationError: null, onProgress: null, onUploadError: null, onUploadAborted: null, onDropZoneEnter: null, onDropZoneLeave: null, onCancelButtonClick: null, onFileLimitReached: void 0, allowedFileExtensions: [], maxFileSize: 0, minFileSize: 0, inputAttr: {}, invalidFileExtensionMessage: messageLocalization.format("dxFileUploader-invalidFileExtension"), invalidMaxFileSizeMessage: messageLocalization.format("dxFileUploader-invalidMaxFileSize"), invalidMinFileSizeMessage: messageLocalization.format("dxFileUploader-invalidMinFileSize"), extendSelection: true, validationMessageMode: "always", uploadFile: null, uploadChunk: null, abortUpload: null, validationMessageOffset: { h: 0, v: 0 }, hoverStateEnabled: true, useNativeInputClick: false, useDragOver: true, nativeDropSupported: true, _uploadButtonType: "normal", _buttonStylingMode: "contained", _hideCancelButtonOnUpload: true, _showFileIcon: false, _cancelButtonPosition: "start", _maxFileCount: void 0 }) } _defaultOptionsRules() { return super._defaultOptionsRules().concat([{ device: () => "desktop" === devices.real().deviceType && !devices.isSimulator(), options: { focusStateEnabled: true } }, { device: [{ platform: "android" }], options: { validationMessageOffset: { v: 0 } } }, { device: () => "desktop" !== devices.real().deviceType, options: { useDragOver: false, nativeDropSupported: false, labelText: "" } }, { device: () => !isFormDataSupported(), options: { uploadMode: "useForm" } }, { device: () => isMaterial(current()), options: { _uploadButtonType: "default" } }, { device: () => isFluent(current()), options: { _buttonStylingMode: "text" } }]) } _initOptions(options) { const isLabelTextDefined = "labelText" in options; super._initOptions(options); if (!isLabelTextDefined && !this._shouldDragOverBeRendered()) { this.option({ labelText: "" }) } } _init() { super._init(); this._initFileInput(); this._initLabel(); this._setUploadStrategy(); this._createFileLimitReachedAction(); this._createFiles(); this._createBeforeSendAction(); this._createUploadStartedAction(); this._createUploadedAction(); this._createFilesUploadedAction(); this._createFileValidationErrorAction(); this._createProgressAction(); this._createUploadErrorAction(); this._createUploadAbortedAction(); this._createDropZoneEnterAction(); this._createDropZoneLeaveAction(); this._createCancelButtonClickAction() } _setUploadStrategy() { const { chunkSize: chunkSize = 0 } = this.option(); if (chunkSize > 0) { const { uploadChunk: uploadChunk } = this.option(); this._uploadStrategy = uploadChunk && isFunction(uploadChunk) ? new CustomChunksFileUploadStrategy(this) : new DefaultChunksFileUploadStrategy(this) } else { const { uploadFile: uploadFile } = this.option(); this._uploadStrategy = uploadFile && isFunction(uploadFile) ? new CustomWholeFileUploadStrategy(this) : new DefaultWholeFileUploadStrategy(this) } } _initFileInput() { this._isCustomClickEvent = false; const { multiple: multiple, accept: accept, hint: hint } = this.option(); if (!this._$fileInput) { this._$fileInput = renderFileUploaderInput(); eventsEngine.on(this._$fileInput, "change", (() => { this._inputChangeHandler() })); eventsEngine.on(this._$fileInput, "click", (e => { e.stopPropagation(); this._resetInputValue(); const { useNativeInputClick: useNativeInputClick } = this.option(); return useNativeInputClick || this._isCustomClickEvent })) } const inputProps = { multiple: multiple, accept: accept, tabIndex: -1 }; if (isDefined(hint)) { inputProps.title = hint } this._$fileInput.prop(inputProps) } _inputChangeHandler() { if (this._doPreventInputChange) { return } const fileName = this._$fileInput.val().replace(/^.*\\/, ""); const files = this._$fileInput.prop("files"); const { uploadMode: uploadMode } = this.option(); if (files && !files.length && "useForm" !== uploadMode) { return } if (this._isFileLimitReached(files)) { var _this$_fileLimitReach; null === (_this$_fileLimitReach = this._fileLimitReachedAction) || void 0 === _this$_fileLimitReach || _this$_fileLimitReach.call(this); return } const value = files ? this._getFiles(files) : [{ name: fileName }]; this._changeValue(value); if ("instantly" === uploadMode) { this._uploadFiles() } } _isFileLimitReached() { let files = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : []; const { _maxFileCount: _maxFileCount, value: value } = this.option(); if (void 0 === _maxFileCount) { return false } const totalCount = files.length + ((null === value || void 0 === value ? void 0 : value.length) ?? 0); const isFileLimitReached = totalCount > _maxFileCount; return isFileLimitReached } _shouldFileListBeExtended() { const { uploadMode: uploadMode, extendSelection: extendSelection, multiple: multiple } = this.option(); return Boolean("useForm" !== uploadMode && extendSelection && multiple) } _changeValue(value) { const { value: currentValue } = this.option(); const files = this._shouldFileListBeExtended() ? null === currentValue || void 0 === currentValue ? void 0 : currentValue.slice() : []; this.option({ value: null === files || void 0 === files ? void 0 : files.concat(value) }) } _getFiles(fileList) { return [...fileList] } _getFile(fileData) { var _this$_files; const { value: value } = this.option(); const targetFileValue = isNumeric(fileData) ? null === value || void 0 === value ? void 0 : value[fileData] : fileData; return null === (_this$_files = this._files) || void 0 === _this$_files ? void 0 : _this$_files.filter((file => file.value === targetFileValue))[0] } _initLabel() { if (!this._$inputLabel) { this._$inputLabel = $("<div>") } this._updateInputLabelText() } _updateInputLabelText() { const { labelText: labelText } = this.option(); const correctedValue = this._isInteractionDisabled() ? "" : labelText; this._$inputLabel.text(correctedValue ?? "") } _focusTarget() { return this.$element().find(".dx-fileuploader-button") } _getSubmitElement() { return this._$fileInput } _initMarkup() { super._initMarkup(); this.$element().addClass("dx-fileuploader"); this._renderWrapper(); this._renderInputWrapper(); this._renderSelectButton(); this._renderInputContainer(); this._renderUploadButton(); this._preventRecreatingFiles = true; this._activeDropZone = null } _render() { const { dropZone: dropZone } = this.option(); this._preventRecreatingFiles = false; this._attachDragEventHandlers(this._$inputWrapper); this._attachDragEventHandlers(dropZone); this._renderFiles(); super._render() } _createFileProgressBar(file) { file.progressBar = this._createProgressBar(file.value.size); if (file.$file) { file.progressBar.$element().appendTo(file.$file) } this._initStatusMessage(file); this._ensureCancelButtonInitialized(file) } _setStatusMessage(file) { let message = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : ""; setTimeout((() => { const { showFileList: showFileList } = this.option(); if (showFileList) { if (file.$statusMessage) { var _file$progressBar; file.$statusMessage.text(message); file.$statusMessage.css("display", ""); null === (_file$progressBar = file.progressBar) || void 0 === _file$progressBar || _file$progressBar.$element().remove() } } }), 400) } _getUploadAbortedStatusMessage() { const { uploadMode: uploadMode, uploadAbortedMessage: uploadAbortedMessage, readyToUploadMessage: readyToUploadMessage } = this.option(); return "instantly" === uploadMode ? uploadAbortedMessage : readyToUploadMessage } _createFiles() { const { value: files } = this.option(); if (this._isFileLimitReached()) { var _this$_fileLimitReach2; null === (_this$_fileLimitReach2 = this._fileLimitReachedAction) || void 0 === _this$_fileLimitReach2 || _this$_fileLimitReach2.call(this) } if (this._files && (0 === (null === files || void 0 === files ? void 0 : files.length) || !this._shouldFileListBeExtended())) { this._preventFilesUploading(this._files); this._files = null } if (!this._files) { this._files = [] } null === files || void 0 === files || files.slice(this._files.length).forEach((value => { var _this$_files2; const file = this._createFile(value); this._validateFile(file); null === (_this$_files2 = this._files) || void 0 === _this$_files2 || _this$_files2.push(file) })) } _preventFilesUploading(files) { null === files || void 0 === files || files.forEach((file => this._uploadStrategy.abortUpload(file))) } _validateFile(file) { file.isValidFileExtension = this._validateFileExtension(file); file.isValidMinSize = this._validateMinFileSize(file); file.isValidMaxSize = this._validateMaxFileSize(file) } _validateFileExtension(file) { const { allowedFileExtensions: allowedFileExtensions } = this.option(); if (!(null !== allowedFileExtensions && void 0 !== allowedFileExtensions && allowedFileExtensions.length)) { return true } return this._isFileExtensionAllowed(file.value, allowedFileExtensions) } _validateMaxFileSize(file) { const fileSize = file.value.size; const { maxFileSize: maxFileSize = 0 } = this.option(); return maxFileSize > 0 ? fileSize <= maxFileSize : true } _validateMinFileSize(file) { const fileSize = file.value.size; const { minFileSize: minFileSize = 0 } = this.option(); return minFileSize > 0 ? fileSize >= minFileSize : true } _isFileExtensionAllowed(file, allowedExtensions) { for (let i = 0, n = allowedExtensions.length; i < n; i += 1) { let allowedExtension = allowedExtensions[i]; if (allowedExtension.startsWith(".")) { allowedExtension = allowedExtension.replace(".", "\\."); if (new RegExp(`${allowedExtension}$`, "i").exec(file.name)) { return true } } else { allowedExtension = allowedExtension.replace(new RegExp("\\*", "g"), ""); if (new RegExp(allowedExtension, "i").exec(file.type)) { return true } } } return false } _createBeforeSendAction() { this._beforeSendAction = this._createActionByOption("onBeforeSend", { excludeValidators: ["readOnly"] }) } _createUploadStartedAction() { this._uploadStartedAction = this._createActionByOption("onUploadStarted", { excludeValidators: ["readOnly"] }) } _createUploadedAction() { this._uploadedAction = this._createActionByOption("onUploaded", { excludeValidators: ["readOnly"] }) } _createFilesUploadedAction() { this._filesUploadedAction = this._createActionByOption("onFilesUploaded", { excludeValidators: ["readOnly"] }) } _createFileValidationErrorAction() { this._fileValidationErrorAction = this._createActionByOption("onFileValidationError", { excludeValidators: ["readOnly"] }) } _createProgressAction() { this._progressAction = this._createActionByOption("onProgress", { excludeValidators: ["readOnly"] }) } _createUploadAbortedAction() { this._uploadAbortedAction = this._createActionByOption("onUploadAborted", { excludeValidators: ["readOnly"] }) } _createUploadErrorAction() { this._uploadErrorAction = this._createActionByOption("onUploadError", { excludeValidators: ["readOnly"] }) } _createDropZoneEnterAction() { this._dropZoneEnterAction = this._createActionByOption("onDropZoneEnter") } _createDropZoneLeaveAction() { this._dropZoneLeaveAction = this._createActionByOption("onDropZoneLeave") } _createCancelButtonClickAction() { this._cancelButtonClickAction = this._createActionByOption("onCancelButtonClick", { excludeValidators: ["readOnly"] }) } _createFileLimitReachedAction() { this._fileLimitReachedAction = this._createActionByOption("onFileLimitReached", { excludeValidators: ["readOnly"] }) } _createFile(value) { return { value: value, loadedSize: 0, onProgress: Callbacks(), onAbort: Callbacks(), onLoad: Callbacks(), onError: Callbacks(), onLoadStart: Callbacks(), isValidFileExtension: true, isValidMaxSize: true, isValidMinSize: true, isValid() { return Boolean(this.isValidFileExtension) && Boolean(this.isValidMaxSize) && Boolean(this.isValidMinSize) }, isInitialized: false } } _resetFileState(file) { file.isAborted = false; file.uploadStarted = false; file.isStartLoad = false; file.loadedSize = 0; file.chunksData = void 0; file.request = void 0 } _renderFiles() { var _this$_files4, _this$_validationMess; const { value: value, showFileList: showFileList } = this.option(); if (!this._$filesContainer) { this._$filesContainer = $("<div>").addClass("dx-fileuploader-files-container").appendTo(this._$content) } else if (!this._shouldFileListBeExtended() || 0 === (null === value || void 0 === value ? void 0 : value.length)) { this._$filesContainer.empty() } if (showFileList) { var _this$_files3; null === (_this$_files3 = this._files) || void 0 === _this$_files3 || _this$_files3.forEach((file => { if (!file.$file) { this._renderFile(file) } })) } this.$element().toggleClass("dx-fileuploader-show-file-list", showFileList); this._toggleFileContainerAria(Boolean(showFileList && (null === (_this$_files4 = this._files) || void 0 === _this$_files4 ? void 0 : _this$_files4.length))); this._toggleFileUploaderEmptyClassName(); this._updateFileNameMaxWidth(); null === (_this$_validationMess = this._validationMessage) || void 0 === _this$_validationMess || _this$_validationMess.repaint() } _toggleFileContainerAria(applyAria) { var _this$_$filesContaine; const aria = { role: applyAria ? "list" : null, "aria-label": applyAria ? messageLocalization.format("dxFileUploader-fileListLabel") : null }; null === (_this$_$filesContaine = this._$filesContainer) || void 0 === _this$_$filesContaine || _this$_$filesContaine.attr(aria) } _renderFile(file) { const { value: value } = file; if (!this._$filesContainer) { return } const $fileContainer = $("<div>").addClass("dx-fileuploader-file-container").appendTo(this._$filesContainer).attr("role", "listitem"); this._renderFileIcon(value.name, $fileContainer); file.$file = $("<div>").addClass("dx-fileuploader-file").appendTo($fileContainer); const $fileInfo = $("<div>").addClass("dx-fileuploader-file-info").appendTo(file.$file); file.$statusMessage = $("<div>").addClass("dx-fileuploader-file-status-message").appendTo(file.$file); $("<div>").addClass("dx-fileuploader-file-name").text(value.name).attr("title", value.name).appendTo($fileInfo); if (isDefined(value.size)) { $("<div>").addClass("dx-fileuploader-file-size").text(getFileSize(value.size)).appendTo($fileInfo) } this._renderFileButtons(file, $fileContainer); if (file.isValid()) { const { readyToUploadMessage: readyToUploadMessage } = this.option(); file.$statusMessage.text(readyToUploadMessage ?? "") } else { var _this$_fileValidation; if (!file.isValidFileExtension) { file.$statusMessage.append(this._createValidationElement("invalidFileExtensionMessage")) } if (!file.isValidMaxSize) { file.$statusMessage.append(this._createValidationElement("invalidMaxFileSizeMessage")) } if (!file.isValidMinSize) { file.$statusMessage.append(this._createValidationElement("invalidMinFileSizeMessage")) } null === (_this$_fileValidation = this._fileValidationErrorAction) || void 0 === _this$_fileValidation || _this$_fileValidation.call(this, { file: file.value }); $fileContainer.addClass("dx-fileuploader-invalid") } } _createValidationElement(key) { return $("<span>").text(this.option()[key]) } _updateFileNameMaxWidth() { var _this$_$filesContaine2, _this$_$filesContaine3, _this$_$filesContaine4, _this$_$filesContaine5, _this$_$filesContaine6; const { allowCanceling: allowCanceling, uploadMode: uploadMode, _showFileIcon: _showFileIcon } = this.option(); const cancelButtonsCount = allowCanceling && "useForm" !== uploadMode ? 1 : 0; const uploadButtonsCount = "useButtons" === uploadMode ? 1 : 0; const filesContainerWidth = getWidth(null === (_this$_$filesContaine2 = this._$filesContainer) || void 0 === _this$_$filesContaine2 ? void 0 : _this$_$filesContaine2.find(".dx-fileuploader-file-container").first()) || getWidth(this._$filesContainer); const $buttonContainer = null === (_this$_$filesContaine3 = this._$filesContainer) || void 0 === _this$_$filesContaine3 ? void 0 : _this$_$filesContaine3.find(".dx-fileuploader-button-container").eq(0); const buttonsWidth = getWidth($buttonContainer) * (cancelButtonsCount + uploadButtonsCount); const $fileSize = null === (_this$_$filesContaine4 = this._$filesContainer) || void 0 === _this$_$filesContaine4 ? void 0 : _this$_$filesContaine4.find(".dx-fileuploader-file-size").eq(0); const $icon = null === (_this$_$filesContaine5 = this._$filesContainer) || void 0 === _this$_$filesContaine5 ? void 0 : _this$_$filesContaine5.find(".dx-fileuploader-file-icon").eq(0); const iconWidth = _showFileIcon ? getOuterWidth($icon) : 0; const prevFileSize = null === $fileSize || void 0 === $fileSize ? void 0 : $fileSize.text(); null === $fileSize || void 0 === $fileSize || $fileSize.text("1000 Mb"); const fileSizeWidth = getWidth($fileSize); null === $fileSize || void 0 === $fileSize || $fileSize.text(prevFileSize ?? ""); const maxWidth = filesContainerWidth - buttonsWidth - fileSizeWidth - iconWidth; null === (_this$_$filesContaine6 = this._$filesContainer) || void 0 === _this$_$filesContaine6 || _this$_$filesContaine6.find(".dx-fileuploader-file-name").css("maxWidth", maxWidth) } _renderFileButtons(file, $container) { const { _cancelButtonPosition: _cancelButtonPosition } = this.option(); const $uploadButton = this._getUploadButton(file); if ($uploadButton) { $container.prepend($uploadButton) } const $cancelButton = this._getCancelButton(file); if ($cancelButton) { if ("end" === _cancelButtonPosition) { $container.append($cancelButton); return } $container.prepend($cancelButton) } } _renderFileIcon(fileName, $container) { const { _showFileIcon: _showFileIcon } = this.option(); if (!_showFileIcon) { return } $("<div>").addClass(`dx-fileuploader-file-icon ${ICON_CLASS} ${ICON_CLASS}-${getFileIconName(fileName)}`).appendTo($container) } _getCancelButton(file) { var _file$value; const { uploadMode: uploadMode, _cancelButtonPosition: _cancelButtonPosition } = this.option(); if ("useForm" === uploadMode) { return null } const { allowCanceling: allowCanceling, readOnly: readOnly, hoverStateEnabled: hoverStateEnabled, _buttonStylingMode: _buttonStylingMode } = this.option(); file.cancelButton = this._createComponent($("<div>").addClass("dx-fileuploader-button dx-fileuploader-cancel-button"), Button, { onClick: () => { var _this$_cancelButtonCl; this._removeFile(file); null === (_this$_cancelButtonCl = this._cancelButtonClickAction) || void 0 === _this$_cancelButtonCl || _this$_cancelButtonCl.call(this, { file: file.value }) }, icon: "close", visible: allowCanceling, disabled: readOnly, integrationOptions: {}, hoverStateEnabled: hoverStateEnabled, stylingMode: _buttonStylingMode, elementAttr: { "aria-label": messageLocalization.format("dxFileUploader-removeFileButtonLabel", (null === file || void 0 === file || null === (_file$value = file.value) || void 0 === _file$value ? void 0 : _file$value.name) ?? "") } }); if ("end" === _cancelButtonPosition) { file.cancelButton.$element().addClass("dx-fileuploader-cancel-button-position-end") } return $("<div>").addClass("dx-fileuploader-button-container").append(file.cancelButton.$element()) } _getUploadButton(file) { var _file$value2; const { uploadMode: uploadMode } = this.option(); if (!file.isValid() || "useButtons" !== uploadMode) { return null } const { hoverStateEnabled: hoverStateEnabled, _buttonStylingMode: _buttonStylingMode } = this.option(); file.uploadButton = this._createComponent($("<div>").addClass("dx-fileuploader-button dx-fileuploader-upload-button"), Button, { onClick: () => this._uploadFile(file), icon: "upload", hoverStateEnabled: hoverStateEnabled, stylingMode: _buttonStylingMode, elementAttr: { "aria-label": messageLocalization.format("dxFileUploader-uploadFileButtonLabel", (null === file || void 0 === file || null === (_file$value2 = file.value) || void 0 === _file$value2 ? void 0 : _file$value2.name) ?? "") } }); file.onLoadStart.add((() => { var _file$uploadButton; null === (_file$uploadButton = file.uploadButton) || void 0 === _file$uploadButton || _file$uploadButton.option({ visible: false, disabled: true }) })); file.onAbort.add((() => { var _file$uploadButton2; null === (_file$uploadButton2 = file.uploadButton) || void 0 === _file$uploadButton2 || _file$uploadButton2.option({ visible: true, disabled: false }) })); return $("<div>").addClass("dx-fileuploader-button-container").append(file.uploadButton.$element()) } _removeFile(file) { var _file$$file, _this$_files5, _this$_files6; null === (_file$$file = file.$file) || void 0 === _file$$file || _file$$file.parent().remove(); null === (_this$_files5 = this._files) || void 0 === _this$_files5 || _this$_files5.splice(this._files.indexOf(file), 1); const { value: value } = this.option(); const valueCopy = null === value || void 0 === value ? void 0 : value.slice(); null === valueCopy || void 0 === valueCopy || valueCopy.splice(valueCopy.indexOf(file.value), 1); this._preventRecreatingFiles = true; this.option({ value: valueCopy }); this._preventRecreatingFiles = false; if (0 === (null === (_this$_files6 = this._files) || void 0 === _this$_files6 ? void 0 : _this$_files6.length)) { this._toggleFileContainerAria(false) } this._toggleFileUploaderEmptyClassName(); this._resetInputValue(true) } removeFile(fileData) { const { uploadMode: uploadMode } = this.option(); if ("useForm" === uploadMode || !isDefined(fileData)) { return } const file = this._getFile(fileData); if (file) { if (file.uploadStarted) { this._preventFilesUploading([file]) } this._removeFile(file) } } _toggleFileUploaderEmptyClassName() { var _this$_files7; this.$element().toggleClass("dx-fileuploader-empty", !(null !== (_this$_files7 = this._files) && void 0 !== _this$_files7 && _this$_files7.length) || this._hasInvalidFile(this._files)) } _hasInvalidFile(files) { return files.some((file => !file.isValid())) } _renderSelectButton() { const $button = $("<div>").addClass("dx-fileuploader-button").appendTo(this._$inputWrapper); const { selectButtonText: selectButtonText, readOnly: readOnly, hoverStateEnabled: hoverStateEnabled } = this.option(); this._selectButton = this._createComponent($button, Button, { text: selectButtonText, focusStateEnabled: false, integrationOptions: {}, disabled: readOnly, hoverStateEnabled: hoverStateEnabled }); if ("desktop" === devices.real().deviceType) { this._selectButton.option({ onClick: () => this._selectFileDialogClickHandler() }) } else { this._attachSelectFileDialogHandlers(this._selectButton.$element()) } const { dialogTrigger: dialogTrigger } = this.option(); this._attachSelectFileDialogHandlers(dialogTrigger) } _selectFileDialogClickHandler() { const { useNativeInputClick: useNativeInputClick } = this.option(); if (useNativeInputClick || this._isInteractionDisabled()) { return } this._isCustomClickEvent = true; eventsEngine.trigger(this._$fileInput, "click"); this._isCustomClickEvent = false } _attachSelectFileDialogHandlers(target) { if (!isDefined(target)) { return } this._detachSelectFileDialogHandlers(target); const $target = $(target); eventsEngine.on($target, addNamespace("click", "dxFileUploaderDialogTrigger"), (() => { this._selectFileDialogClickHandler() })); eventsEngine.on($target, addNamespace("keyup", "dxFileUploaderDialogTrigger"), (e => { const normalizedKeyName = normalizeKeyName(e); if ("enter" === normalizedKeyName || "space" === normalizedKeyName) { this._selectFileDialogClickHandler() } })) } _detachSelectFileDialogHandlers(target) { if (!isDefined(target)) { return } const $target = $(target); eventsEngine.off($target, ".dxFileUploaderDialogTrigger") } _renderUploadButton() { const { uploadButtonText: uploadButtonText, _uploadButtonType: _uploadButtonType, hoverStateEnabled: hoverStateEnabled, uploadMode: uploadMode } = this.option(); if ("useButtons" !== uploadMode) { return } const $uploadButton = $("<div>").addClass("dx-fileuploader-button").addClass("dx-fileuploader-upload-button").appendTo(this._$content); this._uploadButton = this._createComponent($uploadButton, Button, { text: uploadButtonText, onClick: this._uploadButtonClickHandler.bind(this), type: _uploadButtonType, integrationOptions: {}, hoverStateEnabled: hoverStateEnabled }) } _uploadButtonClickHandler() { this._uploadFiles() } _shouldDragOverBeRendered() { const { readOnly: readOnly, uploadMode: uploadMode, nativeDropSupported: nativeDropSupported } = this.option(); return !readOnly && ("useForm" !== uploadMode || nativeDropSupported) } _isInteractionDisabled() { const { readOnly: readOnly, disabled: disabled } = this.option(); return Boolean(readOnly) || Boolean(disabled) } _renderInputContainer() { this._$inputContainer = $("<div>").addClass("dx-fileuploader-input-container").appendTo(this._$inputWrapper); this._renderInput(); this._$fileInput.addClass("dx-fileuploader-input"); const labelId = `dx-fileuploader-input-label-${new Guid}`; this._$inputLabel.attr("id", labelId).addClass("dx-fileuploader-input-label").appendTo(this._$inputContainer); this.setAria("labelledby", labelId, this._$fileInput) } _renderInput() { const { useNativeInputClick: useNativeInputClick, inputAttr: inputAttr } = this.option(); if (useNativeInputClick) { this._selectButton.option({ template: this._selectButtonInputTemplate.bind(this) }) } else { this._$fileInput.appendTo(this._$inputContainer); this._selectButton.option({ template: "content" }) } this._applyInputAttributes(inputAttr) } _selectButtonInputTemplate(data, content) { const $content = $(content); const $text = $("<span>").addClass("dx-button-text").text(data.text); $content.append($text).append(this._$fileInput); return $content } _renderInputWrapper() { if (!this._$content) { return } this._$inputWrapper = $("<div>").addClass("dx-fileuploader-input-wrapper").appendTo(this._$content) } _detachDragEventHandlers(target) { if (!target) { return } eventsEngine.off($(target), addNamespace("", this.NAME)) } _attachDragEventHandlers(target) { const isCustomTarget = target !== this._$inputWrapper; if (!isDefined(target) || !this._shouldDragOverBeRendered()) { return } this._detachDragEventHandlers(target); eventsEngine.on($(target), addNamespace("dragenter", this.NAME), this._dragEnterHandler.bind(this, isCustomTarget)); eventsEngine.on($(target), addNamespace("dragover", this.NAME), this._dragOverHandler.bind(this, isCustomTarget)); eventsEngine.on($(target), addNamespace("dragleave", this.NAME), this._dragLeaveHandler.bind(this, isCustomTarget)); eventsEngine.on($(target), addNamespace("drop", this.NAME), this._dropHandler.bind(this, isCustomTarget)) } _applyInputAttributes(customAttributes) { this._$fileInput.attr(customAttributes) } _useInputForDrop() { const { uploadMode: uploadMode, nativeDropSupported: nativeDropSupported } = this.option(); return Boolean(nativeDropSupported) && "useForm" === uploadMode } _getDropZoneElement(isCustomTarget, e) { if (!e.currentTarget) { return } const { dropZone: dropZone } = this.option(); const targetList = isCustomTarget ? $(dropZone).toArray() : [this._$inputWrapper]; const targetListElements = targetList.map((element => $(element).get(0))); const currentTargetIndex = targetListElements.indexOf(e.currentTarget); return targetListElements[currentTargetIndex] } _dragEnterHandler(isCustomTarget, e) { const { disabled: disabled } = this.option(); if (disabled) { return false } if (!this._useInputForDrop()) { e.preventDefault() } const dropZoneElement = this._getDropZoneElement(isCustomTarget, e); if (isDefined(dropZoneElement) && this._shouldRaiseDragOver(e, dropZoneElement)) { this._activeDropZone = dropZoneElement; this._tryToggleDropZoneActive(true, isCustomTarget, e) } } _shouldRaiseDragOver(e, dropZoneElement) { return null === this._activeDropZone && this.isMouseOverElement(e, dropZoneElement, false) && e.originalEvent.dataTransfer.types.find((item => "Files" === item)) } _dragOverHandler(isCustomTarget, e) { if (!this._useInputForDrop()) { e.preventDefault() } e.originalEvent.dataTransfer.dropEffect = "copy"; if (!isCustomTarget) { const dropZoneElement = this._getDropZoneElement(false, e); if (this._shouldRaiseDragOver(e, dropZoneElement)) { this._dragEnterHandler(false, e) } if (this._shouldRaiseDragLeave(e, false)) { this._dragLeaveHandler(false, e) } } } _dragLeaveHandler(isCustomTarget, e) { if (!this._useInputForDrop()) { e.preventDefault() } if (this._shouldRaiseDragLeave(e, isCustomTarget)) { this._tryToggleDropZoneActive(false, isCustomTarget, e); this._activeDropZone = null } } _shouldRaiseDragLeave(e, isCustomTarget) { return null !== this._activeDropZone && !this.isMouseOverElement(e, this._activeDropZone, !isCustomTarget, -1) } _tryToggleDropZoneActive(active, isCustom, event) { var _this$mouseAction; const classAction = active ? "addClass" : "removeClass"; const mouseAction = active ? "_dropZoneEnterAction" : "_dropZoneLeaveAction"; null === (_this$mouseAction = this[mouseAction]) || void 0 === _this$mouseAction || _this$mouseAction.call(this, { event: event, dropZoneElement: this._activeDropZone }); if (!isCustom) { this.$element()[classAction]("dx-fileuploader-dragover") } } _dropHandler(isCustomTarget, e) { this._activeDropZone = null; if (!isCustomTarget) { this.$element().removeClass("dx-fileuploader-dragover") } if (this._useInputForDrop() || isCustomTarget && this._isInteractionDisabled()) { return } e.preventDefault(); const fileList = e.originalEvent.dataTransfer.files; const files = this._getFiles(fileList); const { multiple: multiple, uploadMode: uploadMode } = this.option(); if (!multiple && files.length > 1 || 0 === files.length) { return } if (this._isFileLimitReached(files)) { var _this$_fileLimitReach3; null === (_this$_fileLimitReach3 = this._fileLimitReachedAction) || void 0 === _this$_fileLimitReach3 || _this$_fileLimitReach3.call(this); return } this._changeValue(files); if ("instantly" === uploadMode) { this._uploadFiles() } } _areAllFilesLoaded() { var _this$_files8; return null === (_this$_files8 = this._files) || void 0 === _this$_files8 ? void 0 : _this$_files8.every((file => !file.isValid() || file._isError || file._isLoaded || file.isAborted)) } _handleAllFilesUploaded() { this._recalculateProgress(); if (this._areAllFilesLoaded()) { var _this$_filesUploadedA; null === (_this$_filesUploadedA = this._filesUploadedAction) || void 0 === _this$_filesUploadedA || _this$_filesUploadedA.call(this) } } _renderWrapper() { const $wrapper = $("<div>").addClass("dx-fileuploader-wrapper").appendTo(this.$element()); const $container = $("<div>").addClass("dx-fileuploader-container").appendTo($wrapper); this._$content = $("<div>").addClass("dx-fileuploader-content").appendTo($container) } _clean() { this._$fileInput.detach(); this._$filesContainer = null; const { dialogTrigger: dialogTrigger, dropZone: dropZone } = this.option(); this._detachSelectFileDialogHandlers(dialogTrigger); this._detachDragEventHandlers(dropZone); if (this._files) { this._files.forEach((file => { file.$file = null; file.$statusMessage = null })) } super._clean() } abortUpload(fileData) { const { uploadMode: uploadMode } = this.option(); if ("useForm" === uploadMode) { return } if (isDefined(fileData)) { const file = this._getFile(fileData); if (file) { this._preventFilesUploading([file]) } } else { this._preventFilesUploading(this._files) } } upload(fileData) { const { uploadMode: uploadMode } = this.option(); if ("useForm" === uploadMode) { return } if (isDefined(fileData)) { const file = this._getFile(fileData); if (file && isFormDataSupported()) { this._uploadFile(file) } } else { this._uploadFiles() } } _uploadFiles() { if (isFormDataSupported()) { var _this$_files9; null === (_this$_files9 = this._files) || void 0 === _this$_files9 || _this$_files9.forEach((file => this._uploadFile(file))) } } _uploadFile(file) { this._uploadStrategy.upload(file) } _updateProgressBar(file, loadedFileData) { var _file$progressBar2, _this$_progressAction; null === (_file$progressBar2 = file.progressBar) || void 0 === _file$progressBar2 || _file$progressBar2.option({ value: loadedFileData.loaded, showStatus: true }); null === (_this$_progressAction = this._progressAction) || void 0 === _this$_progressAction || _this$_progressAction.call(this, { file: file.value, segmentSize: loadedFileData.currentSegmentSize, bytesLoaded: loadedFileData.loaded, bytesTotal: loadedFileData.total, event: loadedFileData.event, request: file.request }) } _updateTotalProgress(totalFilesSize, totalLoadedFilesSize) { let progress = 0; if (isDefined(totalFilesSize)) { if (this._files && this._files.length > 0 && this._areAllFilesLoaded() && 0 === totalFilesSize && 0 === totalLoadedFilesSize) { progress = this._getProgressValue(1) } else if (totalFilesSize) { progress = this._getProgressValue(totalLoadedFilesSize / totalFilesSize) } } this.option({ progress: progress }); this._setLoadedSize(totalLoadedFilesSize) } _getProgressValue(ratio) { return Math.floor(100 * ratio) } _initStatusMessage(file) { var _file$$statusMessage; null === (_file$$statusMessage = file.$statusMessage) || void 0 === _file$$statusMessage || _file$$statusMessage.css("display", "none") } _ensureCancelButtonInitialized(file) { var _file$cancelButton; if (file.isInitialized) { return } null === (_file$cancelButton = file.cancelButton) || void 0 === _file$cancelButton || _file$cancelButton.option({ onClick: () => { var _this$_cancelButtonCl2; this._preventFilesUploading([file]); this._remove