From c1440cf0b8242abc886f64fe90d386b8dc4ac898 Mon Sep 17 00:00:00 2001 From: Jozef Daxner Date: Mon, 3 Jul 2023 09:35:52 +0200 Subject: [PATCH 1/4] Prompt user before leaving --- .../lib/data-fields/models/abstract-data-field.ts | 14 ++++++++++++++ .../abstract-textarea-field.component.ts | 3 ++- .../textarea-field/textarea-field.component.html | 5 ++++- .../textarea-field/textarea-field.component.ts | 12 +++++++++++- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/projects/netgrif-components-core/src/lib/data-fields/models/abstract-data-field.ts b/projects/netgrif-components-core/src/lib/data-fields/models/abstract-data-field.ts index c40677ee79..be4551a285 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/models/abstract-data-field.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/models/abstract-data-field.ts @@ -123,6 +123,8 @@ export abstract class DataField { * */ private _input: ElementRef; + private _focused = false; + /** * @param _stringId - ID of the data field from backend * @param _title - displayed title of the data field from backend @@ -347,6 +349,18 @@ export abstract class DataField { this._input = value; } + public setFocus() { + this._focused = true; + } + + public unsetFocus() { + this._focused = false; + } + + public isFocused() { + return this._focused; + } + /** * This function resolve type of component for HTML * @returns type of component in string diff --git a/projects/netgrif-components-core/src/lib/data-fields/text-field/textarea-field/abstract-textarea-field.component.ts b/projects/netgrif-components-core/src/lib/data-fields/text-field/textarea-field/abstract-textarea-field.component.ts index 160ef69a73..478349968f 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/text-field/textarea-field/abstract-textarea-field.component.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/text-field/textarea-field/abstract-textarea-field.component.ts @@ -7,6 +7,7 @@ import {TranslateService} from '@ngx-translate/core'; import {take} from 'rxjs/operators'; import {CdkTextareaAutosize} from '@angular/cdk/text-field'; import {AbstractTextErrorsComponent} from '../abstract-text-errors.component'; +import {TextAreaField} from '../models/text-area-field'; @Component({ selector: 'ncc-abstract-text-area-field', @@ -14,7 +15,7 @@ import {AbstractTextErrorsComponent} from '../abstract-text-errors.component'; }) export abstract class AbstractTextareaFieldComponent extends AbstractTextErrorsComponent implements AfterViewInit { - @Input() textAreaField: TextField; + @Input() textAreaField: TextAreaField; @Input() formControlRef: FormControl; @Input() showLargeLayout: WrappedBoolean; @ViewChild('dynamicTextArea') dynamicTextArea: CdkTextareaAutosize; diff --git a/projects/netgrif-components/src/lib/data-fields/text-field/textarea-field/textarea-field.component.html b/projects/netgrif-components/src/lib/data-fields/text-field/textarea-field/textarea-field.component.html index 46374f972d..9042729f05 100644 --- a/projects/netgrif-components/src/lib/data-fields/text-field/textarea-field/textarea-field.component.html +++ b/projects/netgrif-components/src/lib/data-fields/text-field/textarea-field/textarea-field.component.html @@ -11,7 +11,10 @@ [ngStyle]="{'min-height': getHeight()+'px', 'height': getHeight()+'px'}" #dynamicTextArea="cdkTextareaAutosize" cdkTextareaAutosize - cdkAutosizeMinRows="1"> + cdkAutosizeMinRows="1" + (focus)="textAreaField.setFocus()" + (blur)="textAreaField.unsetFocus()"> + {{textAreaField.description}} {{getErrorMessage()}} diff --git a/projects/netgrif-components/src/lib/data-fields/text-field/textarea-field/textarea-field.component.ts b/projects/netgrif-components/src/lib/data-fields/text-field/textarea-field/textarea-field.component.ts index 1930b24d00..89ff594e5b 100644 --- a/projects/netgrif-components/src/lib/data-fields/text-field/textarea-field/textarea-field.component.ts +++ b/projects/netgrif-components/src/lib/data-fields/text-field/textarea-field/textarea-field.component.ts @@ -1,4 +1,4 @@ -import {Component, NgZone} from '@angular/core'; +import {Component, HostListener, NgZone} from '@angular/core'; import {TranslateService} from '@ngx-translate/core'; import {AbstractTextareaFieldComponent} from '@netgrif/components-core'; @@ -9,6 +9,16 @@ import {AbstractTextareaFieldComponent} from '@netgrif/components-core'; }) export class TextareaFieldComponent extends AbstractTextareaFieldComponent { + @HostListener('window:beforeunload', ['$event']) + beforeUnloadHander(event) { + if (this.textAreaField.isFocused()) { + event.preventDefault(); + event.returnValue = "Data nie su ulozene!"; + return event; + } + return true; + } + constructor(protected _translate: TranslateService, protected _ngZone: NgZone) { super(_translate, _ngZone); } From f5052177405eff48003c02d6978b0a233c3166fc Mon Sep 17 00:00:00 2001 From: Jozef Daxner Date: Fri, 7 Jul 2023 12:19:47 +0200 Subject: [PATCH 2/4] [NAE-1907] Prompt user before leaving/reloading site when data does not need to be saved - prompt user with dialog when leaving/reloading site and focus is still in input of field (text/number/i18n) - if user decide to stay on site, blur input field so data is saved --- projects/nae-example-app/src/app/app.module.ts | 4 +++- .../abstract-i18n-field.component.ts | 6 ++++-- .../models/abstract-data-field-component.ts | 18 +++++++++++++++--- .../models/save-data-inform-token.ts | 11 +++++++++++ .../abstract-number-field.component.ts | 6 ++++-- .../src/lib/data-fields/public-api.ts | 1 + .../abstract-text-field.component.ts | 6 ++++-- .../i18n-field/i18n-field.component.ts | 11 ++++++++--- .../i18n-text-field.component.html | 3 ++- .../number-currency-field.component.html | 4 +++- .../number-default-field.component.html | 4 +++- .../number-field/number-field.component.ts | 11 ++++++++--- .../html-textarea-field.component.html | 3 ++- .../password-text-field.component.html | 4 +++- .../easymde-wrapper.component.ts | 2 ++ .../simple-text-field.component.html | 4 +++- .../text-field/text-field.component.ts | 12 +++++++++--- .../textarea-field.component.html | 6 +++--- .../textarea-field/textarea-field.component.ts | 12 +----------- 19 files changed, 89 insertions(+), 39 deletions(-) create mode 100644 projects/netgrif-components-core/src/lib/data-fields/models/save-data-inform-token.ts diff --git a/projects/nae-example-app/src/app/app.module.ts b/projects/nae-example-app/src/app/app.module.ts index d5a505c165..dfccdc902c 100644 --- a/projects/nae-example-app/src/app/app.module.ts +++ b/projects/nae-example-app/src/app/app.module.ts @@ -18,7 +18,8 @@ import { SnackBarVerticalPosition, ViewService, ProfileModule, - Dashboard + Dashboard, + NAE_SAVE_DATA_INFORM } from '@netgrif/components-core'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import {FlexLayoutModule, FlexModule} from '@angular/flex-layout'; @@ -246,6 +247,7 @@ export function HttpLoaderFactory(http: HttpClient) { }, {provide: NAE_SNACKBAR_VERTICAL_POSITION, useValue: SnackBarVerticalPosition.TOP}, {provide: NAE_SNACKBAR_HORIZONTAL_POSITION, useValue: SnackBarHorizontalPosition.LEFT}, + {provide: NAE_SAVE_DATA_INFORM, useValue: true}, ResourceProvider, TranslateService, TranslatePipe, diff --git a/projects/netgrif-components-core/src/lib/data-fields/i18n-field/abstract-i18n-field.component.ts b/projects/netgrif-components-core/src/lib/data-fields/i18n-field/abstract-i18n-field.component.ts index e32eb62c53..7e898d925a 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/i18n-field/abstract-i18n-field.component.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/i18n-field/abstract-i18n-field.component.ts @@ -2,6 +2,7 @@ import {Component, Inject, Input, Optional} from '@angular/core'; import {AbstractDataFieldComponent} from '../models/abstract-data-field-component'; import {NAE_INFORM_ABOUT_INVALID_DATA} from '../models/invalid-data-policy-token'; import {I18nField} from './models/i18n-field'; +import {NAE_SAVE_DATA_INFORM} from '../models/save-data-inform-token'; @Component({ selector: 'ncc-abstract-i18n-field', @@ -11,7 +12,8 @@ export abstract class AbstractI18nFieldComponent extends AbstractDataFieldCompon @Input() dataField: I18nField; - constructor(@Optional() @Inject(NAE_INFORM_ABOUT_INVALID_DATA) informAboutInvalidData: boolean | null) { - super(informAboutInvalidData); + constructor(@Optional() @Inject(NAE_INFORM_ABOUT_INVALID_DATA) informAboutInvalidData: boolean | null, + @Optional() @Inject(NAE_SAVE_DATA_INFORM) saveDataInform: boolean | null = false) { + super(informAboutInvalidData, saveDataInform); } } diff --git a/projects/netgrif-components-core/src/lib/data-fields/models/abstract-data-field-component.ts b/projects/netgrif-components-core/src/lib/data-fields/models/abstract-data-field-component.ts index 7faaaadcbc..7de239aa94 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/models/abstract-data-field-component.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/models/abstract-data-field-component.ts @@ -1,7 +1,8 @@ import {DataField} from './abstract-data-field'; import {FormControl} from '@angular/forms'; -import {Component, Inject, Input, OnDestroy, OnInit, Optional} from '@angular/core'; +import {Component, HostListener, Inject, Input, OnDestroy, OnInit, Optional} from '@angular/core'; import {NAE_INFORM_ABOUT_INVALID_DATA} from './invalid-data-policy-token'; +import {NAE_SAVE_DATA_INFORM} from './save-data-inform-token'; /** * Holds the common functionality for all DataFieldComponents. @@ -24,8 +25,19 @@ export abstract class AbstractDataFieldComponent implements OnInit, OnDestroy { */ protected _formControl: FormControl; - protected constructor(@Optional() @Inject(NAE_INFORM_ABOUT_INVALID_DATA) protected _informAboutInvalidData: boolean | null) { - this._formControl = new FormControl('', { updateOn: 'blur' }); + @HostListener('window:beforeunload', ['$event']) + beforeUnloadEventHandler(event) { + if (this._saveDataInform && this.dataField.isFocused()) { + this.dataField.unsetFocus(); + (document.activeElement as HTMLElement).blur(); + return false; + } + return true; + } + + protected constructor(@Optional() @Inject(NAE_INFORM_ABOUT_INVALID_DATA) protected _informAboutInvalidData: boolean | null, + @Optional() @Inject(NAE_SAVE_DATA_INFORM) protected _saveDataInform: boolean | null = false) { + this._formControl = new FormControl('', {updateOn: 'blur'}); } /** diff --git a/projects/netgrif-components-core/src/lib/data-fields/models/save-data-inform-token.ts b/projects/netgrif-components-core/src/lib/data-fields/models/save-data-inform-token.ts new file mode 100644 index 0000000000..e56d260873 --- /dev/null +++ b/projects/netgrif-components-core/src/lib/data-fields/models/save-data-inform-token.ts @@ -0,0 +1,11 @@ +import {InjectionToken} from '@angular/core'; + +/** + * Whether invalid data values should be sent to backend or not. Invalid data is NOT set to backend by default. + * You can use this InjectionToken to override this behavior in a specific application scope. + * + * This token is ultimately injected by individual data fields, so this option can be in theory applied at a very low level of granularity. + * The library implementation doesn't allow access to such low level components, so a custom implementation is necessary to provide this + * token at such low level. Applying the token to individual task views is achievable with the default implementation. + */ +export const NAE_SAVE_DATA_INFORM = new InjectionToken('NaeSaveDataInform'); diff --git a/projects/netgrif-components-core/src/lib/data-fields/number-field/abstract-number-field.component.ts b/projects/netgrif-components-core/src/lib/data-fields/number-field/abstract-number-field.component.ts index 19f9182ea4..432551d875 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/number-field/abstract-number-field.component.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/number-field/abstract-number-field.component.ts @@ -3,6 +3,7 @@ import {NumberField} from './models/number-field'; import {AbstractDataFieldComponent} from '../models/abstract-data-field-component'; import {TranslateService} from '@ngx-translate/core'; import {NAE_INFORM_ABOUT_INVALID_DATA} from '../models/invalid-data-policy-token'; +import {NAE_SAVE_DATA_INFORM} from '../models/save-data-inform-token'; @Component({ selector: 'ncc-abstract-number-field', @@ -13,8 +14,9 @@ export abstract class AbstractNumberFieldComponent extends AbstractDataFieldComp @Input() public dataField: NumberField; protected constructor(protected _translate: TranslateService, - @Optional() @Inject(NAE_INFORM_ABOUT_INVALID_DATA) informAboutInvalidData: boolean | null) { - super(informAboutInvalidData); + @Optional() @Inject(NAE_INFORM_ABOUT_INVALID_DATA) informAboutInvalidData: boolean | null, + @Optional() @Inject(NAE_SAVE_DATA_INFORM) saveDataInform: boolean | null = false) { + super(informAboutInvalidData, saveDataInform); } } diff --git a/projects/netgrif-components-core/src/lib/data-fields/public-api.ts b/projects/netgrif-components-core/src/lib/data-fields/public-api.ts index e5ee0bfe8d..a037849dfe 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/public-api.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/public-api.ts @@ -88,6 +88,7 @@ export * from './task-ref-field/model/task-ref-dashboard-tile'; export * from './models/boolean-label-enabled-token'; export * from './models/invalid-data-policy-token'; export * from './filter-field/models/filter-field-injection-token'; +export * from './models/save-data-inform-token'; /* Enums */ export * from './models/template-appearance'; diff --git a/projects/netgrif-components-core/src/lib/data-fields/text-field/abstract-text-field.component.ts b/projects/netgrif-components-core/src/lib/data-fields/text-field/abstract-text-field.component.ts index 58bf043d29..4e7f6d053e 100644 --- a/projects/netgrif-components-core/src/lib/data-fields/text-field/abstract-text-field.component.ts +++ b/projects/netgrif-components-core/src/lib/data-fields/text-field/abstract-text-field.component.ts @@ -2,6 +2,7 @@ import {Component, Inject, Input, Optional} from '@angular/core'; import {TextField} from './models/text-field'; import {AbstractDataFieldComponent} from '../models/abstract-data-field-component'; import {NAE_INFORM_ABOUT_INVALID_DATA} from '../models/invalid-data-policy-token'; +import {NAE_SAVE_DATA_INFORM} from '../models/save-data-inform-token'; @Component({ selector: 'ncc-abstract-text-field', @@ -11,7 +12,8 @@ export abstract class AbstractTextFieldComponent extends AbstractDataFieldCompon @Input() dataField: TextField; - protected constructor(@Optional() @Inject(NAE_INFORM_ABOUT_INVALID_DATA) informAboutInvalidData: boolean | null) { - super(informAboutInvalidData); + protected constructor(@Optional() @Inject(NAE_INFORM_ABOUT_INVALID_DATA) informAboutInvalidData: boolean | null, + @Optional() @Inject(NAE_SAVE_DATA_INFORM) saveDataInform: boolean | null = false) { + super(informAboutInvalidData, saveDataInform); } } diff --git a/projects/netgrif-components/src/lib/data-fields/i18n-field/i18n-field.component.ts b/projects/netgrif-components/src/lib/data-fields/i18n-field/i18n-field.component.ts index ae3577cd98..6767c58ffa 100644 --- a/projects/netgrif-components/src/lib/data-fields/i18n-field/i18n-field.component.ts +++ b/projects/netgrif-components/src/lib/data-fields/i18n-field/i18n-field.component.ts @@ -1,5 +1,9 @@ import {Component, Inject, Optional} from '@angular/core'; -import {AbstractI18nFieldComponent, NAE_INFORM_ABOUT_INVALID_DATA} from '@netgrif/components-core'; +import { + AbstractI18nFieldComponent, + NAE_INFORM_ABOUT_INVALID_DATA, + NAE_SAVE_DATA_INFORM +} from '@netgrif/components-core'; @Component({ selector: 'nc-i18n-field', @@ -8,7 +12,8 @@ import {AbstractI18nFieldComponent, NAE_INFORM_ABOUT_INVALID_DATA} from '@netgri }) export class I18nFieldComponent extends AbstractI18nFieldComponent { - constructor(@Optional() @Inject(NAE_INFORM_ABOUT_INVALID_DATA) informAboutInvalidData: boolean | null) { - super(informAboutInvalidData); + constructor(@Optional() @Inject(NAE_INFORM_ABOUT_INVALID_DATA) informAboutInvalidData: boolean | null, + @Optional() @Inject(NAE_SAVE_DATA_INFORM) saveDataInform: boolean | null) { + super(informAboutInvalidData, saveDataInform); } } diff --git a/projects/netgrif-components/src/lib/data-fields/i18n-field/i18n-text-field/i18n-text-field.component.html b/projects/netgrif-components/src/lib/data-fields/i18n-field/i18n-text-field/i18n-text-field.component.html index 8387aefac9..fde0b34e34 100644 --- a/projects/netgrif-components/src/lib/data-fields/i18n-field/i18n-text-field/i18n-text-field.component.html +++ b/projects/netgrif-components/src/lib/data-fields/i18n-field/i18n-text-field/i18n-text-field.component.html @@ -50,7 +50,8 @@ [placeholder]="textI18nField.placeholder ? textI18nField.placeholder : ''" [required]="textI18nField.behavior.required" [(ngModel)]="currentValue[selectedLanguage]" - (blur)="setSelectedValue()"> + (focusout)="setSelectedValue(); textI18nField.unsetFocus()" + (focusin)="textI18nField.setFocus()">