Permalink
Browse files

feat(dialog-service, dialog-controller): add "rejectOnCancel"

add test for "yieldController" and "rejectOnCancel" settings
refactor replace arrow functions with plain functions for "describe" and "it" bodies
  • Loading branch information...
StrahilKazlachev committed Nov 11, 2016
1 parent e5a9040 commit e8cd8e523ba91b6b2cf7d0cd5062a8ae36b105ad
View
@@ -268,6 +268,10 @@ The settings available for the dialog are set on the dialog controller on a per-
- `centerHorizontalOnly` means that the dialog will be centered horizontally, and the vertical alignment is left up to you. (defaults to false)
- `position` a callback that is called right before showing the modal with the signature: `(modalContainer: Element, modalOverlay: Element) => void`. This allows you to setup special classes, play with the position, etc... If specified, `centerHorizontalOnly` is ignored. (optional)
- `ignoreTransitions` is a Boolean you must set to `true` if you disable css animation of your dialog. (optional, default to false)
+- `yieldController` is a Boolean you must set to `true` if you want to execute some logic when the dialog gets open and/or get access to the dialog controller
+- `rejectOnCancel` is a Boolean you must set to `true` if you want to handle cancellations as rejection. The reason will be instance of `DialogCancelError` - the property `wasCancelled` will be set to `true` and if cancellation data was provided it will be set to the `reason` property.
+
+> Warning: Plugin authors are advised to be explicit with settings that change behavior(`yieldController` and `rejectOnCancel`).
```javascript
export class Prompt {
@@ -297,10 +301,10 @@ It is possible to resolve and close (using cancel/ok/error methods) dialog in th
}
person = { firstName: 'Wade', middleName: 'Owen', lastName: 'Watts' };
submit(){
- this.dialogService.openAndYieldController({viewModel: EditPerson, model: this.person}).then(controller => {
+ this.dialogService.open({yieldController: true, viewModel: EditPerson, model: this.person}).then(openDialogResult => {
// Note you get here when the dialog is opened, and you are able to close dialog
- // Promise for the result is stored in controller.result property
- controller.result.then((response) => {
+ // Promise for the result is stored in openDialogResult.closeResult property
+ openDialogResult.closeResult.then((response) => {
if (!response.wasCancelled) {
console.log('good');
@@ -313,7 +317,7 @@ It is possible to resolve and close (using cancel/ok/error methods) dialog in th
})
setTimeout(() => {
- controller.cancel('canceled outside after 3 sec')
+ openDialogResult.controller.cancel('canceled outside after 3 sec')
}, 3000)
});
View
@@ -27,8 +27,10 @@ var paths = {
paths.ignore = ['aurelia-dialog.js'];
paths.files = [
+ 'interfaces.js',
'dialog-options.js',
'dialog-result.js',
+ 'dialog-cancel-error.js',
'ai-dialog-body.js',
'ai-dialog-footer.js',
'ai-dialog-header.js',
@@ -0,0 +1,9 @@
+export class DialogCancelError extends Error {
+ wasCancelled = true;
+ reason: any;
+
+ constructor(cancellationReason: any = null) {
+ super('Dialog cancelled.');
+ this.reason = cancellationReason;
+ }
+}
View
@@ -1,5 +1,6 @@
import {invokeLifecycle} from './lifecycle';
import {DialogResult} from './dialog-result';
+import {DialogCancelError} from './dialog-cancel-error';
/**
* A controller object for a Dialog instance.
@@ -68,9 +69,10 @@ export class DialogController {
.then(() => {
return this.renderer.hideDialog(this);
}).then(() => {
- if (this.settings.throwOnCancel && !ok) {
- const cancellationError = new Error('Dialog canceled.');
- cancellationError.cancellationReason = output;
+ if (this.settings.rejectOnCancel && !ok) {
+ const cancellationError = new DialogCancelError(output);
+ cancellationError.reason = output;
+ this._reject(cancellationError);
throw cancellationError;
}
let result = new DialogResult(!ok, output);
View
@@ -3,6 +3,6 @@ export let dialogOptions = {
centerHorizontalOnly: false,
startingZIndex: 1000,
ignoreTransitions: false,
- throwOnCancel: false,
+ rejectOnCancel: false,
yieldController: false
};
View
@@ -5,6 +5,7 @@ import {DialogController} from './dialog-controller';
import {Renderer} from './renderer';
import {invokeLifecycle} from './lifecycle';
import {dialogOptions} from './dialog-options';
+import {DialogCancelError} from './dialog-cancel-error';
import {DialogSettings, OpenDialogResult, CloseDialogResult} from './interfaces';
/**
@@ -48,37 +49,17 @@ export class DialogService {
_removeController(this, dialogController);
});
- let openResult = _getViewModel(this.container, this.compositionEngine, childContainer, dialogController).then((instruction) => {
- dialogController.viewModel = instruction.viewModel;
- dialogController.slot = instruction.viewSlot;
-
- return invokeLifecycle(dialogController.viewModel, 'canActivate', dialogController.settings.model)
- .then(canActivate => {
- if (canActivate) {
- return this.compositionEngine.compose(instruction).then(controller => {
- this.controllers.push(dialogController);
- this.hasActiveDialog = !!this.controllers.length;
- dialogController.controller = controller;
- dialogController.view = controller.view;
-
- return dialogController.renderer.showDialog(dialogController).then(() => {
- return {
- wasCancelled: false,
- controller: dialogController,
- closeResult
- };
- });
- });
- }
-
- if (settings.throwOnCancel) {
- throw new Error('Dialog cancelled.');
- }
-
- return {
- wasCancelled: true // see aurelia/dialog#223
- };
- });
+ let openResult = _getViewModel(this.container, this.compositionEngine, childContainer, dialogController).then((compositionContext) => {
+ dialogController.viewModel = compositionContext.viewModel;
+ dialogController.slot = compositionContext.viewSlot;
+ return _tryActivate(this, dialogController, compositionContext).then((result) => {
+ if (result) { return result; }
+ return {
+ wasCancelled: false,
+ controller: dialogController,
+ closeResult
+ };
+ });
});
return settings.yieldController ? openResult : openResult.then(result => result.wasCancelled ? result : result.closeResult);
@@ -93,7 +74,7 @@ function _createSettings(settings) {
function _getViewModel(container, compositionEngine, childContainer, dialogController) {
let host = dialogController.renderer.getDialogContainer();
- let instruction = {
+ let compositionContext = {
container: container,
childContainer: childContainer,
model: dialogController.settings.model,
@@ -103,19 +84,46 @@ function _getViewModel(container, compositionEngine, childContainer, dialogContr
host: host
};
- if (typeof instruction.viewModel === 'function') {
- instruction.viewModel = Origin.get(instruction.viewModel).moduleId;
+ if (typeof compositionContext.viewModel === 'function') {
+ compositionContext.viewModel = Origin.get(compositionContext.viewModel).moduleId;
}
- if (typeof instruction.viewModel === 'string') {
- return compositionEngine.ensureViewModel(instruction);
+ if (typeof compositionContext.viewModel === 'string') {
+ return compositionEngine.ensureViewModel(compositionContext);
}
- return Promise.resolve(instruction);
+ return Promise.resolve(compositionContext);
}
-function _removeController(service, controller) {
- let i = service.controllers.indexOf(controller);
+function _tryActivate(service, dialogController, compositionContext) {
+ return invokeLifecycle(dialogController.viewModel, 'canActivate', dialogController.settings.model)
+ .then((canActivate => {
+ if (canActivate) {
+ return _composeAndShowDialog(service, dialogController, compositionContext);
+ }
+
+ if (dialogController.settings.rejectOnCancel) {
+ throw new DialogCancelError();
+ }
+
+ return {
+ wasCancelled: true
+ };
+ }));
+}
+
+function _composeAndShowDialog(service, dialogController, compositionContext) {
+ return service.compositionEngine.compose(compositionContext).then(controller => {
+ service.controllers.push(dialogController);
+ service.hasActiveDialog = !!service.controllers.length;
+ dialogController.controller = controller;
+ dialogController.view = controller.view;
+ return dialogController.renderer.showDialog(dialogController)
+ });
+}
+
+function _removeController(service, dialogCOntroller) {
+ let i = service.controllers.indexOf(dialogCOntroller);
if (i !== -1) {
service.controllers.splice(i, 1);
service.hasActiveDialog = !!service.controllers.length;
View
@@ -5,7 +5,7 @@ interface DialogSettings {
lock?: boolean;
startingZIndex?: number;
centerHorizontalOnly?: boolean;
- throwOnCancel?: boolean;
+ rejectOnCancel?: boolean;
yieldController?: boolean;
ignoreTransitions?: boolean;
position?: (modalContainer: Element, modalOverlay: Element) => void;
@@ -17,7 +17,7 @@ interface CloseDialogResult {
}
interface OpenDialogResult {
- wasCancelled: boolean; // see aurelia/dialog#223
+ wasCancelled: boolean;
controller?: DialogController;
closeResult?: Promise<CloseDialogResult>;
}
@@ -6,7 +6,7 @@ import {bootstrap} from 'aurelia-bootstrapper';
let element = document.createElement('div');
-describe('modal gets focused when attached', () => {
+describe('modal gets focused when attached', function () {
let component;
let attachFocus;
let container;
@@ -17,12 +17,12 @@ describe('modal gets focused when attached', () => {
first;
}
- beforeEach(() => {
+ beforeEach(function () {
viewModel = new ViewModel();
});
- describe('when using attribute without .bind', () => {
- beforeEach(() => {
+ describe('when using attribute without .bind', function () {
+ beforeEach(function () {
component = StageComponent
.withResources('src/attach-focus')
.inView('\
@@ -32,16 +32,16 @@ describe('modal gets focused when attached', () => {
.boundTo(viewModel);
});
- it('sets focus to no value element', (done) => {
+ it('sets focus to no value element', function (done) {
component.create(bootstrap).then(() => {
expect(document.activeElement).toBe(viewModel.noValueEl);
done();
});
});
});
- describe('when binding to vm property', () => {
- beforeEach(() => {
+ describe('when binding to vm property', function () {
+ beforeEach(function () {
component = StageComponent
.withResources('src/attach-focus')
.inView('\
@@ -52,15 +52,15 @@ describe('modal gets focused when attached', () => {
.boundTo(viewModel);
});
- it('sets focus to first element when true', (done) => {
+ it('sets focus to first element when true', function (done) {
viewModel.first = true;
component.create(bootstrap).then(() => {
expect(document.activeElement).toBe(viewModel.firstEl);
done();
});
});
- it('sets focus to second element when false', (done) => {
+ it('sets focus to second element when false', function (done) {
viewModel.first = false;
component.create(bootstrap).then(() => {
expect(document.activeElement).toBe(viewModel.secondEl);
@@ -69,7 +69,7 @@ describe('modal gets focused when attached', () => {
});
});
- afterEach(() => {
+ afterEach(function () {
component.dispose();
});
});
@@ -11,9 +11,9 @@ let aurelia = {
transient: () => {}
};
-describe('DialogConfiguration', () => {
+describe('DialogConfiguration', function () {
let configuration;
- beforeEach(() => {
+ beforeEach(function () {
configuration = new DialogConfiguration(aurelia);
Object.keys(dialogOptions).forEach((key) => {
@@ -23,16 +23,16 @@ describe('DialogConfiguration', () => {
Object.assign(dialogOptions, defaultDialogOptions);
});
- describe('useRenderer', () => {
- it('should register a renderer as a transient', () => {
+ describe('useRenderer', function () {
+ it('should register a renderer as a transient', function () {
let renderer = {};
spyOn(aurelia, 'transient');
configuration.useRenderer(renderer);
configuration._apply();
expect(aurelia.transient).toHaveBeenCalledWith(Renderer, renderer);
});
- it('should export settings', () => {
+ it('should export settings', function () {
let renderer = {};
let settings = { first: 'first', second: 'second' };
configuration.useRenderer(renderer, settings);
@@ -42,17 +42,17 @@ describe('DialogConfiguration', () => {
});
});
- describe('useResource', () => {
- it('should call globalResources', () => {
+ describe('useResource', function () {
+ it('should call globalResources', function () {
spyOn(aurelia, 'globalResources');
configuration.useResource('ai-dialog');
configuration._apply();
expect(aurelia.globalResources).toHaveBeenCalled();
});
});
- describe('useDefaults', () => {
- it('should call useRenderer with the default renderer', () => {
+ describe('useDefaults', function () {
+ it('should call useRenderer with the default renderer', function () {
spyOn(configuration, 'useRenderer').and.callThrough();
spyOn(configuration, 'useResource').and.callThrough();
@@ -66,7 +66,7 @@ describe('DialogConfiguration', () => {
expect(configuration.useResource).toHaveBeenCalledWith('attach-focus');
});
- it('should inject default style', () => {
+ it('should inject default style', function () {
spyOn(DOM, 'injectStyles').and.callThrough();
configuration.useDefaults();
@@ -75,8 +75,8 @@ describe('DialogConfiguration', () => {
});
});
- describe('useCSS', () => {
- it('should skip injecting empty style', () => {
+ describe('useCSS', function () {
+ it('should skip injecting empty style', function () {
spyOn(DOM, 'injectStyles').and.callThrough();
// Undefined css
Oops, something went wrong.

0 comments on commit e8cd8e5

Please sign in to comment.