ngxc-file-uploader
Version:
Ngxc file uploader is an Angular 9/10 + file uploader
365 lines (359 loc) • 17.3 kB
JavaScript
import { EventEmitter, Component, ViewEncapsulation, Input, Output, NgModule } from '@angular/core';
import { HttpEventType, HttpClient, HttpClientModule } from '@angular/common/http';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { CommonModule } from '@angular/common';
class NgxFileUploaderComponent {
/**
* constructor
*
* @param {HttpClient} http
*
*/
constructor(http) {
this.http = http;
this.resetUpload = false;
// Outputs
this.ApiResponse = new EventEmitter();
this.uploadInitiated = new EventEmitter();
this.everythingDone = new EventEmitter();
this.allowedFiles = [];
this.notAllowedFiles = [];
this.Caption = [];
this.isAllowedFileSingle = true;
this.progressBarShow = false;
this.enableUploadBtn = false;
this.uploadMsg = false;
this.afterUpload = false;
this.uploadStarted = false;
this.currentUploads = [];
this.fileNameIndex = true;
this.idDate = +new Date();
this.destroy = new Subject();
}
/**
* ngOnChanges
*
* @param {SimpleChanges} changes
*
* @return {void}
*/
ngOnChanges(changes) {
// Track changes in Configuration and see if user has even provided Configuration.
if (changes.config && this.config) {
// Assign User Configurations to Library Properties.
this.theme = this.config.theme || '';
this.id =
this.config.id ||
parseInt((this.idDate / 10000).toString().split('.')[1], 10) +
Math.floor(Math.random() * 20) * 10000;
this.hideProgressBar = this.config.hideProgressBar || false;
this.hideResetBtn = this.config.hideResetBtn || false;
this.hideSelectBtn = this.config.hideSelectBtn || false;
this.maxSize = (this.config.maxSize || 20) * 1024000; // mb to bytes.
this.uploadAPI = this.config.uploadAPI.url;
this.method = this.config.uploadAPI.method || 'POST';
this.formatsAllowed =
this.config.formatsAllowed || '.jpg,.png,.pdf,.docx,.txt,.gif,.jpeg';
this.multiple = this.config.multiple || false;
this.headers = this.config.uploadAPI.headers || {};
this.params = this.config.uploadAPI.params || {};
this.responseType = this.config.uploadAPI.responseType || null;
this.fileNameIndex = this.config.fileNameIndex === false ? false : true;
this.replaceTexts = {
selectFileBtn: this.multiple ? 'Select Files' : 'Select File',
resetBtn: 'Reset',
uploadBtn: 'Upload',
dragNDropBox: 'Drag N Drop',
pleaseWaitMessage: 'Please wait until file is uploaded',
attachPinBtn: this.multiple ? 'Attach Files...' : 'Attach File...',
afterUploadMsg_success: 'Successfully Uploaded !',
afterUploadMsg_error: 'Upload Failed !',
sizeLimit: 'Size Limit',
}; // default replaceText.
if (this.config.replaceTexts) {
// updated replaceText if user has provided any.
this.replaceTexts = Object.assign(Object.assign({}, this.replaceTexts), this.config.replaceTexts);
}
}
// Reset when resetUpload value changes from false to true.
if (changes.resetUpload) {
if (changes.resetUpload.currentValue === true) {
this.resetFileUpload();
}
}
}
/**
* ngOnDestroy
*
* @return {void}
*/
ngOnDestroy() {
this.destroy.next();
this.destroy.complete();
}
/**
* resetFileUpload
* Reset following properties.
*
* @return {void}
*/
resetFileUpload() {
this.allowedFiles = [];
this.Caption = [];
this.notAllowedFiles = [];
this.uploadMsg = false;
this.enableUploadBtn = false;
}
/**
* onChange hook
* - Check when user selects files.
*
* @param {any} event
*
* @return {void}
*/
onChange(event) {
this.notAllowedFiles = [];
const fileExtRegExp = /(?:\.([^.]+))?$/;
let fileList;
if (this.afterUpload || !this.multiple) {
this.allowedFiles = [];
this.Caption = [];
this.afterUpload = false;
}
if (event.type === 'drop') {
fileList = event.dataTransfer.files;
}
else {
fileList = event.target.files || event.srcElement.files;
}
// 'forEach' does not exist on 'filelist' that's why this good old 'for' is used.
for (let i = 0; i < fileList.length; i++) {
const currentFileExt = fileExtRegExp
.exec(fileList[i].name)[1]
.toLowerCase(); // Get file extension.
const isFormatValid = this.formatsAllowed.includes(currentFileExt);
const isSizeValid = fileList[i].size <= this.maxSize;
// Check whether current file format and size is correct as specified in the configurations.
if (isFormatValid && isSizeValid) {
this.allowedFiles.push(fileList[i]);
}
else {
this.notAllowedFiles.push({
fileName: fileList[i].name,
fileSize: this.convertSize(fileList[i].size),
errorMsg: !isFormatValid ? 'Invalid format' : 'Invalid size',
});
}
}
// If there's any allowedFiles.
if (this.allowedFiles.length > 0) {
this.enableUploadBtn = true;
// Upload the files directly if theme is attach pin (as upload btn is not there for this theme).
if (this.theme === 'attachPin') {
this.uploadFiles();
}
}
else {
this.enableUploadBtn = false;
}
this.uploadMsg = false;
this.uploadStarted = false;
this.uploadPercent = 0;
event.target.value = null;
}
/**
* uploadFiles
*
* @return {void}
*/
uploadFiles() {
this.uploadInitiated.emit(true);
this.progressBarShow = true;
this.uploadStarted = true;
this.notAllowedFiles = [];
let isError = false;
this.isAllowedFileSingle = this.allowedFiles.length <= 1;
const formData = new FormData();
// Add data to be sent in this request
this.allowedFiles.forEach((file, i) => {
formData.append(this.Caption[i] || 'file' + (this.fileNameIndex ? i : ''), this.allowedFiles[i]);
});
const options = {
headers: this.headers,
params: this.params,
};
if (this.responseType) {
options.responseType = this.responseType;
}
this.httpCallSubscription = this.http
.request(this.method.toUpperCase(), this.uploadAPI, Object.assign({ body: formData, reportProgress: true, observe: 'events' }, options)).pipe(takeUntil(this.destroy)).subscribe((event) => {
// Upload Progress
if (event.type === HttpEventType.UploadProgress) {
this.enableUploadBtn = false; // button should be disabled if process uploading
const currentDone = event.loaded / event.total;
this.uploadPercent = Math.round((event.loaded / event.total) * 100);
}
else if (event.type === HttpEventType.Response) {
if (event.status === 200 || event.status === 201) {
// Success
this.progressBarShow = false;
this.enableUploadBtn = false;
this.uploadStarted = false;
this.uploadInitiated.emit(false);
this.uploadMsg = true;
this.afterUpload = true;
if (!isError) {
this.uploadMsgText = this.replaceTexts.afterUploadMsg_success;
this.uploadMsgClass = 'text-success lead';
}
}
else {
// Failure
isError = true;
this.handleErrors();
}
this.ApiResponse.emit(event);
}
else {
// console.log('Event Other: ', event);
}
}, (error) => {
// Failure
isError = true;
this.handleErrors();
this.ApiResponse.emit(error);
});
}
/**
* handleErrors
*
* @return {void}
*/
handleErrors() {
this.progressBarShow = false;
this.enableUploadBtn = false;
this.uploadMsg = true;
this.afterUpload = true;
this.uploadMsgText = this.replaceTexts.afterUploadMsg_error;
this.uploadMsgClass = 'text-danger lead';
this.uploadStarted = false;
this.uploadInitiated.emit(false);
}
/**
* removeFile
*
* @param {any} i
* @param {any} sfNa
*
* @return {void}
*/
removeFile(i, sfNa) {
if (sfNa === 'sf') {
this.allowedFiles.splice(i, 1);
this.Caption.splice(i, 1);
}
else {
this.notAllowedFiles.splice(i, 1);
}
if (this.allowedFiles.length === 0) {
this.enableUploadBtn = false;
}
}
/**
* convertSize
*
* @param {number} fileSize
*
* @return {string}
*/
convertSize(fileSize) {
return fileSize < 1024000
? (fileSize / 1024).toFixed(2) + ' KB'
: (fileSize / 1024000).toFixed(2) + ' MB';
}
/**
* attachpinOnclick
*
* @return {void}
*/
attachpinOnclick() {
const element = document.getElementById('sel' + this.id);
if (element !== null) {
element.click();
}
}
/**
* drop
*
* @param {any} event
*
* @return {void}
*/
drop(event) {
event.stopPropagation();
event.preventDefault();
this.onChange(event);
}
/**
* allowDrop
*
* @param {any} event
*
* @return {void}
*/
allowDrop(event) {
event.stopPropagation();
event.preventDefault();
event.dataTransfer.dropEffect = 'copy';
}
/**
* cancelApiCall
*
* @return {void}
*/
cancelApiCall() {
if (this.httpCallSubscription) {
this.httpCallSubscription.unsubscribe();
}
}
}
NgxFileUploaderComponent.decorators = [
{ type: Component, args: [{
selector: 'ngx-file-uploader',
template: "<div class=\"container\" *ngIf=\"(theme !== 'attachPin')\" id=\"default\">\n\n <!-- Drag n Drop theme Starts -->\n <div *ngIf=\"!uploadStarted && theme == 'dragNDrop'\" id=\"dragNDrop\"\n [ngClass]=\"(hideSelectBtn && hideResetBtn) ? null : 'dragNDropBtmPad'\" class=\"dragNDrop\">\n <div style=\"position:relative;\">\n <div id=\"div1\" class=\"div1 afu-dragndrop-box\" (drop)=\"drop($event)\" (dragover)=\"allowDrop($event)\">\n <p class=\"afu-dragndrop-text\">{{replaceTexts?.dragNDropBox}}</p>\n </div>\n <!-- <span class='label label-info' id=\"upload-file-info{{id}}\">{{allowedFiles[0]?.name}}</span> -->\n </div>\n </div>\n <!-- Drag n Drop theme Ends -->\n <label for=\"sel{{id}}\" class=\"btn btn-primary btn-sm afu-select-btn\"\n *ngIf=\"!uploadStarted && !hideSelectBtn\">{{replaceTexts?.selectFileBtn}}</label>\n <input type=\"file\" id=\"sel{{id}}\" style=\"display: none\" *ngIf=\"!hideSelectBtn\" (change)=\"onChange($event)\"\n title=\"Select file\" name=\"files[]\" [accept]=formatsAllowed [attr.multiple]=\"multiple ? '' : null\" />\n <button class=\"btn btn-info btn-sm resetBtn afu-reset-btn\" (click)=\"resetFileUpload()\"\n *ngIf=\"!hideResetBtn\">{{replaceTexts?.resetBtn}}</button>\n <br *ngIf=\"!hideSelectBtn\">\n <p class=\"constraints-info afu-constraints-info\">({{formatsAllowed}}) {{replaceTexts?.sizeLimit}}: {{(convertSize(maxSize))}}\n </p>\n <!--Allowed file list-->\n <div class=\"row afu-valid-file\" *ngFor=\"let sf of allowedFiles;let i=index\">\n <p class=\"col-xs-3 textOverflow\"><span class=\"text-primary\">{{sf.name}}</span></p>\n <p class=\"col-xs-3 padMarg sizeC\"><strong>({{convertSize(sf.size)}})</strong> </p>\n <!-- <input class=\"col-xs-3 progress caption\" type=\"text\" placeholder=\"Caption..\" [(ngModel)]=\"Caption[i]\" *ngIf=\"!uploadStarted\"/> -->\n <div class=\"progress col-xs-3 padMarg afu-progress-bar\" *ngIf=\"isAllowedFileSingle && progressBarShow && !hideProgressBar\">\n <span class=\"progress-bar progress-bar-success\" role=\"progressbar\"\n [ngStyle]=\"{'width':uploadPercent+'%'}\">{{uploadPercent}}%</span>\n </div>\n <a class=\"col-xs-1\" role=\"button\" (click)=\"removeFile(i,'sf')\" *ngIf=\"!uploadStarted\"><i class=\"fa fa-times\"></i></a>\n </div>\n <!--Not Allowed file list-->\n <div class=\"row text-danger afu-invalid-file\" *ngFor=\"let na of notAllowedFiles;let j=index\">\n <p class=\"col-xs-3 textOverflow\"><span>{{na['fileName']}}</span></p>\n <p class=\"col-xs-3 padMarg sizeC\"><strong>({{na['fileSize']}})</strong></p>\n <p class=\"col-xs-3 \">{{na['errorMsg']}}</p>\n <a class=\"col-xs-1 delFileIcon\" role=\"button\" (click)=\"removeFile(j,'na')\" *ngIf=\"!uploadStarted\"> <i\n class=\"fa fa-times\"></i></a>\n </div>\n\n <p *ngIf=\"uploadMsg\" class=\"{{uploadMsgClass}} + afu-upload-status\">{{uploadMsgText}}<p>\n <div *ngIf=\"!isAllowedFileSingle && progressBarShow && !hideProgressBar\">\n <div class=\"progress col-xs-4 padMarg afu-progress-bar\">\n <span class=\"progress-bar progress-bar-success\" role=\"progressbar\"\n [ngStyle]=\"{'width':uploadPercent+'%'}\">{{uploadPercent}}%</span>\n </div>\n <br />\n <br />\n </div>\n <button *ngIf=\"!uploadStarted && enableUploadBtn\" class=\"btn btn-success afu-upload-btn\" type=\"button\" (click)=\"uploadFiles()\"\n >{{replaceTexts?.uploadBtn}}</button>\n <div *ngIf=\"uploadStarted\" class=\"uploading-message\" >{{replaceTexts?.pleaseWaitMessage}}</div>\n <br>\n</div>\n\n<!--/////////////////////////// ATTACH PIN THEME //////////////////////////////////////////////////////////-->\n<div *ngIf=\"theme == 'attachPin'\" id=\"attachPin\">\n <div style=\"position:relative;padding-left:6px\">\n <a class='btn up_btn afu-attach-pin' (click)=\"attachpinOnclick()\">\n {{replaceTexts?.attachPinBtn}}\n <i class=\"fa fa-paperclip\" aria-hidden=\"true\"></i>\n <!-- <p style=\"margin-top:10px\">({{formatsAllowed}}) Size limit- {{(convertSize(maxSize))}}</p> -->\n <input type=\"file\" id=\"sel{{id}}\" (change)=\"onChange($event)\" style=\"display: none\" title=\"Select file\"\n name=\"files[]\" [accept]=formatsAllowed [attr.multiple]=\"multiple ? '' : null\" />\n <br>\n </a>\n \n <span class='label label-info' id=\"upload-file-info{{id}}\">{{allowedFiles[0]?.name}}</span>\n </div>\n</div>\n\n",
encapsulation: ViewEncapsulation.None,
styles: [".constraints-info{font-style:italic;margin-top:10px}.padMarg{margin-bottom:0;padding:0}.caption{margin-right:5px}.textOverflow{overflow:hidden;padding-right:0;text-overflow:ellipsis;white-space:nowrap}.up_btn{background-color:transparent;border:2px solid #5c5b5b;border-radius:22px;color:#000}.delFileIcon{color:#ce0909;text-decoration:none}.uploading-message{margin:15px 0}.dragNDrop .div1{border:2px dashed #5c5b5b;display:border-box;height:6rem;width:20rem}.dragNDrop .div1>p{color:#5c5b5b;font-weight:700;margin-top:1.4em;text-align:center}.dragNDropBtmPad{padding-bottom:2rem}@media screen and (max-width:620px){.caption{padding:0}}@media screen and (max-width:510px){.sizeC{width:25%}}@media screen and (max-width:260px){.caption,.sizeC{font-size:10px}}.resetBtn{margin-left:3px}"]
},] }
];
NgxFileUploaderComponent.ctorParameters = () => [
{ type: HttpClient }
];
NgxFileUploaderComponent.propDecorators = {
config: [{ type: Input }],
resetUpload: [{ type: Input }],
ApiResponse: [{ type: Output }],
uploadInitiated: [{ type: Output }],
everythingDone: [{ type: Output }]
};
class NgxFileUploaderModule {
}
NgxFileUploaderModule.decorators = [
{ type: NgModule, args: [{
imports: [
CommonModule,
HttpClientModule,
],
declarations: [NgxFileUploaderComponent],
exports: [NgxFileUploaderComponent]
},] }
];
/*
* Public API Surface of ngx-file-uploader
*/
/**
* Generated bundle index. Do not edit.
*/
export { NgxFileUploaderComponent, NgxFileUploaderModule };
//# sourceMappingURL=ngxc-file-uploader.js.map