Skip to content

Commit 4f790ee

Browse files
committed
Merge branch 'feature/dialog-service' into develop
2 parents 57d7f29 + 599121a commit 4f790ee

File tree

10 files changed

+280
-2
lines changed

10 files changed

+280
-2
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,3 +311,10 @@ this.routeGeneratorService.navigateByRouteNames(
311311
{ routeName: 'right' }
312312
);
313313
```
314+
315+
## Class transfomer (model handling)
316+
317+
## Dialog service
318+
There is a custom dialog implementation for simpler useage of elements in dialogs.
319+
320+
The Service is named `generic-dialog.service.ts` and an example can be found in `welcome.vm.ts`.

src/app/modules/welcome/welcome.vm.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,8 @@ <h3>Validation</h3>
4242

4343
<h3>Dialog</h3>
4444
<button click.delegate="openDialog()" class="btn btn-primary">open dialog</button>
45+
46+
<h3>Generic Dialog</h3>
47+
<button click.delegate="openGenericDialog()" class="btn btn-primary">open generic dialog</button>
4548
</div>
4649
</template>

src/app/modules/welcome/welcome.vm.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import { autoinject } from 'aurelia-framework';
1+
import { autoinject, PLATFORM } from 'aurelia-framework';
22
import { ValidationControllerFactory, ValidationController, validateTrigger, ValidationRules } from 'aurelia-validation';
33
import { DialogService } from 'aurelia-dialog';
44

55
import { LogManager, Logger} from './../../services/logger.service';
66
import { AppConfigService } from './../../services/app-config.service';
77
import { LanguageService } from './../../services/language.service';
88
import { EditPersonCustomElement } from './../../resources/elements/edit-person/edit-person.element';
9+
import { GenericDialogService } from './../../services/generic-dialog.service';
10+
import { ShowPersonCustomElement } from '../../resources/elements/show-person/show-person.element';
911

1012
@autoinject
1113
export class WelcomeViewModel {
@@ -24,7 +26,8 @@ export class WelcomeViewModel {
2426
private appConfigService: AppConfigService,
2527
private languageService: LanguageService,
2628
private dialogService: DialogService,
27-
validationControllerFactory: ValidationControllerFactory
29+
validationControllerFactory: ValidationControllerFactory,
30+
private genericDialogService: GenericDialogService,
2831
) {
2932
this.logger = LogManager.getLogger('Welcome VM');
3033
this.logger.info('appConfig => name:', this.appConfigService.getName());
@@ -86,6 +89,31 @@ export class WelcomeViewModel {
8689
this.languageService.setLocale(this.languageService.getSupportedLanguages()[0]);
8790
}
8891
}
92+
93+
public openGenericDialog(): void {
94+
const dialog = this.genericDialogService.showDialog<ShowPersonCustomElement>({
95+
title: 'Zeige Person', // Can be a translation string
96+
contentViewModel: PLATFORM.moduleName('resources/elements/show-person/show-person.element'),
97+
contentModel: {
98+
firstName: this.firstName
99+
},
100+
buttons: [
101+
GenericDialogService.createCancelButton<ShowPersonCustomElement>(() => Promise.resolve()),
102+
GenericDialogService.createSaveButton<ShowPersonCustomElement>(ele => {
103+
this.logger.debug('Clicked on save in dialog', ele);
104+
return Promise.resolve();
105+
}, ele => ele.isValid)
106+
]
107+
});
108+
109+
dialog.whenClosed(result => {
110+
if (!result.wasCancelled) {
111+
this.logger.debug('Dialog not canceld', result);
112+
} else {
113+
this.logger.debug('Dialog canceld', result);
114+
}
115+
});
116+
}
89117
}
90118

91119
export class UpperValueConverter {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<template>
2+
<ux-dialog>
3+
<ux-dialog-header>
4+
<div class="row align-items-center">
5+
<div class="col-auto">
6+
<h2 t.bind="title"></h2>
7+
</div>
8+
<div class="col">
9+
<button repeat.for="button of buttons" click.delegate="execute(button)" class="btn ${button.classes} ml-2" disabled.bind="!button.isEnabled"
10+
t.bind="button.title">
11+
</button>
12+
</div>
13+
</div>
14+
</ux-dialog-header>
15+
<ux-dialog-body>
16+
<compose view-model.ref="composeElement" view-model.bind="contentView" model.bind="contentModel"></compose>
17+
</ux-dialog-body>
18+
</ux-dialog>
19+
</template>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { autoinject } from 'aurelia-framework';
2+
import { Compose } from 'aurelia-templating-resources';
3+
import { DialogController } from 'aurelia-dialog';
4+
5+
import {
6+
IDialogConfiguration,
7+
DialogButton,
8+
IActionHandlers
9+
} from '../../../services/generic-dialog.service';
10+
11+
interface IComposeWithViewModel<T> extends Compose {
12+
currentViewModel: T;
13+
}
14+
15+
@autoinject
16+
export class Dialog<T> {
17+
public buttons!: DialogButton<T>[];
18+
public contentModel: any;
19+
public contentView!: string;
20+
public title!: string;
21+
public composeElement!: IComposeWithViewModel<T>;
22+
private config!: IDialogConfiguration<T>;
23+
24+
constructor(
25+
public controller: DialogController
26+
) {
27+
}
28+
29+
public attached(): void {
30+
this.setup();
31+
}
32+
33+
public activate(dialogConfig: IDialogConfiguration<T>): void {
34+
this.config = dialogConfig;
35+
this.contentModel = this.config.contentModel;
36+
this.contentView = this.config.contentViewModel;
37+
}
38+
39+
public execute(button: DialogButton<T>): void {
40+
this.activateButtons(false);
41+
button.execute().then(() => this.activateButtons(true));
42+
}
43+
44+
private activateButtons(activate: boolean): void {
45+
this.buttons.forEach(button => button.enabled = activate);
46+
}
47+
48+
private setup(): void {
49+
this.title = this.config.title;
50+
const actions: IActionHandlers<T> = {
51+
ok: this.controller.ok.bind(this.controller),
52+
cancel: this.controller.cancel.bind(this.controller),
53+
error: this.controller.error.bind(this.controller)
54+
};
55+
this.buttons = this.config.buttons.map(descriptor => {
56+
return new DialogButton(descriptor, this.composeElement.currentViewModel, actions);
57+
});
58+
}
59+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template>
2+
<h1>${ firstName }</h1>
3+
</template>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { autoinject } from 'aurelia-framework';
2+
3+
@autoinject
4+
export class ShowPersonCustomElement {
5+
6+
public firstName = '';
7+
8+
public activate(args): void {
9+
this.firstName = args.firstName;
10+
}
11+
12+
public get isValid(): boolean {
13+
// We could check if a form is valid
14+
return true;
15+
}
16+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { autoinject, PLATFORM } from 'aurelia-framework';
2+
import { DialogService, DialogOpenPromise, DialogCancellableOpenResult } from 'aurelia-dialog';
3+
import * as _ from 'lodash';
4+
5+
type ButtonAction<T> = (element: T) => Promise<any>;
6+
type IsEnabled<T> = (element: T) => boolean;
7+
8+
export interface IDialogConfiguration<T> {
9+
title: string;
10+
buttons: IButtonDescriptor<T>[];
11+
contentViewModel: string;
12+
contentModel?: any;
13+
}
14+
15+
export enum ButtonType {
16+
OK, CANCEL, OTHER, ERROR
17+
}
18+
19+
export interface IButtonDescriptor<T> {
20+
title: string;
21+
type: ButtonType;
22+
onAction: ButtonAction<T>;
23+
classes: string;
24+
enabled: IsEnabled<T>;
25+
}
26+
27+
interface IDialogButton {
28+
execute: () => void;
29+
isEnabled: boolean;
30+
classes: string;
31+
title: string;
32+
}
33+
34+
export interface IActionHandlers<T> {
35+
ok: (output: any) => T;
36+
cancel: (output: any) => T;
37+
error: (output: any) => T;
38+
}
39+
40+
export class DialogButton<T> implements IDialogButton {
41+
public enabled: boolean = true;
42+
43+
public constructor(
44+
private descriptor: IButtonDescriptor<T>,
45+
private context: T,
46+
private actions: IActionHandlers<T>
47+
) {
48+
}
49+
50+
public get isEnabled(): boolean {
51+
return this.enabled && this.descriptor.enabled(this.context);
52+
}
53+
54+
public get title(): string {
55+
return this.descriptor.title;
56+
}
57+
58+
public get classes(): string {
59+
return this.descriptor.classes;
60+
}
61+
62+
public async execute(): Promise<void> {
63+
try {
64+
const result = await this.descriptor.onAction(this.context);
65+
switch (this.descriptor.type) {
66+
case ButtonType.OK:
67+
this.actions.ok(result);
68+
break;
69+
case ButtonType.CANCEL:
70+
this.actions.cancel(result);
71+
break;
72+
case ButtonType.ERROR:
73+
this.actions.error(result);
74+
break;
75+
default:
76+
break;
77+
}
78+
} finally {
79+
return Promise.resolve();
80+
}
81+
}
82+
}
83+
84+
@autoinject
85+
export class GenericDialogService {
86+
87+
public constructor(
88+
public dialogService: DialogService
89+
) {
90+
}
91+
92+
private static createButton<T>(
93+
title: string,
94+
onAction: ButtonAction<T>,
95+
type: ButtonType,
96+
enabled: IsEnabled<T> = () => true,
97+
classes = ''): IButtonDescriptor<T> {
98+
return { title, type, onAction, classes, enabled };
99+
}
100+
101+
public static createSaveButton<T>(action: ButtonAction<T>, enabled?: IsEnabled<T>, classes = ''): IButtonDescriptor<T> {
102+
return GenericDialogService.createButton<T>(
103+
'ACTIONS.SAVE', action, ButtonType.OK, enabled, classes + ' btn-primary'
104+
);
105+
}
106+
public static createCancelButton<T>(action: ButtonAction<T>, enabled?: IsEnabled<T>, classes = ''): IButtonDescriptor<T> {
107+
return GenericDialogService.createButton<T>(
108+
'ACTIONS.CANCEL', action, ButtonType.CANCEL, enabled, classes + ' btn-outline-secondary'
109+
);
110+
}
111+
public static createCloseButton<T>(action: ButtonAction<T>, enabled?: IsEnabled<T>, classes = ''): IButtonDescriptor<T> {
112+
return GenericDialogService.createButton<T>(
113+
'ACTIONS.CLOSE', action, ButtonType.CANCEL, enabled, classes + ' btn-outline-secondary'
114+
);
115+
}
116+
public static createOtherButton<T>(title: string, action: ButtonAction<T>, enabled?: IsEnabled<T>, classes = ''): IButtonDescriptor<T> {
117+
return GenericDialogService.createButton<T>(title, action, ButtonType.OTHER, enabled, classes);
118+
}
119+
public static createErrorButton<T>(title: string, action: ButtonAction<T>, enabled?: IsEnabled<T>, classes = ''): IButtonDescriptor<T> {
120+
return GenericDialogService.createButton<T>(title, action, ButtonType.ERROR, enabled, classes);
121+
}
122+
123+
public showDialog<T>(model: IDialogConfiguration<T>): DialogOpenPromise<DialogCancellableOpenResult> { // TODO: change type
124+
if (model.contentModel) {
125+
Object.keys(model.contentModel).forEach(key => {
126+
if (!_.isFunction(model.contentModel[key])) {
127+
model.contentModel[key] = _.cloneDeep(model.contentModel[key]);
128+
}
129+
});
130+
}
131+
return this.dialogService.open({ viewModel: PLATFORM.moduleName('resources/elements/dialog/dialog.element'), model });
132+
}
133+
}

src/locales/de_CH.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
"VALIDATIONS": {
33
"required": "${$displayName} fehlt!"
44
},
5+
"ACTIONS": {
6+
"SAVE": "Speichern",
7+
"CANCEL": "Abbrechen",
8+
"CLOSE": "Schliessen"
9+
},
510
"TITLE": "Übersetztungs Titel",
611
"TR_ATTR": "...in Deutsch",
712
"TR_SIGNALER": "Signaler Beispiel funktioniert..."

src/locales/en_US.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
"VALIDATIONS": {
33
"required": "${$displayName} missing!"
44
},
5+
"ACTIONS": {
6+
"SAVE": "Save",
7+
"CANCEL": "Cancel",
8+
"CLOSE": "Close"
9+
},
510
"TITLE": "Translation Title",
611
"TR_ATTR": "...in English",
712
"TR_SIGNALER": "Signaler Example works..."

0 commit comments

Comments
 (0)