File

projects/netgrif-components-core/src/lib/data-fields/file-field/file-default-field/abstract-file-default-field.component.ts

Extends

AbstractFileFieldDefaultComponent

Implements

OnInit AfterViewInit OnDestroy

Metadata

selector ncc-abstract-file-default-fied

Index

Properties
Methods
Inputs
Accessors

Constructor

Protected constructor(_taskResourceService: TaskResourceService, _log: LoggerService, _snackbar: SnackBarService, _translate: TranslateService, _eventService: EventService, _sanitizer: DomSanitizer, dataFieldPortalData: DataFieldPortalData<FileField>)

Only inject services. Option injected trough NAE_INFORM_ABOUT_INVALID_DATA InjectionToken

Parameters :
Name Type Optional Description
_taskResourceService TaskResourceService No

Provides to download a file from the backend

_log LoggerService No

Logger service

_snackbar SnackBarService No

Snackbar service to notify user

_translate TranslateService No

Translate service for I18N

_eventService EventService No

used for parsing of backend response Option injected trough NAE_INFORM_ABOUT_INVALID_DATA InjectionToken

_sanitizer DomSanitizer No

Sanitize url of image preview

dataFieldPortalData DataFieldPortalData<FileField> No

Field and form control data if field is provided with portal

Inputs

taskId
Type : string

Task mongo string id is binding property from parent component.

dataField
Type : T
formControlRef
Type : FormControl
showLargeLayout
Type : WrappedBoolean

Methods

Public borderPropertyEnabled
borderPropertyEnabled(property: string)
Parameters :
Name Type Optional
property string No
Returns : boolean
Public changeMaxWidth
changeMaxWidth(event: ResizedEvent)
Parameters :
Name Type Optional
event ResizedEvent No
Returns : void
Protected checkFileBeforeDownload
checkFileBeforeDownload()
Returns : boolean
Public chooseFile
chooseFile()
Returns : void
Public constructDisplayName
constructDisplayName()

Construct display name.

Returns : string
Protected createRequestBody
createRequestBody()
Returns : FileFieldRequest
Public deleteFile
deleteFile()
Returns : void
Public download
download()
Returns : void
Protected downloadViaAnchor
downloadViaAnchor(blob: Blob)
Parameters :
Name Type Optional
blob Blob No
Returns : void
Public getHeight
getHeight()
Returns : any
Public getPreviewBorderColor
getPreviewBorderColor()
Returns : string
Public getPreviewBorderStyle
getPreviewBorderStyle()
Returns : string
Public getPreviewBorderWidth
getPreviewBorderWidth()
Returns : string
Public hasHint
hasHint()
Returns : boolean
Public hasTitle
hasTitle()
Returns : boolean
Protected initFileFieldImage
initFileFieldImage()

Initialize file field image from backend if it is image type.

Returns : void
Protected initializePreviewIfDisplayable
initializePreviewIfDisplayable()
Returns : void
Public isBorderDefault
isBorderDefault()
Returns : boolean
Public isBorderLGBTQ
isBorderLGBTQ()
Returns : boolean
isEmpty
isEmpty()
Returns : boolean
ngAfterViewInit
ngAfterViewInit()
Returns : void
ngOnDestroy
ngOnDestroy()
Returns : void
ngOnInit
ngOnInit()

Set :

Returns : void
Public showPreviewDialog
showPreviewDialog()
Returns : void
Public upload
upload()

Call after click on file field.

If file field has no file uploaded FilesUploadComponent via SideMenu opens.

Otherwise opens a file picker from which the user can select files.

Returns : void
Protected checkAllowedTypes
checkAllowedTypes()
Returns : boolean
Protected checkTypes
checkTypes(itemType: string)
Parameters :
Name Type Optional
itemType string No
Returns : boolean
Public getCutProperty
getCutProperty(label)
Parameters :
Name Optional
label No
Returns : string
Protected resolveMaxSizeMessage
resolveMaxSizeMessage()
Returns : void
Protected resolveParentTaskId
resolveParentTaskId()
Returns : string
Public checkPropertyInComponent
checkPropertyInComponent(property: string)
Parameters :
Name Type Optional
property string No
Returns : boolean

Properties

Static Readonly DEFAULT_PREVIEW_BORDER_COLOR
Type : string
Default value : 'black'

The CSS color string of the default file preview border.

Static Readonly DEFAULT_PREVIEW_BORDER_STYLE
Type : string
Default value : 'none'

The CSS style attribute of the default file preview border.

Static Readonly DEFAULT_PREVIEW_BORDER_WIDTH
Type : number
Default value : 0

The width of the default file preview border in pixels. The px string is appended in the code.

Public fullSource
Type : BehaviorSubject<SafeUrl>

Full size file url

Public imageDivEl
Type : ElementRef
Decorators :
@ViewChild('imageDiv')
Public imageEl
Type : ElementRef
Decorators :
@ViewChild('imageEl')

Image field view element reference from component template that is initialized after view init.

Public isDisplayable
Default value : false

If file type can be displayed

Public isFilePreview
Default value : false
Public isFilePreviewButton
Default value : false
Public previewExtension
Type : FilePreviewType

Extension of file to preview

Public previewSource
Type : SafeUrl

Url of preview file

Public state
Type : FileState
Public cutProperty
Type : string
Public fileUploadEl
Type : ElementRef<HTMLInputElement>
Decorators :
@ViewChild('fileUploadInput')

File picker element reference from component template that is initialized after view init.

Public taskId
Type : string
Decorators :
@Input()

Task mongo string id is binding property from parent component.

Public dataField
Type : T
Decorators :
@Input()
Public formControlRef
Type : FormControl
Decorators :
@Input()
Public showLargeLayout
Type : WrappedBoolean
Decorators :
@Input()

Accessors

defaultState
getdefaultState()
import {
    AfterViewInit,
    Component,
    ElementRef,
    Inject,
    OnDestroy,
    OnInit,
    Optional,
    ViewChild
} from "@angular/core";
import {FileField, FilePreviewType} from "../models/file-field";
import {DomSanitizer, SafeUrl} from "@angular/platform-browser";
import {BehaviorSubject, Subscription} from "rxjs";
import {TaskResourceService} from "../../../resources/engine-endpoint/task-resource.service";
import {LoggerService} from "../../../logger/services/logger.service";
import {SnackBarService} from "../../../snack-bar/services/snack-bar.service";
import {TranslateService} from "@ngx-translate/core";
import {EventService} from "../../../event/services/event.service";
import {EventOutcomeMessageResource} from "../../../resources/interface/message-resource";
import {ProgressType, ProviderProgress} from "../../../resources/resource-provider.service";
import {ChangedFieldsMap} from "../../../event/services/interfaces/changed-fields-map";
import {HttpParams} from "@angular/common/http";
import {take} from "rxjs/operators";
import {ResizedEvent} from "angular-resize-event";
import {DATA_FIELD_PORTAL_DATA, DataFieldPortalData} from "../../models/data-field-portal-data-injection-token";
import {FILE_FIELD_HEIGHT, FILE_FIELD_PADDING, PREVIEW, PREVIEW_BUTTON} from '../models/file-field-constants';
import {FileFieldRequest} from "../../../resources/interface/file-field-request-body";
import {AbstractFileFieldDefaultComponent} from '../../models/abstract-file-field-default-component';

export interface FileState {
    progress: number;
    uploading: boolean;
    downloading: boolean;
    completed: boolean;
    error: boolean;
}

@Component({
    selector: 'ncc-abstract-file-default-fied',
    template: ''
})
export abstract class AbstractFileDefaultFieldComponent extends AbstractFileFieldDefaultComponent<FileField> implements OnInit, AfterViewInit, OnDestroy {
    /**
     * The width of the default file preview border in pixels. The `px` string is appended in the code.
     */
    public static readonly DEFAULT_PREVIEW_BORDER_WIDTH = 0;
    /**
     * The CSS style attribute of the default file preview border.
     */
    public static readonly DEFAULT_PREVIEW_BORDER_STYLE = 'none';
    /**
     * The CSS color string of the default file preview border.
     */
    public static readonly DEFAULT_PREVIEW_BORDER_COLOR = 'black';

    public state: FileState;

    /**
     * Image field view element reference from component template that is initialized after view init.
     */
    @ViewChild('imageEl') public imageEl: ElementRef;

    @ViewChild('imageDiv') public imageDivEl: ElementRef;
    /**
     * If file type can be displayed
     */
    public isDisplayable = false;
    /**
     * Max height of preview
     */
    private maxHeight: string;
    /**
     * Store file for preview
     */
    private fileForPreview: Blob;
    /**
     * Url of preview file
     */
    public previewSource: SafeUrl;
    /**
     * Store file to show/download
     */
    private fileForDownload: Blob;
    /**
     * Full size file url
     */
    public fullSource: BehaviorSubject<SafeUrl>;
    /**
     * Extension of file to preview
     */
    public previewExtension: FilePreviewType;
    /**
     * Form control subscription
     */
    private updatedFieldSubscription: Subscription;

    public isFilePreview = false;
    public isFilePreviewButton = false;

    /**
     * Only inject services.
     * @param _taskResourceService Provides to download a file from the backend
     * @param _log Logger service
     * @param _snackbar Snackbar service to notify user
     * @param _translate Translate service for I18N
     * @param _eventService used for parsing of backend response
     * Option injected trough `NAE_INFORM_ABOUT_INVALID_DATA` InjectionToken
     * @param _sanitizer Sanitize url of image preview
     * @param dataFieldPortalData Field and form control data if field is provided with portal
     */
    protected constructor(protected _taskResourceService: TaskResourceService,
                          protected _log: LoggerService,
                          protected _snackbar: SnackBarService,
                          protected _translate: TranslateService,
                          protected _eventService: EventService,
                          protected _sanitizer: DomSanitizer,
                          @Optional() @Inject(DATA_FIELD_PORTAL_DATA) dataFieldPortalData: DataFieldPortalData<FileField>) {
        super(_log, _snackbar, _translate, dataFieldPortalData);
        this.state = this.defaultState;
        this.fullSource = new BehaviorSubject<SafeUrl>(null);
        this.taskId = dataFieldPortalData.additionalFieldProperties.taskId as string;
    }

    /**
     * Set :
     *  - File field to [FileFieldService]{@link FileFieldService}
     *  - Display name
     */
    ngOnInit() {
        this.isFilePreview = this.dataField?.component?.name === PREVIEW;
        this.isFilePreviewButton = this.dataField?.component?.name === PREVIEW_BUTTON;
    }

    ngAfterViewInit() {
        if (this.fileUploadEl) {
            this.fileUploadEl.nativeElement.onchange = () => {
                this.upload();
            };
        }
        if (this.isFilePreview) {
            if (!!this.imageDivEl) {
                if (!this.isEmpty()) {
                    this.initializePreviewIfDisplayable();
                }
            }
        }
        if (this.isFilePreviewButton) {
            if (!this.isEmpty()) {
                this.initializePreviewIfDisplayable();
            }
        }
        this.updatedFieldSubscription = this.dataField.updated.subscribe(() => {
            this.previewSource = undefined;
            if (!!this.isFilePreview && !!this.dataField?.value?.name) {
                this.fileForDownload = undefined;
                this.fileForPreview = undefined;
                this.initializePreviewIfDisplayable();
            }
            if (!!this.isFilePreviewButton && !!this.dataField?.value?.name) {
                this.fileForDownload = undefined;
                this.fileForPreview = undefined;
                this.initializePreviewIfDisplayable();
            }
        })
    }

    ngOnDestroy(): void {
        super.ngOnDestroy();
        this.fullSource.complete();
        this.updatedFieldSubscription.unsubscribe();
    }

    public chooseFile() {
        if (this.state.uploading || this.formControlRef.disabled) {
            return;
        }
        this.fileUploadEl.nativeElement.click();
    }

    /**
     * Call after click on file field.
     *
     * If file field has no file uploaded
     * [FilesUploadComponent]{@link AbstractFilesUploadComponent} via [SideMenu]{@link SideMenuService} opens.
     *
     * Otherwise opens a file picker from which the user can select files.
     */
    public upload() {
        if (!this.fileUploadEl.nativeElement.files || this.fileUploadEl.nativeElement.files.length === 0) {
            return;
        }
        if (!this.taskId) {
            this._log.error('File cannot be uploaded. No task is set to the field.');
            return;
        }
        if (this.dataField.value?.name &&
            this.fileUploadEl.nativeElement.files.item(0).name === this.dataField.value?.name) {
            this._log.error('User chose the same file. Uploading skipped');
            this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.wontUploadSameFile'));
            this.fileUploadEl.nativeElement.value = '';
            return;
        }
        if (this.dataField.maxUploadSizeInBytes &&
            this.dataField.maxUploadSizeInBytes < this.fileUploadEl.nativeElement.files.item(0).size) {
            this._log.error('File cannot be uploaded. Maximum size of file exceeded.');
            this.resolveMaxSizeMessage();
            this.fileUploadEl.nativeElement.value = '';
            return;
        }
        if (!this.checkAllowedTypes()) {
            return;
        }
        this.state = this.defaultState;
        this.state.uploading = true;
        const fileFormData = new FormData();
        const fileToUpload = this.fileUploadEl.nativeElement.files.item(0) as File;
        fileFormData.append('file', fileToUpload);
        fileFormData.append('data', new Blob([JSON.stringify(this.createRequestBody())], {type: 'application/json'}));
        this._taskResourceService.uploadFile(this.taskId, fileFormData, false)
            .subscribe((response: EventOutcomeMessageResource) => {
                if ((response as ProviderProgress).type && (response as ProviderProgress).type === ProgressType.UPLOAD) {
                    this.state.progress = (response as ProviderProgress).progress;
                } else {
                    this.state.completed = true;
                    this.state.uploading = false;
                    this.state.progress = 0;

                    if (response.error) {
                        this.state.error = true;
                        this._log.error(
                            `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, response.error
                        );
                        if (response.error) {
                            this._snackbar.openErrorSnackBar(this._translate.instant(response.error));
                        } else {
                            this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed'));
                        }
                    } else {
                        const changedFieldsMap: ChangedFieldsMap = this._eventService.parseChangedFieldsFromOutcomeTree(response.outcome);
                        this.dataField.emitChangedFields(changedFieldsMap);
                        this._log.debug(
                            `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0).name} was successfully uploaded`
                        );
                        this.state.error = false;
                        this.dataField.downloaded = false;
                        this.dataField.value.name = fileToUpload.name;
                        if (this.isFilePreview) {
                            this.initializePreviewIfDisplayable();
                        }
                        this.fullSource.next(undefined);
                        this.fileForDownload = undefined;
                        this.formControlRef.setValue(this.dataField.value.name);
                    }
                    this.dataField.touch = true;
                    this.dataField.update();
                    this.fileUploadEl.nativeElement.value = '';
                }
            }, error => {
                this.state.completed = true;
                this.state.error = true;
                this.state.uploading = false;
                this.state.progress = 0;
                this._log.error(
                    `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, error
                );
                if (error?.error?.message) {
                    this._snackbar.openErrorSnackBar(this._translate.instant(error.error.message));
                } else {
                    this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed'));
                }
                this.dataField.touch = true;
                this.dataField.update();
                this.fileUploadEl.nativeElement.value = '';
            });
    }

    public download() {
        if (!this.checkFileBeforeDownload()) {
            return;
        }
        if (!!this.fileForDownload) {
            this.downloadViaAnchor(this.fileForDownload);
            return;
        }
        this.state = this.defaultState;
        this.state.downloading = true;
        let params = new HttpParams();
        params = params.set("fieldId", this.dataField.stringId);
        this._taskResourceService.downloadFile(this.resolveParentTaskId(), params).subscribe(response => {
            if (!(response as ProviderProgress).type || (response as ProviderProgress).type !== ProgressType.DOWNLOAD) {
                this._log.debug(`File [${this.dataField.stringId}] ${this.dataField.value.name} was successfully downloaded`);
                this.downloadViaAnchor(response as Blob);
                if (this.isFilePreview) {
                    this.initDownloadFile(response);
                }
                this.state.downloading = false;
                this.state.progress = 0;
                this.dataField.downloaded = true;
            }
        }, error => {
            this._log.error(`Downloading file [${this.dataField.stringId}] ${this.dataField.value.name} has failed!`, error);
            this._snackbar.openErrorSnackBar(
                this.dataField.value.name + ' ' + this._translate.instant('dataField.snackBar.downloadFail')
            );
            this.state.downloading = false;
            this.state.progress = 0;
        });
    }

    private initDownloadFile(response: Blob | ProviderProgress) {
        if (response instanceof Blob) {
            if (this.previewExtension === FilePreviewType.pdf) {
                this.fileForDownload = new Blob([response], {type: 'application/pdf'});
                this.fullSource.next(this._sanitizer.bypassSecurityTrustResourceUrl(URL.createObjectURL(this.fileForDownload)));
            } else {
                this.fileForDownload = new Blob([response], {type: 'application/octet-stream'});
                this.fullSource.next(this._sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(this.fileForDownload)));
            }
        }
    }

    protected downloadViaAnchor(blob: Blob): void {
        const a = document.createElement('a');
        document.body.appendChild(a);
        a.setAttribute('style', 'display: none');
        if (!this.fileForDownload) {
            blob = new Blob([blob], {type: 'application/octet-stream'});
        }
        const url = window.URL.createObjectURL(!!this.fileForDownload ? this.fileForDownload : blob);
        a.href = url;
        a.download = this.dataField.value.name;
        a.click();
        window.URL.revokeObjectURL(url);
        document.body.removeChild(a);
    }

    public deleteFile() {
        if (!this.dataField.value?.name) {
            return;
        }
        if (!this.taskId) {
            this._log.error('File cannot be deleted. No task is set to the field.');
            return;
        }

        this._taskResourceService.deleteFile(this.taskId, this.createRequestBody()).pipe(take(1)).subscribe(response => {
            if (response.success) {
                const filename = this.dataField.value.name;
                this.dataField.value = {};
                this.formControlRef.setValue('');
                this.dataField.update();
                this.dataField.downloaded = false;
                this.fullSource.next(undefined);
                this.fileForDownload = undefined;
                this.previewSource = undefined;
                this.fileForPreview = undefined;
                this._log.debug(`File [${this.dataField.stringId}] ${filename} was successfully deleted`);
                this.formControlRef.markAsTouched();
            } else {
                this._log.error(`Deleting file [${this.dataField.stringId}] ${this.dataField.value.name} has failed!`, response.error);
                this._snackbar.openErrorSnackBar(
                    this.dataField.value.name + ' ' + this._translate.instant('dataField.snackBar.fileDeleteFailed')
                );
            }
        });
    }

    isEmpty(): boolean {
        return !this.dataField.value?.name;
    }

    protected createRequestBody(): FileFieldRequest {
        return {
            parentTaskId: this.resolveParentTaskId(),
            fieldId: this.dataField.stringId
        };
    }

    protected get defaultState(): FileState {
        return {
            progress: 0,
            completed: false,
            error: false,
            uploading: false,
            downloading: false
        };
    }

    /**
     * Construct display name.
     */
    public constructDisplayName(): string {
        if (!!this.dataField) {
            if (!!this.dataField.value && !!this.dataField.value.name) {
                return this.dataField.value.name;
            } else if (!!this.dataField.placeholder) {
                return this.dataField.placeholder;
            }
        }
        return this._translate.instant('dataField.file.noFile');
    }

    /**
     * Initialize file field image from backend if it is image type.
     */
    protected initFileFieldImage() {
        if (!this.checkFileBeforeDownload()) {
            return;
        }
        this.state.downloading = true;
        let params = new HttpParams()
        params = params.set("fieldId", this.dataField.stringId);
        this._taskResourceService.downloadFilePreview(this.resolveParentTaskId(), params).subscribe(response => {            if (response instanceof Blob) {
                this._log.debug(`Preview of file [${this.dataField.stringId}] ${this.dataField.value.name} was successfully downloaded`);
                this.fileForPreview = new Blob([response], {type: 'application/octet-stream'});
                this.previewSource = this._sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(this.fileForPreview));
            }
            if (response == null || response instanceof Blob) {
                this.state.downloading = false;
            }
        }, error => {
            this._log.error(`Downloading file [${this.dataField.stringId}] ${this.dataField.value.name} has failed!`, error);
            this._snackbar.openErrorSnackBar(
                this.dataField.value.name + ' ' + this._translate.instant('dataField.snackBar.downloadFail')
            );
            this.state.downloading = false;
            this.state.progress = 0;
        });
    }

    protected checkFileBeforeDownload() {
        if (this.isEmpty()) {
            return false;
        }
        if (!this.taskId) {
            this._log.error('File cannot be downloaded. No task is set to the field.');
            return false;
        }
        return true;
    }

    public showPreviewDialog() {
        if (!this.checkFileBeforeDownload()) {
            return;
        }
        let params = new HttpParams();
        params = params.set("fieldId", this.dataField.stringId);
        this._taskResourceService.downloadFile(this.resolveParentTaskId(), params).subscribe(response => {            if (!(response as ProviderProgress).type || (response as ProviderProgress).type !== ProgressType.DOWNLOAD) {
                this._log.debug(`File [${this.dataField.stringId}] ${this.dataField.value.name} was successfully downloaded`);
                this.initDownloadFile(response);
            }
        }, error => {
            this._log.error(`Downloading file [${this.dataField.stringId}] ${this.dataField.value.name} has failed!`, error);
            this._snackbar.openErrorSnackBar(
                this.dataField.value.name + ' ' + this._translate.instant('dataField.snackBar.downloadFail')
            );
            this.state.progress = 0;
        });
    }

    public changeMaxWidth(event: ResizedEvent) {
        if (!!this.imageEl) {
            this.imageEl.nativeElement.style.maxWidth = event.newRect.width + 'px';
        }
    }

    protected initializePreviewIfDisplayable() {
        const extension = this.dataField.value.name.split('.').reverse()[0];
        this.isDisplayable = Object.values(FilePreviewType).includes(extension as any);
        if (this.isDisplayable) {
            this.previewExtension = FilePreviewType[extension];
            this.initFileFieldImage();
        }
    }

    public getHeight() {
        return this.dataField.layout?.rows && this.dataField.layout?.rows !== 1 ?
            (this.dataField.layout.rows) * FILE_FIELD_HEIGHT - FILE_FIELD_PADDING : FILE_FIELD_HEIGHT - FILE_FIELD_PADDING;
    }

    public getPreviewBorderWidth(): string {
        if (this.borderPropertyEnabled('borderWidth')) {
            return this.dataField.component.properties.borderWidth + 'px';
        }
        return `${AbstractFileDefaultFieldComponent.DEFAULT_PREVIEW_BORDER_WIDTH}px`;
    }

    public getPreviewBorderStyle(): string {
        if (this.borderPropertyEnabled('borderStyle')) {
            return this.dataField.component.properties.borderStyle;
        }
        return AbstractFileDefaultFieldComponent.DEFAULT_PREVIEW_BORDER_STYLE;
    }

    public getPreviewBorderColor(): string {
        if (this.borderPropertyEnabled('borderColor')) {
            return this.dataField.component.properties.borderColor;
        }
        return AbstractFileDefaultFieldComponent.DEFAULT_PREVIEW_BORDER_COLOR;
    }

    public isBorderLGBTQ(): boolean {
        if (this.borderPropertyEnabled('borderLGBTQ')) {
            return this.dataField.component.properties.borderLGBTQ === 'true';
        }
        return false;
    }

    public isBorderDefault(): boolean {
        if (this.borderPropertyEnabled('borderEnabled')) {
            return this.dataField.component.properties.borderEnabled === 'true';
        }
        return false;
    }

    public borderPropertyEnabled(property: string): boolean {
        return !!this.dataField.component && !!this.dataField.component.properties && property in this.dataField.component.properties;
    }

    public hasTitle(): boolean {
        return this.dataField.title !== undefined && this.dataField.title !== '';
    }

    public hasHint(): boolean {
        return this.dataField.description !== undefined && this.dataField.description !== '';
    }
}
Legend
Html element
Component
Html element with directive

result-matching ""

    No results matching ""