Compare commits

..
Author SHA1 Message Date
Matthew McSparranandGitHub f577becb29 Updated docs
The README has been updated to include information on adding extra fields to uploads when uploading multiple files.
2017-12-20 10:39:37 -05:00
8 changed files with 109 additions and 16835 deletions
+4 -7
View File
@@ -1,13 +1,11 @@
language: node_js
node_js:
- "10.15.0"
sudo: required
services:
- xvfb
- "6"
before_install:
- google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost &
- export CHROME_BIN=chromium-browser
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
script:
- npm run pretest
@@ -17,7 +15,6 @@ after_success:
- ./node_modules/.bin/codecov
addons:
chrome: stable
firefox: "latest"
apt:
sources:
+57
View File
@@ -43,6 +43,63 @@ Easy to use Angular2 directives for files upload ([demo](http://valor-software.g
```public uploader:FileUploader = new FileUploader({url: URL}); ```
## Using ***ng2-file-upload*** with multiple uploads and other parameters
To add extra information (i.e. title, description) when uploading multiple files:
In your template:
```
<tr *ngFor="let item of uploader.queue">
<td><strong>{{ item?.file?.name }}</strong></td>
**<td><input type="text" [(ngModel)]="item.formData['title']" placeholder="Title of File"></td>**
**<td><input type="text" [(ngModel)]="item.formData['description']" placeholder="File Description"></td>**
<td nowrap>{{ item?.file?.size/1024/1024 | number:'.2' }} MB</td>
<td >
<div class="progress" style="margin-bottom: 0;">
<div class="progress-bar" role="progressbar" [ngStyle]="{ 'width': item.progress + '%' }"></div>
</div>
</td>
<td class="text-center">
<span *ngIf="item.isSuccess"><i class="glyphicon glyphicon-ok"></i></span>
<span *ngIf="item.isCancel"><i class="glyphicon glyphicon-ban-circle"></i></span>
<span *ngIf="item.isError"><i class="glyphicon glyphicon-remove"></i></span>
</td>
<td nowrap>
<button type="button" class="btn btn-success btn-xs"
(click)="item.upload()" [disabled]="item.isReady || item.isUploading || item.isSuccess">
<span class="glyphicon glyphicon-upload"></span> Upload
</button>
<button type="button" class="btn btn-warning btn-xs"
(click)="item.cancel()" [disabled]="!item.isUploading">
<span class="glyphicon glyphicon-ban-circle"></span> Cancel
</button>
<button type="button" class="btn btn-danger btn-xs"
(click)="item.remove()">
<span class="glyphicon glyphicon-trash"></span> Remove
</button>
</td>
</tr>
```
You can add inputs outside of a form, and use ```[(ngModel)]``` to add keys to the ```item``` object.
```
**<td><input type="text" [(ngModel)]="item.formData['title']" placeholder="Title of File"></td>**
**<td><input type="text" [(ngModel)]="item.formData['description']" placeholder="File Description"></td>**
```
Inside your upload component, add the following:
```
this.uploader.onBuildItemForm = (item, form) => {
form.append('title', item.formData.title);
form.append('description', item.formData.description);
console.log(item);
};
```
If you are using `Node.js` with `multer`, the extra information will not show up in the `req.files` object. You will have to pull the data from the `req.body` object.
## API for `ng2FileSelect`
### Properties
-1
View File
@@ -48,7 +48,6 @@ module.exports = function (config) {
if (process.env.TRAVIS) {
configuration.browsers = ['Chrome_travis_ci'];
configuration.singleRun = true;
}
if (process.env.SAUCE) {
-16759
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -84,8 +84,8 @@
"jasmine-core": "2.5.2",
"jasmine-data-provider": "2.2.0",
"jasmine-spec-reporter": "3.2.0",
"karma": "^4.2.0",
"karma-chrome-launcher": "^3.1.0",
"karma": "1.4.0",
"karma-chrome-launcher": "^2.0.0",
"karma-cli": "^1.0.1",
"karma-coverage-istanbul-reporter": "^1.3.0",
"karma-jasmine": "^1.0.2",
+10 -15
View File
@@ -1,14 +1,12 @@
import { Directive, ElementRef, EventEmitter, HostListener, Input, Output } from '@angular/core';
import { Directive, EventEmitter, ElementRef, HostListener, Input, Output } from '@angular/core';
import { FileUploader, FileUploaderOptions, FilterFunction } from './file-uploader.class';
import { FileUploader, FileUploaderOptions } from './file-uploader.class';
@Directive({ selector: '[ng2FileDrop]' })
export class FileDropDirective {
@Input() public uploader: FileUploader;
// tslint:disable-next-line:no-input-rename
@Input('ng2FileFilter') public filter: FilterFunction['fn'];
@Output() public fileOver: EventEmitter<any> = new EventEmitter();
@Output() public onFileDrop: EventEmitter<FileList> = new EventEmitter<FileList>();
@Output() public onFileDrop: EventEmitter<File[]> = new EventEmitter<File[]>();
protected element: ElementRef;
@@ -24,7 +22,7 @@ export class FileDropDirective {
return {};
}
@HostListener('drop', ['$event'])
@HostListener('drop', [ '$event' ])
public onDrop(event: any): void {
let transfer = this._getTransfer(event);
if (!transfer) {
@@ -32,17 +30,14 @@ export class FileDropDirective {
}
let options = this.getOptions();
let filters = typeof this.filter === 'function' ? [{
name: 'ng2FileDropDirectiveFilter',
fn: this.filter
}, ...options.filters] : this.getFilters();
let filters = this.getFilters();
this._preventAndStop(event);
this.uploader.addToQueue(transfer.files, options, filters);
this.fileOver.emit(false);
this.onFileDrop.emit(transfer.files);
}
@HostListener('dragover', ['$event'])
@HostListener('dragover', [ '$event' ])
public onDragOver(event: any): void {
let transfer = this._getTransfer(event);
if (!this._haveFiles(transfer.types)) {
@@ -54,10 +49,10 @@ export class FileDropDirective {
this.fileOver.emit(true);
}
@HostListener('dragleave', ['$event'])
@HostListener('dragleave', [ '$event' ])
public onDragLeave(event: any): any {
if ((this as any).element) {
if (event.currentTarget === (this as any).element[0]) {
if (event.currentTarget === (this as any).element[ 0 ]) {
return;
}
}
@@ -66,11 +61,11 @@ export class FileDropDirective {
this.fileOver.emit(false);
}
protected _getTransfer(event: any): DragEvent['dataTransfer'] {
protected _getTransfer(event: any): any {
return event.dataTransfer ? event.dataTransfer : event.originalEvent.dataTransfer; // jQuery fix;
}
protected _preventAndStop(event: Event): any {
protected _preventAndStop(event: any): any {
event.preventDefault();
event.stopPropagation();
}
+3 -8
View File
@@ -1,12 +1,10 @@
import { Directive, ElementRef, EventEmitter, HostListener, Input, Output } from '@angular/core';
import { Directive, EventEmitter, ElementRef, Input, HostListener, Output } from '@angular/core';
import { FileUploader, FilterFunction } from './file-uploader.class';
import { FileUploader } from './file-uploader.class';
@Directive({ selector: '[ng2FileSelect]' })
export class FileSelectDirective {
@Input() public uploader: FileUploader;
// tslint:disable-next-line:no-input-rename
@Input('ng2FileFilter') public filter: FilterFunction['fn'];
@Output() public onFileSelected: EventEmitter<File[]> = new EventEmitter<File[]>();
protected element: ElementRef;
@@ -31,10 +29,7 @@ export class FileSelectDirective {
public onChange(): any {
let files = this.element.nativeElement.files;
let options = this.getOptions();
let filters = typeof this.filter === 'function' ? [{
name: 'ng2FileSelectDirectiveFilter',
fn: this.filter
}, ...options.filters] : this.getFilters();
let filters = this.getFilters();
this.uploader.addToQueue(files, options, filters);
this.onFileSelected.emit(files);
+33 -43
View File
@@ -12,11 +12,11 @@ export interface Headers {
value: string;
}
export type ParsedResponseHeaders = { [headerFieldName: string]: string };
export type ParsedResponseHeaders = { [ headerFieldName: string ]: string };
export interface FilterFunction {
name: string;
fn(this: FileUploader, item?: FileLikeObject, options?: FileUploaderOptions, queueIndex?: number): boolean | Promise<boolean>;
export type FilterFunction = {
name: string,
fn: (item?: FileLikeObject, options?: FileUploaderOptions) => boolean
};
export interface FileUploaderOptions {
@@ -35,7 +35,7 @@ export interface FileUploaderOptions {
disableMultipart?: boolean;
itemAlias?: string;
authTokenHeader?: string;
additionalParameter?: { [key: string]: any };
additionalParameter?: { [ key: string ]: any };
parametersBeforeFiles?: boolean;
formatDataFunction?: Function;
formatDataFunctionIsAsync?: boolean;
@@ -89,39 +89,35 @@ export class FileUploader {
this.options.filters.unshift({ name: 'mimeType', fn: this._mimeTypeFilter });
}
for (const q of this.queue) {
q.url = this.options.url;
for (let i = 0; i < this.queue.length; i++) {
this.queue[ i ].url = this.options.url;
}
}
public async addToQueue(files: FileList, options?: FileUploaderOptions, filters?: FilterFunction[] | string): Promise<void> {
public addToQueue(files: File[], options?: FileUploaderOptions, filters?: FilterFunction[] | string): void {
let list: File[] = [];
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < files.length; i++) {
list.push(files[i]);
for (let file of files) {
list.push(file);
}
let arrayOfFilters = this._getFilters(filters);
let count = this.queue.length;
let addedFileItems: FileItem[] = [];
let idx = 0;
for (const some of list) {
list.map((some: File) => {
if (!options) {
options = this.options;
}
let temp = new FileLikeObject(some);
if (await this._isValidFile(temp, arrayOfFilters, options, idx++)) {
if (this._isValidFile(temp, arrayOfFilters, options)) {
let fileItem = new FileItem(this, some, options);
addedFileItems.push(fileItem);
this.queue.push(fileItem);
this._onAfterAddingFile(fileItem);
} else {
let filter = arrayOfFilters[this._failFilterIndex];
let filter = arrayOfFilters[ this._failFilterIndex ];
this._onWhenAddingFileFailed(temp, filter, options);
}
}
});
if (this.queue.length !== count) {
this._onAfterAddingAll(addedFileItems);
this.progress = this._getTotalProgress();
@@ -134,7 +130,7 @@ export class FileUploader {
public removeFromQueue(value: FileItem): void {
let index = this.getIndexOfItem(value);
let item = this.queue[index];
let item = this.queue[ index ];
if (item.isUploading) {
item.cancel();
}
@@ -144,26 +140,26 @@ export class FileUploader {
public clearQueue(): void {
while (this.queue.length) {
this.queue[0].remove();
this.queue[ 0 ].remove();
}
this.progress = 0;
}
public uploadItem(value: FileItem): void {
let index = this.getIndexOfItem(value);
let item = this.queue[index];
let item = this.queue[ index ];
let transport = this.options.isHTML5 ? '_xhrTransport' : '_iframeTransport';
item._prepareToUploading();
if (this.isUploading) {
return;
}
this.isUploading = true;
(this as any)[transport](item);
(this as any)[ transport ](item);
}
public cancelItem(value: FileItem): void {
let index = this.getIndexOfItem(value);
let item = this.queue[index];
let item = this.queue[ index ];
let prop = this.options.isHTML5 ? item._xhr : item._form;
if (item && item.isUploading) {
prop.abort();
@@ -176,7 +172,7 @@ export class FileUploader {
return;
}
items.map((item: FileItem) => item._prepareToUploading());
items[0].upload();
items[ 0 ].upload();
}
public cancelAll(): void {
@@ -279,7 +275,7 @@ export class FileUploader {
public _onCompleteItem(item: FileItem, response: string, status: number, headers: ParsedResponseHeaders): void {
item._onComplete(response, status, headers);
this.onCompleteItem(item, response, status, headers);
let nextItem = this.getReadyItems()[0];
let nextItem = this.getReadyItems()[ 0 ];
this.isUploading = false;
if (nextItem) {
nextItem.upload();
@@ -293,7 +289,7 @@ export class FileUploader {
protected _headersGetter(parsedHeaders: ParsedResponseHeaders): any {
return (name: any): any => {
if (name) {
return parsedHeaders[name.toLowerCase()] || void 0;
return parsedHeaders[ name.toLowerCase() ] || void 0;
}
return parsedHeaders;
};
@@ -320,7 +316,7 @@ export class FileUploader {
// For AWS, Additional Parameters must come BEFORE Files
if (this.options.additionalParameter !== undefined) {
Object.keys(this.options.additionalParameter).forEach((key: string) => {
let paramVal = this.options.additionalParameter[key];
let paramVal = this.options.additionalParameter[ key ];
// Allow an additional parameter to include the filename
if (typeof paramVal === 'string' && paramVal.indexOf('{{file_name}}') >= 0) {
paramVal = paramVal.replace('{{file_name}}', item.file.name);
@@ -345,7 +341,7 @@ export class FileUploader {
let response = this._transformResponse(xhr.response, headers);
let gist = this._isSuccessCode(xhr.status) ? 'Success' : 'Error';
let method = '_on' + gist + 'Item';
(this as any)[method](item, response, xhr.status, headers);
(this as any)[ method ](item, response, xhr.status, headers);
this._onCompleteItem(item, response, xhr.status, headers);
};
xhr.onerror = () => {
@@ -375,11 +371,11 @@ export class FileUploader {
if (this.authToken) {
xhr.setRequestHeader(this.authTokenHeader, this.authToken);
}
xhr.onreadystatechange = function (): void {
if (xhr.readyState === XMLHttpRequest.DONE) {
that.response.emit(xhr.responseText);
xhr.onreadystatechange = function () {
if (xhr.readyState == XMLHttpRequest.DONE) {
that.response.emit(xhr.responseText)
}
};
}
if (this.options.formatDataFunctionIsAsync) {
sendable.then(
(result: any) => xhr.send(JSON.stringify(result))
@@ -424,18 +420,12 @@ export class FileUploader {
return this.options.queueLimit === undefined || this.queue.length < this.options.queueLimit;
}
protected async _isValidFile(file: FileLikeObject, filters: FilterFunction[], options: FileUploaderOptions, queueIndex: number): Promise<boolean> {
protected _isValidFile(file: FileLikeObject, filters: FilterFunction[], options: FileUploaderOptions): boolean {
this._failFilterIndex = -1;
for (const filter of filters) {
return !filters.length ? true : filters.every((filter: FilterFunction) => {
this._failFilterIndex++;
if (!(await Promise.resolve(filter.fn.call(this, file, options, queueIndex)))) {
return false;
}
}
return true;
return filter.fn.call(this, file, options);
});
}
protected _isSuccessCode(status: number): boolean {
@@ -459,7 +449,7 @@ export class FileUploader {
key = line.slice(0, i).trim().toLowerCase();
val = line.slice(i + 1).trim();
if (key) {
parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
parsed[ key ] = parsed[ key ] ? parsed[ key ] + ', ' + val : val;
}
});
return parsed;