Skip to content

Commit

Permalink
feat(file dropzone): restore on prefix to callback arguments
Browse files Browse the repository at this point in the history
feat(both components): remove unpublished filtering
docs(validation): update validation doc to describe the `@filter` arg
  • Loading branch information
gilest committed Feb 21, 2022
1 parent b36c04d commit 5d81caa
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 151 deletions.
23 changes: 9 additions & 14 deletions docs/validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,24 @@ For more details see the [MDN accept attribute reference](https://developer.mozi

## Custom validation

Both components provide callback arguments with which you may implement custom validation of chosen files.
Both `<FileUpload>` and `<FileDropzone>` components accept the `@filter` argument with which you may implement custom validation of chosen files.

`<FileUpload>` may be passed `@onSelect` and `<FileDropzone>` may be passed `@onDrop`.

These are called *after* files have been chosen and *before* `@onFileAdd` is called by the queue.

To implement validation, you may (optionally) return a subset of files from these callbacks to restrict which files are queued for upload.
This are called *after* files have been chosen and *before* `fileAdded` is called by the queue.

See the example below where the same validation callback is used for both selection methods.

Commonly validated file properties are `type`, `name` and `size`. For more details see the [MDN File reference](https://developer.mozilla.org/en-US/docs/Web/API/File).

```hbs
<FileDropzone
@name="photos"
@onDrop={{this.validateFiles}}
as |dropzone queue|
@queue={{queue}}
@filter={{this.validateFile}}
as |dropzone|
>
...
<FileUpload
@name="photos"
@onSelect={{this.validateFiles}}
@onFileAdd={{perform this.uploadImage}}
@queue={{queue}}
@filter={{this.validateFile}}
/>
...
</FileDropzone>
Expand All @@ -50,8 +45,8 @@ const allowedTypes = [

export default class ExampleComponent extends Component {
...
validateFiles(files) {
return files.filter((file) => allowedTypes.includes(file.type));
validateFile(file) {
return allowedTypes.includes(file.type);
}
}
```
43 changes: 6 additions & 37 deletions ember-file-upload/addon/components/file-dropzone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,23 @@ interface FileDropzoneArgs {
/**
* Called when files have entered the dropzone.
*/
filesEnter?: (
onDragEnter?: (
files: File[] | DataTransferItem[],
dataTransfer: DataTransferWrapper
) => void;

/**
* Called when files have left the dropzone.
*/
filesLeave?: (
onDragLeave?: (
files: File[] | DataTransferItem[],
dataTransfer: DataTransferWrapper
) => void;

/**
* Called when file have been dropped on the dropzone.
*/
filesDropped?: (files: UploadFile[]) => void;
onDrop?: (files: UploadFile[], dataTransfer: DataTransferWrapper) => void;

// old/deprecated API

Expand Down Expand Up @@ -85,31 +85,7 @@ interface FileDropzoneArgs {
for?: string;

/**
* @deprecated use `filesEnter()` instead
*/
onDragEnter?: (
files: File[] | DataTransferItem[],
dataTransfer: DataTransferWrapper
) => void;

/**
* @deprecated use `filesLeave()` instead
*/
onDragLeave?: (
files: File[] | DataTransferItem[],
dataTransfer: DataTransferWrapper
) => void;

/**
* @deprecated use `filesDropped()` instead
*/
onDrop?: (
files: File[] | DataTransferItem[],
dataTransfer: DataTransferWrapper
) => void;

/**
* @deprecated use `filesDropped()` instead
* @deprecated use `onDrop()` instead
*/
onFileAdd: (file: UploadFile) => void;
}
Expand Down Expand Up @@ -205,7 +181,6 @@ export default class FileDropzoneComponent extends Component<FileDropzoneArgs> {
this.active = true;

this.args.onDragEnter?.(this.files, this.dataTransferWrapper);
this.args.filesEnter?.(this.files, this.dataTransferWrapper);
}
}

Expand All @@ -219,7 +194,6 @@ export default class FileDropzoneComponent extends Component<FileDropzoneArgs> {
event.dataTransfer.dropEffect = this.cursor;
}
this.args.onDragLeave?.(this.files, this.dataTransferWrapper);
this.args.filesLeave?.(this.files, this.dataTransferWrapper);

this.dataTransferWrapper = undefined;

Expand Down Expand Up @@ -326,13 +300,8 @@ export default class FileDropzoneComponent extends Component<FileDropzoneArgs> {
// }

if (this.dataTransferWrapper) {
// @TODO stop filtering files based on the output of onDrop
// in favor of `filter()` - it never was officially public API
const files =
this.args.onDrop?.(this.files, this.dataTransferWrapper) ?? this.files;

const addedFiles = this.addFiles(files);
this.args.filesDropped?.(addedFiles);
const addedFiles = this.addFiles(this.files);
this.args.onDrop?.(addedFiles, this.dataTransferWrapper);

this.active = false;
this.dataTransferWrapper = undefined;
Expand Down
7 changes: 0 additions & 7 deletions ember-file-upload/addon/components/file-upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,6 @@ interface FileUploadArgs {
* @deprecated use `filesSelected()` instead
*/
onFileAdd: (file: UploadFile) => void;

// @TODO remove `onSelect` in favor of `filter()` - it never was officially
// of public API
/**
* @deprecated use `filter()` instead
*/
onSelect?: (files: UploadFile[]) => UploadFile[];
}

/**
Expand Down
140 changes: 47 additions & 93 deletions ember-file-upload/tests/integration/components/file-dropzone-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,49 +18,30 @@ module('Integration | Component | FileDropzone', function (hooks) {
this.queue = new Queue({ name: 'test', fileQueue: fileQueueService });
});

test('filesEnter', async function (assert) {
this.filesEnter = (files) =>
files.forEach((file) => assert.step(file.name));
await render(hbs`
<FileDropzone
class="test-dropzone"
@queue={{this.queue}}
@filesEnter={{this.filesEnter}}
/>
`);

await dragEnter('.test-dropzone', new File([], 'dingus.txt'));

assert.verifySteps(['dingus.txt']);
});
test('onDragEnter is called when a file is dragged over', async function (assert) {
this.onDragEnter = () => assert.step('onDragEnter');

test('filesLeave', async function (assert) {
this.filesLeave = (files) =>
files.forEach((file) => assert.step(file.name));
await render(hbs`
<FileDropzone
class="test-dropzone"
@queue={{this.queue}}
@filesLeave={{this.filesLeave}}
/>
@name="test"
@onDragEnter={{this.onDragEnter}} />
`);

await dragEnter('.test-dropzone', new File([], 'dingus.txt'));
await dragLeave('.test-dropzone', new File([], 'dingus.txt'));
await dragEnter('.test-dropzone');

assert.verifySteps(['dingus.txt']);
assert.verifySteps(['onDragEnter']);
});

test('filter and filesDropped', async function (assert) {
test('filter and onDrop', async function (assert) {
this.filter = (file) => file.name.includes('.txt');
this.filesDropped = (files) =>
files.forEach((file) => assert.step(file.name));
this.onDrop = (files) => files.forEach((file) => assert.step(file.name));
await render(hbs`
<FileDropzone
class="test-dropzone"
@queue={{this.queue}}
@filter={{this.filter}}
@filesDropped={{this.filesDropped}}
@onDrop={{this.onDrop}}
/>
`);

Expand All @@ -73,9 +54,7 @@ module('Integration | Component | FileDropzone', function (hooks) {

assert.verifySteps(['dingus.txt', 'dongus.txt']);
});
});

module('deprecated api', function () {
test('dropping a file calls onDrop', async function (assert) {
this.onDrop = (files) => files.forEach((file) => assert.step(file.name));

Expand All @@ -91,35 +70,55 @@ module('Integration | Component | FileDropzone', function (hooks) {
assert.verifySteps(['dingus.txt']);
});

// @TODO stop filtering files based on the output of onDrop
// in favor of `filter()` - it never was officially public API
test('only calls onFileAdd for files returned from onDrop', async function (assert) {
this.onDrop = (files) => {
assert.step(`onDrop: ${files.mapBy('name').join(',')}`);
return files.filter((f) => f.type.split('/')[0] === 'text');
};
this.onFileAdd = (file) => assert.step(`onFileAdd: ${file.name}`);
test('onDragLeave is called when a file is dragged out', async function (assert) {
this.onDragLeave = () => assert.step('onDragLeave');

await render(hbs`
<FileDropzone
class="test-dropzone"
@name="test"
@multiple={{true}}
@onDrop={{this.onDrop}}
@onFileAdd={{this.onFileAdd}}
/>
@onDragLeave={{this.onDragLeave}} />
`);

await dragEnter('.test-dropzone', new File([], 'dingus.txt'));
await dragLeave('.test-dropzone', new File([], 'dingus.txt'));

assert.verifySteps(['onDragLeave']);
});

test('yielded properties', async function (assert) {
await render(hbs`
<FileDropzone @name="test" as |dropzone queue|>
<div class="supported">{{dropzone.supported}}</div>
<div class="active">{{dropzone.active}}</div>
<div class="queue-name">{{queue.name}}</div>
</FileDropzone>
`);

assert.dom('.supported').hasText('true');
assert.dom('.active').hasText('false');
assert.dom('.queue-name').hasText('test');
});
});

module('deprecated api', function () {
test('dropping multiple files calls onFileAdd with each file', async function (assert) {
this.onFileAdd = (file) => assert.step(file.name);

await render(hbs`
<FileDropzone
class="test-dropzone"
@name="test"
@onFileAdd={{this.onFileAdd}} />
`);

await dragAndDrop(
'.test-dropzone',
new File([], 'dingus.html', { type: 'text/html' }),
new File([], 'dingus.png', { type: 'image/png' })
new File([], 'dingus.txt'),
new File([], 'dingus.png')
);

assert.verifySteps([
'onDrop: dingus.html,dingus.png',
'onFileAdd: dingus.html',
]);
assert.verifySteps(['dingus.txt', 'dingus.png']);
});

test('dropping multiple files calls onDrop with both files', async function (assert) {
Expand Down Expand Up @@ -180,50 +179,5 @@ module('Integration | Component | FileDropzone', function (hooks) {

assert.verifySteps(['dingus.txt']);
});

test('onDragEnter is called when a file is dragged over', async function (assert) {
this.onDragEnter = () => assert.step('onDragEnter');

await render(hbs`
<FileDropzone
class="test-dropzone"
@name="test"
@onDragEnter={{this.onDragEnter}} />
`);

await dragEnter('.test-dropzone');

assert.verifySteps(['onDragEnter']);
});

test('onDragLeave is called when a file is dragged out', async function (assert) {
this.onDragLeave = () => assert.step('onDragLeave');

await render(hbs`
<FileDropzone
class="test-dropzone"
@name="test"
@onDragLeave={{this.onDragLeave}} />
`);

await dragEnter('.test-dropzone', new File([], 'dingus.txt'));
await dragLeave('.test-dropzone', new File([], 'dingus.txt'));

assert.verifySteps(['onDragLeave']);
});

test('yielded properties', async function (assert) {
await render(hbs`
<FileDropzone @name="test" as |dropzone queue|>
<div class="supported">{{dropzone.supported}}</div>
<div class="active">{{dropzone.active}}</div>
<div class="queue-name">{{queue.name}}</div>
</FileDropzone>
`);

assert.dom('.supported').hasText('true');
assert.dom('.active').hasText('false');
assert.dom('.queue-name').hasText('test');
});
});
});

0 comments on commit 5d81caa

Please sign in to comment.