Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #15435 - FileUpload | Templating enhancements #15508

Merged
merged 1 commit into from
May 9, 2024
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
74 changes: 73 additions & 1 deletion src/app/components/fileupload/fileupload.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@ export interface UploadEvent {
*/
originalEvent: HttpEvent<any>;
}
/**
* Remove uploaded file event.
* @group Events
*/
export interface RemoveUploadedFileEvent {
/**
* Removed file.
*/
file: any;
/**
* Uploaded files.
*/
files: any[];
}
/**
* Form data event.
* @group Events
Expand Down Expand Up @@ -128,6 +142,31 @@ export interface FileUploadTemplates {
* Custom template of file.
*/
file(): TemplateRef<any>;
/**
* Custom template of file.
*/
header(context: {
/**
* File list.
*/
$implicit: any;
/**
* Uploaded files list.
*/
uploadedFiles: any;
/**
* Callback to invoke on choose button click.
*/
chooseCallback: VoidFunction;
/**
* Callback to invoke on clear button click.
*/
clearCallback: VoidFunction;
/**
* Callback to invoke on upload.
*/
uploadCallback: VoidFunction;
}): TemplateRef<any>;
/**
* Custom template of content.
*/
Expand All @@ -136,7 +175,40 @@ export interface FileUploadTemplates {
* File list.
*/
$implicit: any;
}): TemplateRef<{ $implicit: any }>;
/**
* Uploaded files list.
*/
uploadedFiles: any;
/**
* Upload progress.
*/
progress: any;
/**
* Status messages about upload process.
*/
messages: any;
/**
* Callback to invoke on choose button click.
*/
chooseCallback: VoidFunction;
/**
* Callback to invoke on clear button click.
*/
removeFileCallback: VoidFunction;
/**
* Callback to invoke on clear button click.
*/
clearCallback: VoidFunction;
/**
* Callback to invoke on upload.
*/
uploadCallback: VoidFunction;
/**
* Callback to invoke on remove uploaded file, accepts index as a parameter.
* @param index Index of the file to remove.
*/
removeUploadedFileCallback: VoidFunction;
}): TemplateRef<any>;
/**
* Custom template of toolbar.
*/
Expand Down
48 changes: 42 additions & 6 deletions src/app/components/fileupload/fileupload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import {
ViewChild,
ViewEncapsulation,
booleanAttribute,
numberAttribute
numberAttribute,
signal
} from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { BlockableUI, Message, PrimeNGConfig, PrimeTemplate, SharedModule, TranslationKeys } from 'primeng/api';
Expand All @@ -37,7 +38,7 @@ import { ProgressBarModule } from 'primeng/progressbar';
import { RippleModule } from 'primeng/ripple';
import { VoidListener } from 'primeng/ts-helpers';
import { Subscription } from 'rxjs';
import { FileBeforeUploadEvent, FileProgressEvent, FileRemoveEvent, FileSelectEvent, FileSendEvent, FileUploadErrorEvent, FileUploadEvent, FileUploadHandlerEvent } from './fileupload.interface';
import { FileBeforeUploadEvent, FileProgressEvent, FileRemoveEvent, FileSelectEvent, FileSendEvent, FileUploadErrorEvent, FileUploadEvent, FileUploadHandlerEvent, RemoveUploadedFileEvent } from './fileupload.interface';
/**
* FileUpload is an advanced uploader with dragdrop support, multi file uploads, auto uploading, progress tracking and validations.
* @group Components
Expand All @@ -46,6 +47,18 @@ import { FileBeforeUploadEvent, FileProgressEvent, FileRemoveEvent, FileSelectEv
selector: 'p-fileUpload',
template: `
<div [ngClass]="'p-fileupload p-fileupload-advanced p-component'" [ngStyle]="style" [class]="styleClass" *ngIf="mode === 'advanced'" [attr.data-pc-name]="'fileupload'" [attr.data-pc-section]="'root'">
<input
[attr.aria-label]="browseFilesLabel"
#advancedfileinput
type="file"
(change)="onFileSelect($event)"
[multiple]="multiple"
[accept]="accept"
[disabled]="disabled || isChooseDisabled()"
[attr.title]="''"
[attr.data-pc-section]="'input'"
[style.display]="'none'"
/>
<div class="p-fileupload-buttonbar" [attr.data-pc-section]="'buttonbar'">
<ng-container *ngIf="!headerTemplate">
<span
Expand Down Expand Up @@ -100,7 +113,7 @@ import { FileBeforeUploadEvent, FileProgressEvent, FileRemoveEvent, FileSelectEv
</ng-container>
</p-button>
</ng-container>
<ng-container *ngTemplateOutlet="headerTemplate"></ng-container>
<ng-container *ngTemplateOutlet="headerTemplate; context: { $implicit: files, uploadedFiles: uploadedFiles, chooseCallback: choose.bind(this), clearCallback: clear.bind(this), uploadCallback: upload.bind(this) }"></ng-container>
<ng-container *ngTemplateOutlet="toolbarTemplate"></ng-container>
</div>
<div #content class="p-fileupload-content" (dragenter)="onDragEnter($event)" (dragleave)="onDragLeave($event)" (drop)="onDrop($event)" [attr.data-pc-section]="'content'">
Expand All @@ -126,7 +139,7 @@ import { FileBeforeUploadEvent, FileProgressEvent, FileRemoveEvent, FileSelectEv
<ng-template ngFor [ngForOf]="files" [ngForTemplate]="fileTemplate"></ng-template>
</div>
</div>
<ng-container *ngTemplateOutlet="contentTemplate; context: { $implicit: files }"></ng-container>
<ng-container *ngTemplateOutlet="contentTemplate; context: { $implicit: files, uploadedFiles: uploadedFiles, removeUploadedFileCallback: removeUploadedFile.bind(this), progress: progress, messages: msgs }"></ng-container>
<div *ngIf="emptyTemplate && !hasFiles() && !uploadedFileCount" class="p-fileupload-empty">
<ng-container *ngTemplateOutlet="emptyTemplate"></ng-container>
</div>
Expand Down Expand Up @@ -417,6 +430,12 @@ export class FileUpload implements AfterViewInit, AfterContentInit, OnInit, OnDe
* @group Emits
*/
@Output() onImageError: EventEmitter<Event> = new EventEmitter<Event>();
/**
* This event is triggered if an error occurs while loading an image file.
* @param {RemoveUploadedFileEvent} event - Remove event.
* @group Emits
*/
@Output() onRemoveUploadedFile: EventEmitter<RemoveUploadedFileEvent> = new EventEmitter<RemoveUploadedFileEvent>();

@ContentChildren(PrimeTemplate) templates: QueryList<PrimeTemplate> | undefined;

Expand Down Expand Up @@ -490,6 +509,8 @@ export class FileUpload implements AfterViewInit, AfterContentInit, OnInit, OnDe

dragOverListener: VoidListener;

public uploadedFiles = [];

constructor(
@Inject(DOCUMENT) private document: Document,
@Inject(PLATFORM_ID) private platformId: any,
Expand Down Expand Up @@ -741,7 +762,7 @@ export class FileUpload implements AfterViewInit, AfterContentInit, OnInit, OnDe
} else {
this.onError.emit({ files: this.files });
}

this.uploadedFiles.push(...this.files);
this.clear();
break;
case HttpEventType.UploadProgress: {
Expand Down Expand Up @@ -774,13 +795,28 @@ export class FileUpload implements AfterViewInit, AfterContentInit, OnInit, OnDe
this.clearInputElement();
this.cd.markForCheck();
}

/**
* Removes a single file.
* @param {Event} event - Browser event.
* @param {Number} index - Index of the file.
* @group Method
*/
remove(event: Event, index: number) {
this.clearInputElement();
this.onRemove.emit({ originalEvent: event, file: this.files[index] });
this.files.splice(index, 1);
this.checkFileLimit(this.files);
}
/**
* Removes uploaded file.
* @param {Number} index - Index of the file to be removed.
* @group Method
*/
removeUploadedFile(index) {
let removedFile = this.uploadedFiles.splice(index, 1)[0];
this.uploadedFiles = [...this.uploadedFiles];
this.onRemoveUploadedFile.emit({ file: removedFile, files: this.uploadedFiles });
}

isFileLimitExceeded() {
const isAutoMode = this.auto;
Expand Down
46 changes: 37 additions & 9 deletions src/app/showcase/doc/fileupload/templatedoc.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Component } from '@angular/core';
import { Component, inject } from '@angular/core';
import { Code } from '@domain/code';
import { PrimeNGConfig } from 'primeng/api';

@Component({
selector: 'file-upload-template-demo',
Expand All @@ -13,20 +14,20 @@ import { Code } from '@domain/code';
>
<div class="card">
<p-fileUpload name="myfile[]" url="https://www.primefaces.org/cdn/api/upload.php" [multiple]="true" accept="image/*" maxFileSize="1000000">
<ng-template let-file pTemplate="header">
<ng-template pTemplate="header" let-chooseCallback="chooseCallback" let-clearCallback="clearCallback" let-uploadCallback="uploadCallback" let-files="files">
<div class="flex flex-wrap justify-content-between align-items-center flex-1 gap-2">
<div class="flex gap-2">
<p-button (click)="chooseCallback()" icon="pi pi-images" [rounded]="true" [outlined]="true" />
<p-button (click)="uploadEvent(uploadCallback)" icon="pi pi-cloud-upload" [rounded]="true" [outlined]="true" severity="success" [disabled]="!files || files.length === 0" />
<p-button (click)="clearCallback()" icon="pi pi-times" [rounded]="true" [outlined]="true" severity="danger" [disabled]="!files || files.length === 0" />
<p-button (onClick)="choose($event, chooseCallback)" icon="pi pi-images" [rounded]="true" [outlined]="true" />
<p-button (onClick)="uploadEvent(uploadCallback)" icon="pi pi-cloud-upload" [rounded]="true" [outlined]="true" severity="success" [disabled]="!files || files.length === 0" />
<p-button (onClick)="clearCallback()" icon="pi pi-times" [rounded]="true" [outlined]="true" severity="danger" [disabled]="!files || files.length === 0" />
</div>
<p-progressBar [value]="totalSizePercent" [showValue]="false" styleClass="md:w-20rem h-1rem w-full md:ml-auto" [ngClass]="{ 'exceeded-progress-bar': totalSizePercent > 100 }">
<span class="white-space-nowrap">{{ totalSize }}B / 1Mb</span>
</p-progressBar>
</div>
</ng-template>
<ng-template pTemplate="content" let-files>
<div *ngIf="files.length > 0">
<ng-template pTemplate="content" let-files let-uploadedFiles="uploadedFiles" let-removeFileCallback="removeFileCallback" let-removeUploadedFileCallback="removeUploadedFileCallback">
<div *ngIf="uploadedFiles?.length > 0">
<h5>Pending</h5>
<div class="flex flex-wrap p-0 sm:p-5 gap-5">
<div *ngFor="let file of files; let i = index" class="card m-0 px-6 flex flex-column border-1 surface-border align-items-center gap-3">
Expand All @@ -36,7 +37,6 @@ import { Code } from '@domain/code';
<span class="font-semibold">{{ file.name }}</span>
<div>{{ formatSize(file.size) }}</div>
<p-badge value="Pending" severity="warning" />
<p-button icon="pi pi-times" (click)="onRemoveTemplatingFile(file, removeFileCallback, index)" [outlined]="true" [rounded]="true" severity="danger" />
</div>
</div>
</div>
Expand All @@ -50,7 +50,7 @@ import { Code } from '@domain/code';
<span class="font-semibold">{{ file.name }}</span>
<div>{{ formatSize(file.size) }}</div>
<p-badge value="Completed" class="mt-3" severity="success" />
<p-button icon="pi pi-times" (click)="removeUploadedFileCallback(index)" [outlined]="true" [rounded]="true" severity="danger" />
<p-button icon="pi pi-times" (onClick)="removeUploadedFileCallback(index)" [outlined]="true" [rounded]="true" severity="danger" />
</div>
</div>
</div>
Expand All @@ -67,6 +67,34 @@ import { Code } from '@domain/code';
`
})
export class TemplateDoc {
config = inject(PrimeNGConfig);

choose(event, callback) {
callback();
}

onRemoveTemplatingFile(event, file, removeFileCallback, index) {
removeFileCallback(event, index);
}

uploadEvent(callback) {
callback();
}

formatSize(bytes) {
const k = 1024;
const dm = 3;
const sizes = this.config.translation.fileSizeTypes;
if (bytes === 0) {
return `0 \${sizes[0]}`;
}

const i = Math.floor(Math.log(bytes) / Math.log(k));
const formattedSize = parseFloat((bytes / Math.pow(k, i)).toFixed(dm));

return `${formattedSize} ${sizes[i]}`;
}

code: Code = {
basic: `<p-fileUpload name="myfile[]" url="https://www.primefaces.org/cdn/api/upload.php" [multiple]="true" accept="image/*" maxFileSize="1000000">
<ng-template pTemplate="toolbar">
Expand Down
Loading