From 52d7ace57ae06f085be5dd93d8fa8418278cb775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-=C3=89tienne=20Lord?= <7397743+pelord@users.noreply.github.com> Date: Mon, 4 Dec 2023 09:46:49 -0500 Subject: [PATCH] feat(common): enhance dialog components (select, checkbox, form based) * fix(common): json dialog service with undefined ignorekeys * feat(common): add a dialog to select values * wip * feat(common): enhance the confirm dialog with a yes no option * feat(common): form field, allow multiple selections * feat(common): add a dialog to select value from a scrolling list * feat(common): add a dialog to select value from checkboxes or radio * wip * wip * feat(common): enhance text field to handle password type * feat(common): add a dialog based on igo-form * feat(demo): add a dialog example section * chore(common): delete code, remplaced bu form-dialog component * chore(demo): merge conflicts resolution * chore(common): changes based on review comments * chore(common): review comments * wip * wip * chore(common): confirm dialog optional options now have an interface * chore(form-dialog): css ng-deep removal (review comment) * chore(common): form-dialog optional options now have an interface * chore(common): form dialog data is now mandatory * chore(common): form dialog, use a behaviorSubject to handle field content * chore(common): select value review comments * chore(common): form-dialog reviews * chore(common): add select-value-dialog dialog options * chore(demo): changes based on review * chore(common): form field text icon to icon-button * wip --- .../confirm-dialog.component.html | 14 +- .../confirm-dialog.component.ts | 3 + .../confirm-dialog.interface.ts | 4 + .../confirm-dialog/confirm-dialog.service.ts | 27 ++- .../common/src/lib/confirm-dialog/index.ts | 1 + .../form-dialog/form-dialog.component.html | 35 ++++ .../form-dialog/form-dialog.component.scss | 15 ++ .../lib/form-dialog/form-dialog.component.ts | 81 ++++++++ .../lib/form-dialog/form-dialog.interface.ts | 23 +++ .../src/lib/form-dialog/form-dialog.module.ts | 35 ++++ .../lib/form-dialog/form-dialog.service.ts | 33 ++++ packages/common/src/lib/form-dialog/index.ts | 3 + .../form-field-select.component.html | 1 + .../form-field/form-field-select.component.ts | 5 + .../form-field/form-field-text.component.html | 14 +- .../form-field/form-field-text.component.ts | 11 +- .../lib/form/form-field/form-field.module.ts | 4 +- .../src/lib/form/shared/form.interfaces.ts | 4 + .../common/src/lib/form/shared/form.utils.ts | 3 +- .../lib/json-dialog/json-dialog.service.ts | 2 +- .../src/lib/select-value-dialog/index.ts | 4 + ...ct-value-check-radio-dialog.component.html | 43 ++++ ...ct-value-check-radio-dialog.component.scss | 12 ++ ...lect-value-check-radio-dialog.component.ts | 88 +++++++++ .../select-value-dialog.enums.ts | 4 + .../select-value-dialog.interface.ts | 20 ++ .../select-value-dialog.module.ts | 38 ++++ .../select-value-dialog.service.ts | 33 ++++ packages/common/src/locale/en.common.json | 15 ++ packages/common/src/locale/fr.common.json | 15 ++ packages/common/src/public_api.ts | 4 + projects/demo/src/app/app.component.html | 1 + projects/demo/src/app/app.module.ts | 2 + .../common/dialog/dialog-routing.module.ts | 12 ++ .../app/common/dialog/dialog.component.html | 54 +++++ .../app/common/dialog/dialog.component.scss | 28 +++ .../src/app/common/dialog/dialog.component.ts | 185 ++++++++++++++++++ .../src/app/common/dialog/dialog.module.ts | 32 +++ .../src/app/common/form/form.component.ts | 4 +- 39 files changed, 889 insertions(+), 23 deletions(-) create mode 100644 packages/common/src/lib/confirm-dialog/confirm-dialog.interface.ts create mode 100644 packages/common/src/lib/form-dialog/form-dialog.component.html create mode 100644 packages/common/src/lib/form-dialog/form-dialog.component.scss create mode 100644 packages/common/src/lib/form-dialog/form-dialog.component.ts create mode 100644 packages/common/src/lib/form-dialog/form-dialog.interface.ts create mode 100644 packages/common/src/lib/form-dialog/form-dialog.module.ts create mode 100644 packages/common/src/lib/form-dialog/form-dialog.service.ts create mode 100644 packages/common/src/lib/form-dialog/index.ts create mode 100644 packages/common/src/lib/select-value-dialog/index.ts create mode 100644 packages/common/src/lib/select-value-dialog/select-value-check-radio-dialog.component.html create mode 100644 packages/common/src/lib/select-value-dialog/select-value-check-radio-dialog.component.scss create mode 100644 packages/common/src/lib/select-value-dialog/select-value-check-radio-dialog.component.ts create mode 100644 packages/common/src/lib/select-value-dialog/select-value-dialog.enums.ts create mode 100644 packages/common/src/lib/select-value-dialog/select-value-dialog.interface.ts create mode 100644 packages/common/src/lib/select-value-dialog/select-value-dialog.module.ts create mode 100644 packages/common/src/lib/select-value-dialog/select-value-dialog.service.ts create mode 100644 projects/demo/src/app/common/dialog/dialog-routing.module.ts create mode 100644 projects/demo/src/app/common/dialog/dialog.component.html create mode 100644 projects/demo/src/app/common/dialog/dialog.component.scss create mode 100644 projects/demo/src/app/common/dialog/dialog.component.ts create mode 100644 projects/demo/src/app/common/dialog/dialog.module.ts diff --git a/packages/common/src/lib/confirm-dialog/confirm-dialog.component.html b/packages/common/src/lib/confirm-dialog/confirm-dialog.component.html index abdfa9d646..0b945b8332 100644 --- a/packages/common/src/lib/confirm-dialog/confirm-dialog.component.html +++ b/packages/common/src/lib/confirm-dialog/confirm-dialog.component.html @@ -1,12 +1,14 @@

- {{ 'igo.common.confirmDialog.title' | translate }} + {{ titleKey | translate }}

-
{{ confirmMessage }}
+
+ {{ confirmMessage | translate }} +
- -
diff --git a/packages/common/src/lib/confirm-dialog/confirm-dialog.component.ts b/packages/common/src/lib/confirm-dialog/confirm-dialog.component.ts index 4b5321aaae..709d1f00d2 100644 --- a/packages/common/src/lib/confirm-dialog/confirm-dialog.component.ts +++ b/packages/common/src/lib/confirm-dialog/confirm-dialog.component.ts @@ -8,6 +8,9 @@ import { MatDialogRef } from '@angular/material/dialog'; }) export class ConfirmDialogComponent { public confirmMessage: string; + public titleKey: string = 'igo.common.confirmDialog.title'; + public proccessKey: string = 'igo.common.confirmDialog.confirmBtn'; + public cancelKey: string = 'igo.common.confirmDialog.cancelBtn'; constructor(public dialogRef: MatDialogRef) {} } diff --git a/packages/common/src/lib/confirm-dialog/confirm-dialog.interface.ts b/packages/common/src/lib/confirm-dialog/confirm-dialog.interface.ts new file mode 100644 index 0000000000..781bd1bfc3 --- /dev/null +++ b/packages/common/src/lib/confirm-dialog/confirm-dialog.interface.ts @@ -0,0 +1,4 @@ +export interface ConfirmDialogOptions { + title?: string; + modeYesNo?: boolean; +} diff --git a/packages/common/src/lib/confirm-dialog/confirm-dialog.service.ts b/packages/common/src/lib/confirm-dialog/confirm-dialog.service.ts index 1e44644971..e2276eff30 100644 --- a/packages/common/src/lib/confirm-dialog/confirm-dialog.service.ts +++ b/packages/common/src/lib/confirm-dialog/confirm-dialog.service.ts @@ -1,26 +1,33 @@ import { Injectable } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; -import { LanguageService } from '@igo2/core'; - import { Observable } from 'rxjs'; import { ConfirmDialogComponent } from './confirm-dialog.component'; +import { ConfirmDialogOptions } from './confirm-dialog.interface'; @Injectable() export class ConfirmDialogService { - constructor( - private dialog: MatDialog, - private languageService: LanguageService - ) {} + constructor(private dialog: MatDialog) {} - public open(message: string): Observable { + public open( + message: string, + options?: ConfirmDialogOptions + ): Observable { + const _options: ConfirmDialogOptions = { + title: 'igo.common.confirmDialog.title', + modeYesNo: false, + ...options + }; const dialogRef = this.dialog.open(ConfirmDialogComponent, { disableClose: false }); - dialogRef.componentInstance.confirmMessage = - this.languageService.translate.instant(message); - + dialogRef.componentInstance.confirmMessage = message; + dialogRef.componentInstance.titleKey = _options.title; + if (_options.modeYesNo) { + dialogRef.componentInstance.proccessKey = 'igo.common.confirmDialog.yes'; + dialogRef.componentInstance.cancelKey = 'igo.common.confirmDialog.no'; + } return dialogRef.afterClosed(); } } diff --git a/packages/common/src/lib/confirm-dialog/index.ts b/packages/common/src/lib/confirm-dialog/index.ts index cdf988051c..2cddef43ca 100644 --- a/packages/common/src/lib/confirm-dialog/index.ts +++ b/packages/common/src/lib/confirm-dialog/index.ts @@ -1,2 +1,3 @@ export * from './confirm-dialog.component'; +export * from './confirm-dialog.interface'; export * from './confirm-dialog.service'; diff --git a/packages/common/src/lib/form-dialog/form-dialog.component.html b/packages/common/src/lib/form-dialog/form-dialog.component.html new file mode 100644 index 0000000000..8b8585b968 --- /dev/null +++ b/packages/common/src/lib/form-dialog/form-dialog.component.html @@ -0,0 +1,35 @@ +

{{ data.title | translate }}

+ + +
+ + +
+ +
+ + +
+
+
+ + +
diff --git a/packages/common/src/lib/form-dialog/form-dialog.component.scss b/packages/common/src/lib/form-dialog/form-dialog.component.scss new file mode 100644 index 0000000000..722c17df9f --- /dev/null +++ b/packages/common/src/lib/form-dialog/form-dialog.component.scss @@ -0,0 +1,15 @@ +:host { + div[mat-dialog-actions] { + margin: 10px 0 0 0; + } + .form-dialog-container { + width: 100%; + padding: 10px; + + igo-form-field, + igo-form-group { + display: block; + height: auto; + } + } +} diff --git a/packages/common/src/lib/form-dialog/form-dialog.component.ts b/packages/common/src/lib/form-dialog/form-dialog.component.ts new file mode 100644 index 0000000000..c6dacb518a --- /dev/null +++ b/packages/common/src/lib/form-dialog/form-dialog.component.ts @@ -0,0 +1,81 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; + +import { LanguageService } from '@igo2/core'; + +import { BehaviorSubject } from 'rxjs'; + +import { + Form, + FormField, + FormFieldGroup, + FormFieldInputs +} from '../form/shared/form.interfaces'; +import { FormService } from '../form/shared/form.service'; +import { FormDialogData } from './form-dialog.interface'; + +@Component({ + selector: 'igo-form-dialog', + templateUrl: './form-dialog.component.html', + styleUrls: ['./form-dialog.component.scss'] +}) +export class FormDialogComponent { + form$ = new BehaviorSubject
(undefined); + data$ = new BehaviorSubject<{ [key: string]: any }>(undefined); + constructor( + public languageService: LanguageService, + public dialogRef: MatDialogRef, + private formService: FormService, + @Inject(MAT_DIALOG_DATA) + public data: FormDialogData + ) { + this.data.processButtonText = + this.data.processButtonText ?? 'igo.common.formDialog.processButtonText'; + this.data.cancelButtonText = + this.data.cancelButtonText ?? 'igo.common.formDialog.cancelButtonText'; + this.data.title = this.data.title ?? 'igo.common.formDialog.title'; + this.data$ = this.data.data$; + + let fields: FormField[] = []; + let groups: FormFieldGroup[] = []; + this.data.formFieldConfigs?.map((config) => + fields.push(this.formService.field(config)) + ); + + this.data.formGroupsConfigs?.map((formGroupsConfig) => { + const fields = formGroupsConfig.formFieldConfigs?.map((config) => + this.formService.field(config) + ); + groups.push( + this.formService.group({ name: formGroupsConfig.name }, fields) + ); + }); + const form = this.formService.form(fields, groups); + + this.form$.next(form); + } + + onSubmit(data: { [key: string]: any }) { + const form = this.form$.getValue(); + if (form.control.valid) { + this.dialogRef.close(data); + } else { + if (form.groups?.length) { + form.groups.map((group) => { + Object.keys(group.control.controls).map((k) => { + group.control.controls[k].markAsTouched(); + group.control.controls[k].updateValueAndValidity(); + }); + }); + } else { + form.fields.map((f) => { + f.control.markAsTouched(); + f.control.updateValueAndValidity(); + }); + } + } + } + cancel() { + this.dialogRef.close(); + } +} diff --git a/packages/common/src/lib/form-dialog/form-dialog.interface.ts b/packages/common/src/lib/form-dialog/form-dialog.interface.ts new file mode 100644 index 0000000000..cbdbb1e099 --- /dev/null +++ b/packages/common/src/lib/form-dialog/form-dialog.interface.ts @@ -0,0 +1,23 @@ +import { MatDialogConfig } from '@angular/material/dialog'; + +import { BehaviorSubject } from 'rxjs'; + +import { + FormFieldConfig, + FormGroupsConfig +} from '../form/shared/form.interfaces'; + +export interface FormDialogFormConfig { + formFieldConfigs?: FormFieldConfig[]; + formGroupsConfigs?: FormGroupsConfig[]; +} +export interface FormDialogData + extends FormDialogOptions, + FormDialogFormConfig {} +export interface FormDialogOptions extends MatDialogConfig { + data$?: BehaviorSubject<{ [key: string]: any }>; + title?: string; + processButtonText?: string; + cancelButtonText?: string; + notice?: string; +} diff --git a/packages/common/src/lib/form-dialog/form-dialog.module.ts b/packages/common/src/lib/form-dialog/form-dialog.module.ts new file mode 100644 index 0000000000..94196eea29 --- /dev/null +++ b/packages/common/src/lib/form-dialog/form-dialog.module.ts @@ -0,0 +1,35 @@ +import { CommonModule } from '@angular/common'; +import { ModuleWithProviders, NgModule } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatDividerModule } from '@angular/material/divider'; + +import { IgoLanguageModule } from '@igo2/core'; + +import { IgoCustomHtmlModule } from '../custom-html/custom-html.module'; +import { IgoFormModule } from '../form/form.module'; +import { FormDialogComponent } from './form-dialog.component'; +import { FormDialogService } from './form-dialog.service'; + +@NgModule({ + imports: [ + CommonModule, + MatButtonModule, + MatDialogModule, + IgoCustomHtmlModule, + IgoLanguageModule, + IgoFormModule, + MatDividerModule + ], + declarations: [FormDialogComponent], + exports: [FormDialogComponent], + providers: [FormDialogService] +}) +export class IgoFormDialogModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: IgoFormDialogModule, + providers: [] + }; + } +} diff --git a/packages/common/src/lib/form-dialog/form-dialog.service.ts b/packages/common/src/lib/form-dialog/form-dialog.service.ts new file mode 100644 index 0000000000..c659f9d7e3 --- /dev/null +++ b/packages/common/src/lib/form-dialog/form-dialog.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; + +import { Observable } from 'rxjs'; + +import { FormDialogComponent } from './form-dialog.component'; +import { + FormDialogData, + FormDialogFormConfig, + FormDialogOptions +} from './form-dialog.interface'; + +@Injectable() +export class FormDialogService { + constructor(private dialog: MatDialog) {} + + public open( + formDialogConfig?: FormDialogFormConfig, + options?: FormDialogOptions + ): Observable<{ [key: string]: any }> { + const data: FormDialogData = { + formFieldConfigs: formDialogConfig.formFieldConfigs, + formGroupsConfigs: formDialogConfig.formGroupsConfigs, + ...options + }; + const dialogRef = this.dialog.open(FormDialogComponent, { + disableClose: false, + data, + ...options + }); + return dialogRef.afterClosed(); + } +} diff --git a/packages/common/src/lib/form-dialog/index.ts b/packages/common/src/lib/form-dialog/index.ts new file mode 100644 index 0000000000..d6a7b6daa2 --- /dev/null +++ b/packages/common/src/lib/form-dialog/index.ts @@ -0,0 +1,3 @@ +export * from './form-dialog.component'; +export * from './form-dialog.interface'; +export * from './form-dialog.service'; diff --git a/packages/common/src/lib/form/form-field/form-field-select.component.html b/packages/common/src/lib/form/form-field/form-field-select.component.html index db0c8a2bf8..2aba46c15a 100644 --- a/packages/common/src/lib/form/form-field/form-field-select.component.html +++ b/packages/common/src/lib/form/form-field/form-field-select.component.html @@ -1,5 +1,6 @@ = new BehaviorSubject([]); + /** + * If the select allow multiple selections + */ + @Input() multiple: boolean = false; + /** * The field's form control */ diff --git a/packages/common/src/lib/form/form-field/form-field-text.component.html b/packages/common/src/lib/form/form-field/form-field-text.component.html index b317e54ede..eb602bf60e 100644 --- a/packages/common/src/lib/form/form-field/form-field-text.component.html +++ b/packages/common/src/lib/form/form-field/form-field-text.component.html @@ -1,7 +1,9 @@ @@ -17,6 +19,16 @@ matPrefix > + + + {{ getErrorMessage() | translate }} diff --git a/packages/common/src/lib/form/form-field/form-field-text.component.ts b/packages/common/src/lib/form/form-field/form-field-text.component.ts index 23dc4aff73..8e721f91ef 100644 --- a/packages/common/src/lib/form/form-field/form-field-text.component.ts +++ b/packages/common/src/lib/form/form-field/form-field-text.component.ts @@ -25,7 +25,7 @@ import { }) export class FormFieldTextComponent implements OnInit { disabled$: BehaviorSubject = new BehaviorSubject(false); - + hide: boolean = true; /** * The field's form control */ @@ -36,6 +36,11 @@ export class FormFieldTextComponent implements OnInit { */ @Input() placeholder: string; + /** + * if the input is a password + */ + @Input() isPassword: boolean; + /** * Field placeholder */ @@ -77,4 +82,8 @@ export class FormFieldTextComponent implements OnInit { } this.disabled$.next(disabled); } + + togglePassword() { + this.hide = !this.hide; + } } diff --git a/packages/common/src/lib/form/form-field/form-field.module.ts b/packages/common/src/lib/form/form-field/form-field.module.ts index 61edea6869..26c454bb4e 100644 --- a/packages/common/src/lib/form/form-field/form-field.module.ts +++ b/packages/common/src/lib/form/form-field/form-field.module.ts @@ -1,6 +1,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; @@ -27,7 +28,8 @@ import { FormFieldComponent } from './form-field.component'; MatInputModule, MatSelectModule, IgoLanguageModule, - IgoDynamicOutletModule + IgoDynamicOutletModule, + MatButtonModule ], exports: [ FormFieldComponent, diff --git a/packages/common/src/lib/form/shared/form.interfaces.ts b/packages/common/src/lib/form/shared/form.interfaces.ts index 0027f0c894..2464de3c07 100644 --- a/packages/common/src/lib/form/shared/form.interfaces.ts +++ b/packages/common/src/lib/form/shared/form.interfaces.ts @@ -27,6 +27,10 @@ export interface FormFieldGroupOptions { validator?: ValidatorFn; errors?: { [key: string]: string }; } +export interface FormGroupsConfig { + name: string; + formFieldConfigs: FormFieldConfig[]; +} export interface FormFieldConfig { name: string; diff --git a/packages/common/src/lib/form/shared/form.utils.ts b/packages/common/src/lib/form/shared/form.utils.ts index cb0b87644a..e44bc834ed 100644 --- a/packages/common/src/lib/form/shared/form.utils.ts +++ b/packages/common/src/lib/form/shared/form.utils.ts @@ -24,7 +24,8 @@ export function formControlIsRequired(control: AbstractControl): boolean { export function getDefaultErrorMessages(): { [key: string]: string } { return { - required: 'igo.common.form.errors.required' + required: 'igo.common.form.errors.required', + email: 'igo.common.form.errors.email' }; } diff --git a/packages/common/src/lib/json-dialog/json-dialog.service.ts b/packages/common/src/lib/json-dialog/json-dialog.service.ts index 9186042d0e..a5f094c480 100644 --- a/packages/common/src/lib/json-dialog/json-dialog.service.ts +++ b/packages/common/src/lib/json-dialog/json-dialog.service.ts @@ -15,7 +15,7 @@ export class JsonDialogService { }); dialogRef.componentInstance.data = data; dialogRef.componentInstance.title = title; - dialogRef.componentInstance.ignoreKeys = ignoreKeys; + dialogRef.componentInstance.ignoreKeys = ignoreKeys ?? []; return dialogRef.afterClosed(); } diff --git a/packages/common/src/lib/select-value-dialog/index.ts b/packages/common/src/lib/select-value-dialog/index.ts new file mode 100644 index 0000000000..8d0d5a247d --- /dev/null +++ b/packages/common/src/lib/select-value-dialog/index.ts @@ -0,0 +1,4 @@ +export * from './select-value-check-radio-dialog.component'; +export * from './select-value-dialog.enums'; +export * from './select-value-dialog.interface'; +export * from './select-value-dialog.service'; diff --git a/packages/common/src/lib/select-value-dialog/select-value-check-radio-dialog.component.html b/packages/common/src/lib/select-value-dialog/select-value-check-radio-dialog.component.html new file mode 100644 index 0000000000..2b90d75835 --- /dev/null +++ b/packages/common/src/lib/select-value-dialog/select-value-check-radio-dialog.component.html @@ -0,0 +1,43 @@ +

{{ data.title | translate }}

+ + +
+ + {{ + choice.title || choice.value + }} + + + + + {{ choice.title || choice.value }} + + +
+
+
+ + +
+
+ diff --git a/packages/common/src/lib/select-value-dialog/select-value-check-radio-dialog.component.scss b/packages/common/src/lib/select-value-dialog/select-value-check-radio-dialog.component.scss new file mode 100644 index 0000000000..bd1ee3f0c0 --- /dev/null +++ b/packages/common/src/lib/select-value-dialog/select-value-check-radio-dialog.component.scss @@ -0,0 +1,12 @@ +:host::ng-deep { + div[mat-dialog-actions] { + margin: 10px 0 0 0; + } + section.choices-section { + max-width: auto; + display: grid; + grid-template-columns: auto auto; + grid-gap: 0px; + padding: 5px; + } +} diff --git a/packages/common/src/lib/select-value-dialog/select-value-check-radio-dialog.component.ts b/packages/common/src/lib/select-value-dialog/select-value-check-radio-dialog.component.ts new file mode 100644 index 0000000000..4a9dc77826 --- /dev/null +++ b/packages/common/src/lib/select-value-dialog/select-value-check-radio-dialog.component.ts @@ -0,0 +1,88 @@ +import { Component, Inject, OnInit, Optional } from '@angular/core'; +import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { MatRadioChange } from '@angular/material/radio'; + +import { LanguageService } from '@igo2/core'; + +import { BehaviorSubject } from 'rxjs'; + +import { SelectValueDialogType } from './select-value-dialog.enums'; +import { SelectValueData } from './select-value-dialog.interface'; + +@Component({ + selector: 'igo-select-value-check-radio-dialog', + templateUrl: './select-value-check-radio-dialog.component.html', + styleUrls: ['./select-value-check-radio-dialog.component.scss'] +}) +export class SelectValueCheckRadioDialogComponent implements OnInit { + public formGroup: UntypedFormGroup; + public isDisabled$: BehaviorSubject = new BehaviorSubject(true); + + constructor( + private formBuilder: UntypedFormBuilder, + public languageService: LanguageService, + public dialogRef: MatDialogRef, + @Optional() + @Inject(MAT_DIALOG_DATA) + public data: SelectValueData + ) { + this.formGroup = this.formBuilder.group(this.getFg()); + this.data.selectFieldText = + this.data.selectFieldText ?? + 'igo.common.selectValueDialog.selectFieldText'; + this.data.processButtonText = + this.data.processButtonText ?? + 'igo.common.selectValueDialog.processButtonText'; + this.data.cancelButtonText = + this.data.cancelButtonText ?? + 'igo.common.selectValueDialog.cancelButtonText'; + this.data.title = + this.data.title ?? 'igo.common.selectValueDialog.multipleTitle'; + if (this.data.type === SelectValueDialogType.Radio) { + this.data.title = 'igo.common.selectValueDialog.title'; + } + } + + ngOnInit() { + this.formGroup.valueChanges.subscribe(() => this.canProcess()); + } + + getFg() { + const a = {}; + this.data.choices.map((l) => (a[l.value] = false)); + return a; + } + + canProcess() { + const choices = this.data.choices.map((l) => l.value); + const selectedIds = []; + choices.map((l) => + this.formGroup.value[l] ? selectedIds.push(l) : undefined + ); + selectedIds.length + ? this.isDisabled$.next(false) + : this.isDisabled$.next(true); + } + + save() { + const choices = this.data.choices.map((l) => l.value); + + const selectedChoices = []; + choices.map((l) => + this.formGroup.value[l] ? selectedChoices.push(l) : undefined + ); + this.dialogRef.close({ choices: selectedChoices }); + } + + cancel() { + this.dialogRef.close(); + } + + onChange(e: MatRadioChange) { + const choices = {}; + this.data.choices.map((l) => (choices[l.value] = false)); + choices[e.value] = true; + this.formGroup.setValue(choices); + } +} diff --git a/packages/common/src/lib/select-value-dialog/select-value-dialog.enums.ts b/packages/common/src/lib/select-value-dialog/select-value-dialog.enums.ts new file mode 100644 index 0000000000..f77b07c527 --- /dev/null +++ b/packages/common/src/lib/select-value-dialog/select-value-dialog.enums.ts @@ -0,0 +1,4 @@ +export enum SelectValueDialogType { + Radio = 'radio', + Checkbox = 'checkbox' +} diff --git a/packages/common/src/lib/select-value-dialog/select-value-dialog.interface.ts b/packages/common/src/lib/select-value-dialog/select-value-dialog.interface.ts new file mode 100644 index 0000000000..2cec81a406 --- /dev/null +++ b/packages/common/src/lib/select-value-dialog/select-value-dialog.interface.ts @@ -0,0 +1,20 @@ +import { MatDialogConfig } from '@angular/material/dialog'; + +import { SelectValueDialogType } from './select-value-dialog.enums'; + +export interface Choice { + value: any; + title: any; +} + +export interface SelectValueData extends SelectValueDialogOptions { + choices: Choice[]; +} + +export interface SelectValueDialogOptions extends MatDialogConfig { + type?: SelectValueDialogType; + title?: string; + processButtonText?: string; + cancelButtonText?: string; + selectFieldText?: string; +} diff --git a/packages/common/src/lib/select-value-dialog/select-value-dialog.module.ts b/packages/common/src/lib/select-value-dialog/select-value-dialog.module.ts new file mode 100644 index 0000000000..32c80ed226 --- /dev/null +++ b/packages/common/src/lib/select-value-dialog/select-value-dialog.module.ts @@ -0,0 +1,38 @@ +import { CommonModule } from '@angular/common'; +import { ModuleWithProviders, NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatRadioModule } from '@angular/material/radio'; + +import { IgoLanguageModule } from '@igo2/core'; + +import { IgoFormModule } from '../form/form.module'; +import { SelectValueCheckRadioDialogComponent } from './select-value-check-radio-dialog.component'; +import { SelectValueDialogService } from './select-value-dialog.service'; + +@NgModule({ + imports: [ + CommonModule, + MatCheckboxModule, + MatButtonModule, + MatRadioModule, + FormsModule, + MatDialogModule, + ReactiveFormsModule, + IgoLanguageModule, + IgoFormModule + ], + declarations: [SelectValueCheckRadioDialogComponent], + exports: [SelectValueCheckRadioDialogComponent], + providers: [SelectValueDialogService] +}) +export class IgoSelectValueDialogModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: IgoSelectValueDialogModule, + providers: [] + }; + } +} diff --git a/packages/common/src/lib/select-value-dialog/select-value-dialog.service.ts b/packages/common/src/lib/select-value-dialog/select-value-dialog.service.ts new file mode 100644 index 0000000000..c810fc16db --- /dev/null +++ b/packages/common/src/lib/select-value-dialog/select-value-dialog.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; + +import { Observable } from 'rxjs'; + +import { SelectValueCheckRadioDialogComponent } from './select-value-check-radio-dialog.component'; +import { + Choice, + SelectValueData, + SelectValueDialogOptions +} from './select-value-dialog.interface'; + +@Injectable() +export class SelectValueDialogService { + constructor(private dialog: MatDialog) {} + + public open( + choices: Choice[], + options?: SelectValueDialogOptions + ): Observable { + const data: SelectValueData = { + choices, + ...options + }; + + const dialogRef = this.dialog.open(SelectValueCheckRadioDialogComponent, { + disableClose: false, + data, + ...options + }); + return dialogRef.afterClosed(); + } +} diff --git a/packages/common/src/locale/en.common.json b/packages/common/src/locale/en.common.json index 9eb4c39220..6d4188a4f6 100644 --- a/packages/common/src/locale/en.common.json +++ b/packages/common/src/locale/en.common.json @@ -4,9 +4,23 @@ "confirmDialog": { "cancelBtn": "Cancel", "confirmBtn": "Confirm", + "yes": "Yes", + "no": "No", "title": "Confirm", "applyBtn": "Apply" }, + "selectValueDialog": { + "cancelButtonText": "Cancel", + "processButtonText": "Select", + "multipleTitle": "Please, select values", + "title": "Please, select a value", + "selectFieldText": "Options" + }, + "formDialog": { + "cancelButtonText": "Cancel", + "processButtonText": "Submit", + "title": "Fill the form" + }, "table": { "filter": "Filter" }, @@ -15,6 +29,7 @@ }, "form": { "errors": { + "email": "You must provide a email", "required": "This field is required" } }, diff --git a/packages/common/src/locale/fr.common.json b/packages/common/src/locale/fr.common.json index 54816155ba..cec2de3425 100644 --- a/packages/common/src/locale/fr.common.json +++ b/packages/common/src/locale/fr.common.json @@ -4,9 +4,23 @@ "confirmDialog": { "cancelBtn": "Annuler", "confirmBtn": "Confirmer", + "yes": "Oui", + "no": "Non", "title": "Confirmation", "applyBtn": "Appliquer" }, + "selectValueDialog": { + "cancelButtonText": "Annuler", + "processButtonText": "Sélectionner", + "multipleTitle": "Veuiller sélectionner une ou plusieurs valeurs", + "title": "Veuiller sélectionner une valeur", + "selectFieldText": "Options" + }, + "formDialog": { + "cancelButtonText": "Annuler", + "processButtonText": "Confirmer", + "title": "Veuiller remplir le formulaire" + }, "table": { "filter": "Filtre" }, @@ -15,6 +29,7 @@ }, "form": { "errors": { + "email": "Veuillez saisir un courriel", "required": "Ce champ est requis" } }, diff --git a/packages/common/src/public_api.ts b/packages/common/src/public_api.ts index 51b9e608e8..13425a3d54 100644 --- a/packages/common/src/public_api.ts +++ b/packages/common/src/public_api.ts @@ -10,6 +10,8 @@ export * from './lib/clickout/clickout.module'; export * from './lib/clone/clone.module'; export * from './lib/collapsible/collapsible.module'; export * from './lib/confirm-dialog/confirm-dialog.module'; +export * from './lib/select-value-dialog/select-value-dialog.module'; +export * from './lib/form-dialog/form-dialog.module'; export * from './lib/context-menu/context-menu.module'; export * from './lib/custom-html/custom-html.module'; export * from './lib/dom/dom.module'; @@ -53,6 +55,8 @@ export * from './lib/clickout'; export * from './lib/clone'; export * from './lib/collapsible'; export * from './lib/confirm-dialog'; +export * from './lib/select-value-dialog'; +export * from './lib/form-dialog'; export * from './lib/context-menu'; export * from './lib/custom-html'; export * from './lib/drag-drop'; diff --git a/projects/demo/src/app/app.component.html b/projects/demo/src/app/app.component.html index d95b62f64a..2e181750a1 100644 --- a/projects/demo/src/app/app.component.html +++ b/projects/demo/src/app/app.component.html @@ -38,6 +38,7 @@
{{ version.lib }}

Action + Dialogs Dynamic Component Entity Table Entity Selector diff --git a/projects/demo/src/app/app.module.ts b/projects/demo/src/app/app.module.ts index 1baad4813a..cdea5579d5 100644 --- a/projects/demo/src/app/app.module.ts +++ b/projects/demo/src/app/app.module.ts @@ -27,6 +27,7 @@ import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { AppAuthFormModule } from './auth/auth-form/auth-form.module'; import { AppActionModule } from './common/action/action.module'; +import { AppDialogModule } from './common/dialog/dialog.module'; import { AppDynamicComponentModule } from './common/dynamic-component/dynamic-component.module'; import { AppEntitySelectorModule } from './common/entity-selector/entity-selector.module'; import { AppEntityTableModule } from './common/entity-table/entity-table.module'; @@ -91,6 +92,7 @@ export const defaultTooltipOptions: MatTooltipDefaultOptions = { AppRequestModule, AppActionModule, + AppDialogModule, AppDynamicComponentModule, AppEntityTableModule, AppEntitySelectorModule, diff --git a/projects/demo/src/app/common/dialog/dialog-routing.module.ts b/projects/demo/src/app/common/dialog/dialog-routing.module.ts new file mode 100644 index 0000000000..94096ca32f --- /dev/null +++ b/projects/demo/src/app/common/dialog/dialog-routing.module.ts @@ -0,0 +1,12 @@ +import { RouterModule, Routes } from '@angular/router'; + +import { AppDialogComponent } from './dialog.component'; + +const routes: Routes = [ + { + path: 'dialog', + component: AppDialogComponent + } +]; + +export const AppDialogRoutingModule = RouterModule.forChild(routes); diff --git a/projects/demo/src/app/common/dialog/dialog.component.html b/projects/demo/src/app/common/dialog/dialog.component.html new file mode 100644 index 0000000000..b09ad0705e --- /dev/null +++ b/projects/demo/src/app/common/dialog/dialog.component.html @@ -0,0 +1,54 @@ + + +
+
Confirmation
+
+ + +
+
+ + +
+
Custom selectors
+
+ + +
+
+ +
+
Form based
+
+ + +
+
+ +
+
Renderer
+
+ +
+
+
+
diff --git a/projects/demo/src/app/common/dialog/dialog.component.scss b/projects/demo/src/app/common/dialog/dialog.component.scss new file mode 100644 index 0000000000..4f39b363ae --- /dev/null +++ b/projects/demo/src/app/common/dialog/dialog.component.scss @@ -0,0 +1,28 @@ +pre, +code { + font-family: monospace, monospace; +} +pre { + overflow: auto; +} +pre > code { + display: block; + padding: 1rem; + word-wrap: normal; +} + +.example-label { + display: table-cell; + font-size: 14px; + margin-left: 8px; + min-width: 120px; +} + +.example-button-row { + display: table-cell; + max-width: 600px; +} + +.example-button-row .mat-mdc-button-base { + margin: 8px 8px 8px 0; +} diff --git a/projects/demo/src/app/common/dialog/dialog.component.ts b/projects/demo/src/app/common/dialog/dialog.component.ts new file mode 100644 index 0000000000..90be4c987b --- /dev/null +++ b/projects/demo/src/app/common/dialog/dialog.component.ts @@ -0,0 +1,185 @@ +import { Component } from '@angular/core'; +import { Validators } from '@angular/forms'; + +import { + Choice, + ConfirmDialogService, + FormDialogService, + FormFieldConfig, + FormGroupsConfig, + JsonDialogService, + SelectValueDialogService +} from '@igo2/common'; + +import { SelectValueDialogType } from 'packages/common/src/lib/select-value-dialog/select-value-dialog.enums'; + +@Component({ + selector: 'app-dialog', + templateUrl: './dialog.component.html', + styleUrls: ['./dialog.component.scss'] +}) +export class AppDialogComponent { + constructor( + private confirmDialogService: ConfirmDialogService, + private selectValueDialogService: SelectValueDialogService, + private jsonDialogService: JsonDialogService, + private formDialogService: FormDialogService + ) {} + + confirm() { + this.confirmDialogService + .open('Do you want to continue?') + .subscribe((r) => { + alert(`Your choice is: ${r}`); + }); + } + yesno() { + this.confirmDialogService + .open('Is the sky blue today? ', { modeYesNo: true }) + .subscribe((r) => { + alert(`Your choice is: ${r}`); + }); + } + + private select(type: SelectValueDialogType) { + const choices: Choice[] = [ + { value: '1', title: 'Chocolate' }, + { value: '2', title: 'Candy' }, + { value: 3, title: 'Cake' } + ]; + + this.selectValueDialogService.open(choices, { type }).subscribe((r) => { + if (r?.choices) { + if (r.choices.length > 1) { + alert(`Your choice(s) are: ${r.choices}`); + } else { + alert(`Your choice is: ${r.choices}`); + } + } + }); + } + + check() { + this.select(SelectValueDialogType.Checkbox); + } + radio() { + this.select(SelectValueDialogType.Radio); + } + + json() { + this.jsonDialogService.open( + 'A JSON viewer', + { + isbn: '123-456-222', + author: { + lastname: 'Doe', + firstname: 'Jane' + }, + editor: { + lastname: 'Smith', + firstname: 'Jane' + }, + title: 'The Ultimate Database Study Guide', + category: ['Non-Fiction', 'Technology'] + }, + ['category'] + ); + } + form() { + const formFieldConfigs: FormFieldConfig[] = [ + { + name: 'country', + title: 'Country', + options: { + cols: 1 + } + } + ]; + + const formFieldConfigs1: FormFieldConfig[] = [ + { + name: 'city', + title: 'City', + options: { + cols: 1 + } + } + ]; + const formFieldConfigs2: FormFieldConfig[] = [ + { + name: 'id', + title: 'ID', + options: { + cols: 1, + validator: Validators.required + } + }, + { + name: 'name', + title: 'Name', + options: { + cols: 1, + validator: Validators.required + } + }, + { + name: 'status', + title: 'Status', + type: 'select', + options: { + cols: 2 + }, + inputs: { + choices: [ + { value: 1, title: 'Single' }, + { value: 2, title: 'Married' } + ] + } + } + ]; + + const formGroupsConfigs: FormGroupsConfig[] = [ + { name: 'country', formFieldConfigs: formFieldConfigs1 }, + { name: 'city', formFieldConfigs: formFieldConfigs2 } + ]; + + this.formDialogService + .open({ formFieldConfigs, formGroupsConfigs }, { minWidth: '50vh' }) + .subscribe((data) => { + if (data) { + alert(JSON.stringify(data)); + } + }); + } + + email() { + const formFieldConfigs: FormFieldConfig[] = [ + { + name: 'email', + title: 'Email', + options: { + cols: 2, + validator: Validators.compose([Validators.required, Validators.email]) + } + }, + { + name: 'password', + title: 'Password', + options: { + cols: 2, + validator: Validators.required + }, + inputs: { + isPassword: true + } + } + ]; + + this.formDialogService.open({ formFieldConfigs }).subscribe((data) => { + if (data) { + data.password = '°°°°°°°°°°'; + alert(JSON.stringify(data)); + } + }); + } +} diff --git a/projects/demo/src/app/common/dialog/dialog.module.ts b/projects/demo/src/app/common/dialog/dialog.module.ts new file mode 100644 index 0000000000..ed43752882 --- /dev/null +++ b/projects/demo/src/app/common/dialog/dialog.module.ts @@ -0,0 +1,32 @@ +import { NgModule } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatDividerModule } from '@angular/material/divider'; + +import { + IgoConfirmDialogModule, + IgoFormDialogModule, + IgoJsonDialogModule, + IgoSelectValueDialogModule +} from '@igo2/common'; +import { IgoLanguageModule } from '@igo2/core'; + +import { SharedModule } from '../../shared/shared.module'; +import { AppDialogRoutingModule } from './dialog-routing.module'; +import { AppDialogComponent } from './dialog.component'; + +@NgModule({ + declarations: [AppDialogComponent], + imports: [ + SharedModule, + AppDialogRoutingModule, + IgoConfirmDialogModule, + IgoSelectValueDialogModule, + IgoFormDialogModule, + IgoJsonDialogModule, + MatButtonModule, + IgoLanguageModule.forRoot(), + MatDividerModule + ], + exports: [AppDialogComponent] +}) +export class AppDialogModule {} diff --git a/projects/demo/src/app/common/form/form.component.ts b/projects/demo/src/app/common/form/form.component.ts index 54afc609f7..d7ab5ab47d 100644 --- a/projects/demo/src/app/common/form/form.component.ts +++ b/projects/demo/src/app/common/form/form.component.ts @@ -1,7 +1,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { Validators } from '@angular/forms'; -import { Form, FormService } from '@igo2/common'; +import { Form, FormFieldConfig, FormService } from '@igo2/common'; import { LanguageService } from '@igo2/core'; import { BehaviorSubject, Subscription } from 'rxjs'; @@ -26,7 +26,7 @@ export class AppFormComponent implements OnInit, OnDestroy { ) {} ngOnInit() { - const fieldConfigs = [ + const fieldConfigs: FormFieldConfig[] = [ { name: 'id', title: 'ID',