A very lite Aurelia dialog plugin.
This project is a cut-off version of original aurelia-dialog, with added focus-trap from Micromodal.
- removed most of the features, simplified APIs (incompatible).
- give users total control on CSS and layout.
- properly trap focus, prevent users from using Tab and Enter to hit button/anchor on the background DOM behind the active dialog.
There are breaking changes, if aurelia-dialog did not trouble you, don't migrate.
For users who absolutely need proper focus trap, or have strong need for the CSS customization, aurelia-dialog-lite is a good fit.
npm i aurelia-dialog-lite
Or
yarn add aurelia-dialog-lite
In Aurelia app's src/main.js
(or src/main.ts
).
aurelia.use.plugin(PLATFORM.moduleName('aurelia-dialog-lite'));
Note
PLATFORM.moduleName()
wrapper is only needed for app built with webpack.
Optionally, you can modify the default dialog settings.
aurelia.use.plugin(PLATFORM.moduleName('aurelia-dialog-lite'), {
host: document.body,
overlayClassName: 'dialog-lite-overlay',
escDismiss: false,
overlayDismiss: false
});
- host is the element where aurelia-dialog-lite appends all dialogs to. Default to HTML body.
- overlayClassName is the CSS class name for the overlay element. Default to
"dialog-lite-overlay"
. See simplifed layout for more details. - escDismiss allows for closing the dialog via the keyboard ESC key.
- overlayDismiss allows for closing the dialog via clicking the overlay element.
They are the only settings available in aurelia-dialog-lite.
Before getting into the code, it's better to understand how aurelia-dialog-lite renders the dialogs.
For a dialog with following HTML template:
<template>
<div class="my-dialog">
...
</div>
</template>
aurelia-dialog-lite inserts following to the host
element (default to HTML body) .
<body>
...
<!-- following is appended by aurelia-dialog-lite -->
<div class="dialog-lite-overlay">
<div class="my-dialog">
...
</div>
</div>
<!-- additional dialog has its own overlay element -->
<div class="dialog-lite-overlay">
<div class="another-dialog">
...
</div>
</div>
</body>
Imaging aurelia-dialog-lite only changes the <template>
in your dialog HTML template to <div class="dialog-lite-overlay">
, then appends the whole thing to HTML body.
Original aurelia-dialog creates lots of DOM layers above your actual dialog, making CSS customization quite difficult.
The default CSS for the overlay element is quite simple. It covers the full screen, uses flex layout to centre the user dialog. The following is all of aurelia-dialog-lite's CSS (injected to HTML head automatically).
.dialog-lite-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
Note not all browsers support flex layout. To support old browsers like IE11, override
.dialog-lite-overlay
in your local CSS.
aurelia-dialog-lite injects the above CSS piece onto the very beginning of the HTML head, so your local CSS will always override/patch the original.
This is all that aurelia-dialog-lite does on the DOM. Knowing your dialog DOM is simply wrapped by a <div class="dialog-lite-overlay">
, you are responsible to position and style your dialog through local CSS.
Original aurelia-dialog supports few custom elements:
<ux-dialog>
,<ux-dialog-header>
,<ux-dialog-body>
,<ux-dialog-footer>
, and one custom attributeattach-focus
. They are all removed from aurelia-dialog-lite.
Inject DialogService
to your view model.
Following guide shows all code examples in ESNext. It's very similar in TypeScript. At the end of each section, all the runnable code demos have both ESNext and TypeScript versions.
import {inject} from 'aurelia-framework';
import {DialogService} from 'aurelia-dialog-lite';
@inject(DialogService)
export class MyComponent {
constructor(dialogService) {
this.dialogService = dialogService;
}
}
Then use dialogService.open()
to show a dialog.
import {TestDialog} from './test-dialog';
export class MyComponent {
//...
showTestDialog() {
this.dialogService.open({
viewModel: TestDialog,
model: { title: 'Test dialog' }
}).then(
output => {
// output is the dialog output, can be anything.
},
err => {
// err is the cancellation error.
// You can ignore it most of the time.
}
)
}
}
Or use async/await
async showTestDialog() {
try {
const output = await this.dialogService.open({
viewModel: TestDialog,
model: { title: 'Test dialog' }
});
// ...
} catch (err) {
// cancelled
)
}
Note different from original aurelia-dialog, we simply return dialog closePromise. It either resolves to an output, or rejects to an cancellation error which you can ignore most of the time.
- viewModel is the dialog implementation.
- view is the optional HTML template for the dialog. Only for non-default HTML template file, or view only dialog implementation. See advanced usage for more details.
- model is an optional model object which will be passed to
activate(model?: any)
lifecycle callback of the dialog.
In dialog, inject DialogController
so that you can call controller.cancel()
or controller.ok()
later.
import {inject} from 'aurelia-framework';
import {DialogController} from 'aurelia-dialog-lite';
@inject(DialogController}
export class TestDialog {
constructor(controller) {
this.controller = controller;
}
// The model set by dialogService.open()
activate(model) {
this.title = model.title;
}
}
<template>
<div class="my-dialog">
<p>${title}</p>
<button click.trigger="controller.cancel()">Cancel</button>
<button click.trigger="controller.ok()">OK</button>
</div>
</template>
controller.cancel(reason: string = 'cancelled')
supports an optional reason for the cancellation. Default to"cancelled"
. You can get it out from the rejectederr.message
followingdialogService.open()
.controller.ok(output?: any)
supports an optional output for the resolution. The output is the resolved result of the promise returned bydialogService.open()
.
Note different from original aurelia-dialog,
controller.cancel()
andcontroller.ok()
are now synchronous, doesn't return a promise any more.
Demo | ||
---|---|---|
Baisc usage | ESNext | TypeScript |
On top of the global settings through aurelia.use.plugin()
, there are two more places you can customise the settings per dialog.
this.dialogService.open({
viewModel: TestDialog,
escDismiss: true
})
You can customise host
, overlayClassName
, escDismiss
, and overlayDismiss
. All the options are customisable per dialog.
export class TestDialog {
constructor(controller) {
this.controller = controller;
this.controller.overlayDismiss = true;
}
}
The injected dialog controller instance is unique per dialog. Customise the settings in constructor so that the later rendering will honour the changed settings.
Note the API is simplified from the original aurelia-dialig:
aurelia-dialog
this.controller.settings.overlayDismiss = true;
aurelia-dialog-lite
this.controller.overlayDismiss = true;
Demo | dialogService.open | dialog constructor |
---|---|---|
Customise settings | ESNext | TypeScript |
API dialogService.create()
is similar to dialogService.open()
, but it returns a promise that resolves to new dialog controller, so you can control the dialog from outside.
In fact, dialogService.open()
is simply implemented through dialogService.create()
.
public open(contextSettings: DialogContextSettings = {}): Promise<any> {
return this.create(contextSettings).then(
dialogController => dialogController.closePromise
);
}
After you got the dialog controller from dialogService.create()
, you can use controller.ok(...)
or controller.cancel()
to close the dialog based on what happened outside of the dialog (e.g. a remote update).
this.dialogService.create({...})
.then(controller => {
// you can later call controller.ok(...) or controller.cancel()
// to close the dialog from outside.
}
Or async/await
const controller = await this.dialogService.create({...});
// ...
Following demo shows how to use dialog controller to close the dialog from outside. It also demonstrate native bootstrap modal layout inside aurelia-dialog-lite.
Note technically the delayed closure can be implemented in the dialog class itself. We only use delayed closure as a simple example of controlling dialog from outside.
Demo | ||
---|---|---|
Programmatically close dialog | ESNext | TypeScript |
Demo | ||
---|---|---|
View-only dialog | ESNext | TypeScript |
Demo | ||
---|---|---|
Multiple view templates | ESNext | TypeScript |
DialogService exposes the controllers of the active dialogs through dialogSerivce.controllers
, you can manually call ok()
or cancel()
on one or more of the controllers. It also exposes a simple boolean flag dialogSerivce.hasActiveDialog
which is only true
when there is at least one active dialog.
dialogSerivce.cancelAll()
is a convenient method to cancel all active dialogs. It returns a promise to be resolved after all dialogs were cancelled.
dialogSerivce.cancelAll()
replaced aurelia-dialog's originaldialogSerivce.closeAll()
.
aurelia-dialog-lite does not provide an option to set starting z-index. Just put an z-index in your CSS directly. For example set to 1040 as same as boostrap's modal backdrop.
.dialog-lite-overlay {
z-index: 1040;
/* if you have a customised bootstrap scss
z-index: $zindex-modal-backdrop;
*/
}
We removed "Enter" key support from original aurelia-dialog because of an edge case. When user uses "Tab" to shift focus to a cancel button, then hit "Enter" key, he/she wants to "click" the cancel button, not to trigger the default action of "Enter" key.
Likely aurelia-combo fixed the edge case. User can use aurelia-combo and aurelia-dialog-lite together to handle default "Enter" action without worrying about misfired "Enter" key on focused button.
Demo | ||
---|---|---|
Use aurelia-combo to handle "Enter" key | ESNext | TypeScript |
To customise the position of dialogs, you can directly overwrite the default css class .dialog-lite-overlay
or provide your own css class together with overlayClassName
option (can be either global or local setting).
Here is an example of customised .dialog-lite-overlay
without using flex layout (some old browsers do not support it well).
Demo | ||
---|---|---|
Customise position | ESNext | TypeScript |
TBD: due to manual rendering, aurelia-dialog-lite doesn't work with aurelia-animator-css right now.
MIT.