Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ class FileUploader extends Editor<FileUploaderProperties> {

_cancelButtonClickAction?: (event?: Partial<CancelButtonClickEvent>) => void;

_fileLimitReachedAction?: () => void;

static __internals: {
changeFileInputRenderer: (renderer: () => dxElementWrapper) => void;
resetFileInputTag: () => void;
Expand Down Expand Up @@ -211,6 +213,7 @@ class FileUploader extends Editor<FileUploaderProperties> {
onDropZoneEnter: null,
onDropZoneLeave: null,
onCancelButtonClick: null,
onFileLimitReached: undefined,
allowedFileExtensions: [],
maxFileSize: 0,
minFileSize: 0,
Expand All @@ -233,6 +236,7 @@ class FileUploader extends Editor<FileUploaderProperties> {
_hideCancelButtonOnUpload: true,
_showFileIcon: false,
_cancelButtonPosition: 'start',
_maxFileCount: undefined,
},
};
}
Expand Down Expand Up @@ -299,9 +303,11 @@ class FileUploader extends Editor<FileUploaderProperties> {

this._initFileInput();
this._initLabel();

this._setUploadStrategy();

this._createFileLimitReachedAction();
this._createFiles();

this._createBeforeSendAction();
this._createUploadStartedAction();
this._createUploadedAction();
Expand Down Expand Up @@ -336,11 +342,14 @@ class FileUploader extends Editor<FileUploaderProperties> {
if (!this._$fileInput) {
this._$fileInput = renderFileUploaderInput();

eventsEngine.on(this._$fileInput, 'change', this._inputChangeHandler.bind(this));
eventsEngine.on(this._$fileInput, 'change', () => { this._inputChangeHandler(); });
eventsEngine.on(this._$fileInput, 'click', (e: Event): boolean | undefined => {
e.stopPropagation();

this._resetInputValue();

const { useNativeInputClick } = this.option();

// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
return useNativeInputClick || this._isCustomClickEvent;
});
Expand Down Expand Up @@ -374,20 +383,42 @@ class FileUploader extends Editor<FileUploaderProperties> {
const fileName = this._$fileInput.val().replace(/^.*\\/, '');
// @ts-expect-error dxElementWrapper should be extdened
const files = this._$fileInput.prop('files');

const { uploadMode } = this.option();

if (files && !files.length && uploadMode !== 'useForm') {
return;
}

if (this._isFileLimitReached(files as unknown as File[])) {
this._fileLimitReachedAction?.();
return;
}

// @ts-expect-error dxElementWrapper should be extdened
const value = files ? this._getFiles(files) : [{ name: fileName }];

this._changeValue(value as File[]);

if (uploadMode === 'instantly') {
this._uploadFiles();
}
}

_isFileLimitReached(files: File[] = []): boolean {
// eslint-disable-next-line @typescript-eslint/naming-convention
const { _maxFileCount, value } = this.option();

if (_maxFileCount === undefined) {
return false;
}

const totalCount = files.length + (value?.length ?? 0);
const isFileLimitReached = totalCount > _maxFileCount;

return isFileLimitReached;
}

_shouldFileListBeExtended(): boolean {
const { uploadMode, extendSelection, multiple } = this.option();

Expand All @@ -398,6 +429,7 @@ class FileUploader extends Editor<FileUploaderProperties> {
const { value: currentValue } = this.option();

const files = this._shouldFileListBeExtended() ? currentValue?.slice() : [];

this.option({ value: files?.concat(value) });
}

Expand Down Expand Up @@ -494,6 +526,10 @@ class FileUploader extends Editor<FileUploaderProperties> {
_createFiles(): void {
const { value: files } = this.option();

if (this._isFileLimitReached()) {
this._fileLimitReachedAction?.();
}

if (this._files && (files?.length === 0 || !this._shouldFileListBeExtended())) {
this._preventFilesUploading(this._files);
this._files = null;
Expand Down Expand Up @@ -605,6 +641,10 @@ class FileUploader extends Editor<FileUploaderProperties> {
this._cancelButtonClickAction = this._createActionByOption('onCancelButtonClick', { excludeValidators: ['readOnly'] });
}

_createFileLimitReachedAction(): void {
this._fileLimitReachedAction = this._createActionByOption('onFileLimitReached', { excludeValidators: ['readOnly'] });
}

_createFile(value: File): FileUploaderItem {
return {
value,
Expand Down Expand Up @@ -1230,13 +1270,19 @@ class FileUploader extends Editor<FileUploaderProperties> {
const fileList = e.originalEvent.dataTransfer.files;
const files = this._getFiles(fileList);

const { multiple } = this.option();
const { multiple, uploadMode } = this.option();

if ((!multiple && files.length > 1) || files.length === 0) {
return;
}

if (this._isFileLimitReached(files as unknown as File[])) {
this._fileLimitReachedAction?.();
return;
}

this._changeValue(files);
const { uploadMode } = this.option();

if (uploadMode === 'instantly') {
this._uploadFiles();
}
Expand Down Expand Up @@ -1618,6 +1664,8 @@ class FileUploader extends Editor<FileUploaderProperties> {
case '_showFileIcon':
this._invalidate();
break;
case '_maxFileCount':
break;
case 'labelText':
this._updateInputLabelText();
break;
Expand Down Expand Up @@ -1678,6 +1726,9 @@ class FileUploader extends Editor<FileUploaderProperties> {
case 'onCancelButtonClick':
this._createCancelButtonClickAction();
break;
case 'onFileLimitReached':
this._createFileLimitReachedAction();
break;
case 'useNativeInputClick':
this._renderInput();
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ export type CancelButtonClickEvent = NativeEventInfo<InteractionEvent> & {
readonly file?: File;
};

export type FileLimitReachedEvent = NativeEventInfo<FileUploader>;

interface Properties extends PublicProperties {
_buttonStylingMode?: ButtonStyle;

Expand All @@ -129,6 +131,8 @@ interface Properties extends PublicProperties {

_cancelButtonPosition?: 'start' | 'end';

_maxFileCount?: number;

extendSelection?: boolean;

allowCanceling?: boolean;
Expand All @@ -139,7 +143,9 @@ interface Properties extends PublicProperties {

useDragOver?: boolean;

onCancelButtonClick?: ((e: CancelButtonClickEvent) => void);
onCancelButtonClick?: (e: CancelButtonClickEvent) => void;

onFileLimitReached?: (e: FileLimitReachedEvent) => void;
}

export interface FileUploaderProperties extends Properties,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4720,3 +4720,181 @@ QUnit.module('Accessibility', moduleConfig, () => {
});
});

QUnit.module('File limit', moduleConfig, () => {
QUnit.test('onFileLimitReached should be fired when selecting files exceeds the limit', function(assert) {
assert.expect(3);

let fileLimitReachedCount = 0;

const $element = $('#fileuploader').dxFileUploader({
multiple: true,
_maxFileCount: 2,
onFileLimitReached: (e) => {
fileLimitReachedCount++;

const { component, element } = e;

assert.strictEqual(component, $element.dxFileUploader('instance'), 'component field is correct');
assert.strictEqual($(element).is($element), true, 'element field is correct');
},
});

simulateFileChoose($element, [fakeFile, fakeFile1, fakeFile2]);

assert.strictEqual(fileLimitReachedCount, 1, 'onFileLimitReached callback was called once');
});

QUnit.test('files should not be added when limit is reached', function(assert) {
const $element = $('#fileuploader').dxFileUploader({
multiple: true,
_maxFileCount: 2,
uploadMode: 'useButtons',
});
const instance = $element.dxFileUploader('instance');

simulateFileChoose($element, [fakeFile, fakeFile1]);
assert.strictEqual(instance.option('value').length, 2, 'two files were added');

simulateFileChoose($element, [fakeFile2]);
assert.strictEqual(instance.option('value').length, 2, 'files count is still 2, third file was not added');
});

QUnit.test('onFileLimitReached should be fired when drag and drop files exceeds the limit', function(assert) {
let fileLimitReachedCount = 0;

const $element = $('#fileuploader').dxFileUploader({
multiple: true,
_maxFileCount: 2,
uploadMode: 'useButtons',
onFileLimitReached: () => {
fileLimitReachedCount++;
},
});

const instance = $element.dxFileUploader('instance');
const $inputWrapper = $element.find(`.${FILEUPLOADER_INPUT_WRAPPER_CLASS}`);

simulateFileChoose($element, [fakeFile]);
assert.strictEqual(instance.option('value').length, 1, 'one file was added');

const files = [fakeFile1, fakeFile2];

triggerDragEvent($inputWrapper, 'dragenter');
triggerDragEvent($inputWrapper, 'drop', { files, types: ['Files'] });

assert.strictEqual(fileLimitReachedCount, 1, 'onFileLimitReached was fired');
assert.strictEqual(instance.option('value').length, 1, 'files count is still 1');
});

QUnit.test('files should be added when count is less than limit', function(assert) {
const $element = $('#fileuploader').dxFileUploader({
multiple: true,
_maxFileCount: 3,
uploadMode: 'useButtons',
});
const instance = $element.dxFileUploader('instance');

simulateFileChoose($element, [fakeFile, fakeFile1]);
assert.strictEqual(instance.option('value').length, 2, 'two files were added');

simulateFileChoose($element, [fakeFile2]);
assert.strictEqual(instance.option('value').length, 3, 'third file was added');
});

QUnit.test('_maxFileCount: undefined should not limit files', function(assert) {
let fileLimitReachedCount = 0;

const $element = $('#fileuploader').dxFileUploader({
multiple: true,
_maxFileCount: undefined,
uploadMode: 'useButtons',
onFileLimitReached: () => {
fileLimitReachedCount++;
},
});
const instance = $element.dxFileUploader('instance');

simulateFileChoose($element, [fakeFile, fakeFile1, fakeFile2]);

assert.strictEqual(fileLimitReachedCount, 0, 'onFileLimitReached was not called');
assert.strictEqual(instance.option('value').length, 3, 'all three files were added');
});

QUnit.test('onFileLimitReached should not be fired when _maxFileCount is not set', function(assert) {
let fileLimitReachedCount = 0;

const $element = $('#fileuploader').dxFileUploader({
multiple: true,
uploadMode: 'useButtons',
onFileLimitReached: () => {
fileLimitReachedCount++;
},
});
const instance = $element.dxFileUploader('instance');

simulateFileChoose($element, [fakeFile, fakeFile1, fakeFile2]);

assert.strictEqual(fileLimitReachedCount, 0, 'onFileLimitReached was not called');
assert.strictEqual(instance.option('value').length, 3, 'all files were added');
});

QUnit.test('file limit should work in instantly upload mode', function(assert) {
let fileLimitReachedCount = 0;

const $element = $('#fileuploader').dxFileUploader({
multiple: true,
uploadMode: 'instantly',
_maxFileCount: 2,
onFileLimitReached: () => {
fileLimitReachedCount++;
},
});
const instance = $element.dxFileUploader('instance');

simulateFileChoose($element, [fakeFile, fakeFile1, fakeFile2]);

assert.strictEqual(fileLimitReachedCount, 1, 'onFileLimitReached was called');
assert.strictEqual(instance.option('value').length, 0, 'no files were added');
});

QUnit.test('onFileLimitReached should be fired on initial render when value exceeds _maxFileCount', function(assert) {
let fileLimitReachedCount = 0;

const $element = $('#fileuploader').dxFileUploader({
multiple: true,
uploadMode: 'useButtons',
_maxFileCount: 2,
value: [fakeFile, fakeFile1, fakeFile2],
onFileLimitReached: () => {
fileLimitReachedCount++;
},
});
const instance = $element.dxFileUploader('instance');

assert.strictEqual(fileLimitReachedCount, 1, 'onFileLimitReached was called on init');
assert.strictEqual(instance.option('value').length, 3, 'value won\'t reset to empty array');
});

QUnit.test('onFileLimitReached should be fired when value is changed programmatically and exceeds _maxFileCount', function(assert) {
let fileLimitReachedCount = 0;

const $element = $('#fileuploader').dxFileUploader({
multiple: true,
uploadMode: 'useButtons',
_maxFileCount: 2,
onFileLimitReached: () => {
fileLimitReachedCount++;
},
});
const instance = $element.dxFileUploader('instance');

instance.option('value', [fakeFile, fakeFile1]);
assert.strictEqual(fileLimitReachedCount, 0, 'onFileLimitReached was not called for 2 files');
assert.strictEqual(instance.option('value').length, 2, 'two files were set');

instance.option('value', [fakeFile, fakeFile1, fakeFile2]);
assert.strictEqual(fileLimitReachedCount, 1, 'onFileLimitReached was called when setting 3 files');
assert.strictEqual(instance.option('value').length, 3, 'value is changed');
});
});

Loading