Skip to content

Commit 53920ad

Browse files
Merge pull request #120 from Garbanas/bugfix/upload-event-handling
Fix missing event reference causing exception in Firefox
2 parents 4504549 + 14c0cd6 commit 53920ad

File tree

5 files changed

+119
-92
lines changed

5 files changed

+119
-92
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ headertext | Text to be displayed inside the drop box | headertext="Drop files
147147
customstyle | Custom style class name to be used | customstyle="my-style"
148148
[disableIf] | Conditionally disable the dropzone | [disableIf]="condition"
149149
showBrowseBtn | Whether browse file button should be shown | showBrowseBtn="true"
150+
browseBtnLabel | The label of the browse file button | browseBtnLabel="Browse files"
150151

151152
## License
152153

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
<div id="dropZone" [className]="customstyle" [class.over]="dragoverflag"
1+
<div [className]="customstyle" [class.over]="dragoverflag"
22
(drop)="dropFiles($event)"
33
(dragover)="onDragOver($event)" (dragleave)="onDragLeave($event)">
44
<div class="content">
55
<ng-content></ng-content>
66
{{headertext}}
77
<div *ngIf="showBrowseBtn">
8-
<input type="file" id="selectedFile" (change)="uploadFiles($event)" multiple style="display: none;" />
9-
<input type="button" class="btn btn-primary btn-xs" value="Custom Btn " onclick="document.getElementById('selectedFile').click();" />
8+
<input type="file" #fileSelector (change)="uploadFiles($event)" multiple style="display: none;" />
9+
<input type="button" class="btn btn-primary btn-xs" value="{{browseBtnLabel}}" (click)="onBrowseButtonClick($event)" />
1010
</div>
1111
</div>
1212
</div>

src/lib/ngx-drop/file-drop.component.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
justify-content: center;
1313
align-items: center;
1414
}
15+
1516
.over{
1617
background-color: rgba(147, 147, 147, 0.5);
17-
}
18+
}

src/lib/ngx-drop/file-drop.component.ts

Lines changed: 111 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,37 @@
1-
import { Component, Input, Output, EventEmitter, NgZone, OnDestroy, Renderer } from '@angular/core';
1+
import {
2+
Component,
3+
Input,
4+
Output,
5+
EventEmitter,
6+
NgZone,
7+
OnDestroy,
8+
Renderer2,
9+
ViewChild,
10+
ElementRef
11+
} from '@angular/core';
212
import { timer, Subscription } from 'rxjs';
313

414
import { UploadFile } from './upload-file.model';
515
import { UploadEvent } from './upload-event.model';
6-
import { FileSystemFileEntry, FileSystemEntryMetadata, FileSystemEntry, FileSystemDirectoryEntry } from './dom.types';
16+
import { FileSystemFileEntry, FileSystemEntry, FileSystemDirectoryEntry } from './dom.types';
717

818
@Component({
919
selector: 'file-drop',
1020
templateUrl: './file-drop.component.html',
1121
styleUrls: ['./file-drop.component.scss']
1222
})
13-
14-
1523
export class FileComponent implements OnDestroy {
1624

1725
@Input()
18-
headertext: string = '';
26+
public headertext: string = '';
27+
@Input()
28+
public customstyle: string = 'drop-zone';
1929
@Input()
20-
customstyle: string = null;
30+
public disableIf: boolean = false;
2131
@Input()
22-
disableIf: boolean = false;
32+
public showBrowseBtn: boolean = false;
2333
@Input()
24-
showBrowseBtn: boolean = false;
34+
public browseBtnLabel: string = 'Browse files';
2535

2636
@Output()
2737
public onFileDrop: EventEmitter<UploadEvent> = new EventEmitter<UploadEvent>();
@@ -30,37 +40,34 @@ export class FileComponent implements OnDestroy {
3040
@Output()
3141
public onFileLeave: EventEmitter<any> = new EventEmitter<any>();
3242

33-
stack = [];
43+
@ViewChild('fileSelector')
44+
public fileSelector: ElementRef;
45+
46+
stack: string[] = [];
3447
files: UploadFile[] = [];
35-
subscription: Subscription;
48+
subscription: Subscription | null = null;
3649
dragoverflag: boolean = false;
3750

3851
globalDisable: boolean = false;
3952
globalStart: Function;
4053
globalEnd: Function;
4154

42-
numOfActiveReadEntries = 0
43-
44-
length;
45-
items;
55+
numOfActiveReadEntries = 0;
4656

4757
constructor(
4858
private zone: NgZone,
49-
private renderer: Renderer
59+
private renderer: Renderer2
5060
) {
51-
if (!this.customstyle) {
52-
this.customstyle = 'drop-zone';
53-
}
54-
this.globalStart = this.renderer.listen('document', 'dragstart', (evt) => {
61+
this.globalStart = this.renderer.listen('document', 'dragstart', (evt: Event) => {
5562
this.globalDisable = true;
5663
});
57-
this.globalEnd = this.renderer.listen('document', 'dragend', (evt) => {
64+
this.globalEnd = this.renderer.listen('document', 'dragend', (evt: Event) => {
5865
this.globalDisable = false;
5966
});
6067
}
6168

6269
public onDragOver(event: Event): void {
63-
if (!this.globalDisable && !this.disableIf) {
70+
if (!this.isDropzoneDisabled()) {
6471
if (!this.dragoverflag) {
6572
this.dragoverflag = true;
6673
this.onFileOver.emit(event);
@@ -70,7 +77,7 @@ export class FileComponent implements OnDestroy {
7077
}
7178

7279
public onDragLeave(event: Event): void {
73-
if (!this.globalDisable && !this.disableIf) {
80+
if (!this.isDropzoneDisabled()) {
7481
if (this.dragoverflag) {
7582
this.dragoverflag = false;
7683
this.onFileLeave.emit(event);
@@ -79,90 +86,97 @@ export class FileComponent implements OnDestroy {
7986
}
8087
}
8188

82-
dropFiles(event: any) {
83-
this.dragoverflag = false;
84-
event.dataTransfer.dropEffect = 'copy';
85-
if (event.dataTransfer.items) {
86-
this.length = event.dataTransfer.items.length;
87-
this.items = event.dataTransfer.items;
88-
} else {
89-
this.length = event.dataTransfer.files.length;
90-
this.items = event.dataTransfer.files;
89+
public dropFiles(event: DragEvent): void {
90+
if (!this.isDropzoneDisabled()) {
91+
this.dragoverflag = false;
92+
if (event.dataTransfer) {
93+
event.dataTransfer.dropEffect = 'copy';
94+
let items: FileList | DataTransferItemList;
95+
if (event.dataTransfer.items) {
96+
items = event.dataTransfer.items;
97+
} else {
98+
items = event.dataTransfer.files;
99+
}
100+
this.preventAndStop(event);
101+
this.checkFiles(items);
102+
}
91103
}
92-
this.checkFiles(event);
93104
}
94105

95-
uploadFiles(event: any) {
96-
if (event.srcElement) {
97-
this.items = event.srcElement.files;
98-
this.length = this.items.length;
99-
this.checkFiles(event);
106+
public onBrowseButtonClick(event: MouseEvent): void {
107+
if (this.fileSelector && this.fileSelector.nativeElement) {
108+
(this.fileSelector.nativeElement as HTMLInputElement).click();
100109
}
101110
}
102111

103-
checkFiles(event: any) {
104-
if (!this.globalDisable && !this.disableIf) {
105-
106-
for (let i = 0; i < this.length; i++) {
107-
let entry: FileSystemEntry;
108-
if(this.items[i].webkitGetAsEntry){
109-
entry = this.items[i].webkitGetAsEntry();
110-
}
111-
112-
if (!entry) {
113-
const file: File = this.items[i];
114-
if (file) {
115-
const fakeFileEntry: FileSystemFileEntry = {
116-
name: file.name,
117-
isDirectory: false,
118-
isFile: true,
119-
file: (callback: (filea: File) => void): void => {
120-
callback(file)
121-
}
122-
}
123-
const toUpload: UploadFile = new UploadFile(fakeFileEntry.name, fakeFileEntry);
124-
this.addToQueue(toUpload);
125-
}
126-
} else {
127-
if (entry.isFile) {
128-
const toUpload: UploadFile = new UploadFile(entry.name, entry);
129-
this.addToQueue(toUpload);
130-
} else if (entry.isDirectory) {
131-
this.traverseFileTree(entry, entry.name);
132-
}
133-
}
112+
public uploadFiles(event: Event): void {
113+
if (!this.isDropzoneDisabled()) {
114+
if (event.target) {
115+
const items = (event.target as HTMLInputElement).files || ([] as any);
116+
this.checkFiles(items);
134117
}
118+
}
119+
}
135120

136-
this.preventAndStop(event);
121+
private checkFiles(items: FileList | DataTransferItemList): void {
122+
for (let i = 0; i < items.length; i++) {
123+
const item = items[i];
124+
let entry: FileSystemEntry | null = null;
125+
if (this.canGetAsEntry(item)) {
126+
entry = item.webkitGetAsEntry();
127+
}
137128

138-
const timerObservable = timer(200, 200);
139-
this.subscription = timerObservable.subscribe(t => {
140-
if (this.files.length > 0 && this.numOfActiveReadEntries === 0) {
141-
this.onFileDrop.emit(new UploadEvent(this.files));
142-
this.files = [];
129+
if (!entry) {
130+
if (item) {
131+
const fakeFileEntry: FileSystemFileEntry = {
132+
name: (item as File).name,
133+
isDirectory: false,
134+
isFile: true,
135+
file: (callback: (filea: File) => void): void => {
136+
callback(item as File)
137+
}
138+
};
139+
const toUpload: UploadFile = new UploadFile(fakeFileEntry.name, fakeFileEntry);
140+
this.addToQueue(toUpload);
143141
}
144-
});
142+
} else {
143+
if (entry.isFile) {
144+
const toUpload: UploadFile = new UploadFile(entry.name, entry);
145+
this.addToQueue(toUpload);
146+
} else if (entry.isDirectory) {
147+
this.traverseFileTree(entry, entry.name);
148+
}
149+
}
145150
}
146151

152+
const timerObservable = timer(200, 200);
153+
if (this.subscription) {
154+
this.subscription.unsubscribe();
155+
}
156+
this.subscription = timerObservable.subscribe(t => {
157+
if (this.files.length > 0 && this.numOfActiveReadEntries === 0) {
158+
this.onFileDrop.emit(new UploadEvent(this.files));
159+
this.files = [];
160+
}
161+
});
147162
}
148163

149-
private traverseFileTree(item: FileSystemEntry, path: string) {
150-
164+
private traverseFileTree(item: FileSystemEntry, path: string): void {
151165
if (item.isFile) {
152166
const toUpload: UploadFile = new UploadFile(path, item);
153167
this.files.push(toUpload);
154168
this.zone.run(() => {
155-
this.popToStack();
169+
this.popFromStack();
156170
});
157171
} else {
158172
this.pushToStack(path);
159173
path = path + '/';
160174
const dirReader = (item as FileSystemDirectoryEntry).createReader();
161-
let entries = [];
175+
let entries: FileSystemEntry[] = [];
162176
const thisObj = this;
163177

164178
const readEntries = function () {
165-
thisObj.numOfActiveReadEntries++
179+
thisObj.numOfActiveReadEntries++;
166180
dirReader.readEntries(function (res) {
167181
if (!res.length) {
168182
// add empty folders
@@ -179,7 +193,7 @@ export class FileComponent implements OnDestroy {
179193
}
180194
}
181195
thisObj.zone.run(() => {
182-
thisObj.popToStack();
196+
thisObj.popFromStack();
183197
});
184198
} else {
185199
// continue with the reading
@@ -194,30 +208,39 @@ export class FileComponent implements OnDestroy {
194208
}
195209
}
196210

197-
private addToQueue(item: UploadFile) {
211+
private canGetAsEntry(item: any): item is DataTransferItem {
212+
return !!item.webkitGetAsEntry;
213+
}
214+
215+
private isDropzoneDisabled(): boolean {
216+
return (this.globalDisable || this.disableIf);
217+
}
218+
219+
private addToQueue(item: UploadFile): void {
198220
this.files.push(item);
199221
}
200222

201-
pushToStack(str) {
223+
pushToStack(str: string): void {
202224
this.stack.push(str);
203225
}
204226

205-
popToStack() {
206-
const value = this.stack.pop();
227+
popFromStack(): string | undefined {
228+
return this.stack.pop();
207229
}
208230

209-
private clearQueue() {
231+
private clearQueue(): void {
210232
this.files = [];
211233
}
212234

213-
private preventAndStop(event) {
235+
private preventAndStop(event: Event): void {
214236
event.stopPropagation();
215237
event.preventDefault();
216238
}
217239

218-
ngOnDestroy() {
240+
ngOnDestroy(): void {
219241
if (this.subscription) {
220242
this.subscription.unsubscribe();
243+
this.subscription = null;
221244
}
222245
this.globalStart();
223246
this.globalEnd();

tsconfig.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
"moduleResolution": "node",
88
"emitDecoratorMetadata": true,
99
"experimentalDecorators": true,
10+
"strictNullChecks": true,
11+
"noImplicitAny": true,
1012
"target": "es5",
1113
"typeRoots": [
1214
"node_modules/@types"

0 commit comments

Comments
 (0)