Now.js Framework Documentation
DialogManager
DialogManager
Overview
DialogManager shows the modal dialogs of Now.js. alert, confirm and prompt return a Promise of the user's answer;
custom builds a dialog from your own options and returns the element so you can drive it. Dialogs stack, and the
keyboard always acts on the topmost one.
Use it when:
- You want to inform, ask for confirmation, or ask for a value instead of
window.alert/confirm/prompt - You need a small dialog with your own buttons
For windows with a lot of content, forms, or data binding, use Modal.
Features:
- Promise-based API (
await DialogManager.confirm(...)) - Opens with focus on the default button, so Enter answers it · Escape closes · Tab stays inside the dialog
- Drag the dialog by its header
- Backdrop through BackdropManager, page scrolling locked while open
- Messages and button labels go through
Now.translate(except incustom)
Access and Initialization
DialogManager ships in the core bundle (now.core.min.js). DialogManager (global) and Now.getManager('dialog') are
the same object. Now.init() calls DialogManager.init() for you with the dialog key of its config, so you never call
init() yourself.
await Now.init({
dialog: {
closeOnBackdrop: false, // clicking outside does not close
draggable: false
}
});Configuration
| Key | Default | Effect |
|---|---|---|
duration |
200 |
Time (ms) from starting to close until the dialog is removed from the DOM; keep it equal to the CSS transition |
draggable |
true |
The dialog can be dragged by .dialog-header; turn it off per dialog with options.draggable: false |
closeOnEscape |
true |
Escape closes the topmost dialog (needs keyboard) |
closeOnBackdrop |
true |
Clicking the backdrop closes the dialog |
preventScroll |
true |
Sets body.style.overflow = 'hidden' while any dialog is open and restores it when the last one closes |
baseZIndex |
1100 |
z-index of the first dialog, used when the CSS variable --z-index-alert is absent |
focusTrap |
true |
Tab keeps focus cycling inside the dialog |
keyboard |
true |
Binds the document-level key handler during init() (used for Escape) |
templates |
{alert, confirm, prompt} |
HTML of each layout, see Template |
API Reference
DialogManager.alert(message, title?, options?)
Shows a message with a single OK button.
| Parameter | Type | Description |
|---|---|---|
message |
string | HTMLElement | The message (a string is HTML), passed through Now.translate |
title |
string | Title, defaults to 'Alert', passed through Now.translate |
options |
Object | See shared options |
Returns: Promise<true> — resolves once the dialog closes, however it is closed
await DialogManager.alert('Saved', 'Success');DialogManager.confirm(message, title?, options?)
Asks for confirmation with Cancel and Confirm buttons (Confirm is the default button).
| Parameter | Type | Description |
|---|---|---|
message |
string | HTMLElement | The question, passed through Now.translate |
title |
string | Title, defaults to 'Confirm' |
options |
Object | See shared options |
Returns: Promise<boolean> — true on Confirm; false on Cancel, Escape, the × button or the backdrop
if (await DialogManager.confirm('Delete this item?', 'Confirm delete')) {
await deleteItem();
}DialogManager.prompt(message, defaultValue?, title?, options?)
Asks for a value through an <input type="text" class="dialog-input">, with Cancel and OK buttons.
| Parameter | Type | Description |
|---|---|---|
message |
string | HTMLElement | Text above the input, passed through Now.translate |
defaultValue |
string | Initial value (fully selected, so typing replaces it), defaults to '' |
title |
string | Title, defaults to 'Prompt' |
options |
Object | See shared options |
Returns: Promise<string|null> — the input value on OK or Enter ('' when empty); null on Cancel, Escape, the × button or the backdrop
const name = await DialogManager.prompt('Folder name', 'New folder', 'Create folder');
if (name !== null) {
await createFolder(name);
}There is no placeholder or input-type option; use custom with your own template if you need one.
Shared options of alert, confirm, prompt
options is merged over what the method builds, so it can replace any key of custom.
The ones that are safe to pass are:
| Key | Type | Effect |
|---|---|---|
customClass |
string | Extra class on .dialog, e.g. 'alert' centers the footer buttons |
draggable |
boolean | false = this dialog cannot be dragged |
buttons replaces the buttons that give the answer (see Common Pitfalls).
Change button labels through the translations of the keys OK, Cancel and Confirm.
DialogManager.custom(options)
Builds and shows a dialog from your own options, returning the element instead of a Promise.
| Key | Type | Default | Description |
|---|---|---|---|
template |
string | 'alert' |
Template name (alert, confirm, prompt, or one you added) |
title |
string | — | Written into .dialog-title with textContent |
message |
string | HTMLElement | — | Written into .dialog-message when present, otherwise .dialog-body |
buttons |
Object | {} |
Footer buttons, see Buttons |
defaultValue |
string | — | Initial value of .dialog-input (the prompt template) |
customClass |
string | — | Extra class on .dialog |
draggable |
boolean | — | false = cannot be dragged |
onShow |
Function | — | Bound to the dialog:shown event |
onClose |
Function | — | Bound to the dialog:closed event |
Returns: HTMLElement — the shown dialog
custom does not translate anything; pass Now.translate(...) yourself. This example wraps it in a Promise that
resolves however the dialog is closed:
function askLeave() {
return new Promise(resolve => {
let answer = 'stay';
DialogManager.custom({
title: Now.translate('Unsaved changes'),
message: Now.translate('Leave this page?'),
buttons: {
leave: {text: Now.translate('Leave'), class: 'btn-primary', callback: () => { answer = 'leave'; }},
stay: {text: Now.translate('Stay'), default: true, callback: () => { answer = 'stay'; }}
},
// dialog:closed fires on every way of closing, including Escape, the × button and the backdrop
onClose: () => resolve(answer)
});
});
}Buttons
buttons is an object of {key: config}; buttons appear in key order, each as <button type="button" class="btn ...">.
| Key | Type | Description |
|---|---|---|
text |
string | Button label (not translated) |
class |
string | Class appended after btn, defaults to text, e.g. btn-primary |
callback |
Function | callback(dialog) runs on click, then the dialog closes itself. A button without a callback does nothing and does not close |
attrs |
Object | Extra HTML attributes, e.g. {'data-id': 5} |
default |
boolean | Makes it the default button (focused on open, pressed by Enter in the prompt input) |
DialogManager.show(dialog)
Shows a dialog made with createDialog (an element, id, or selector): positions it, adds the backdrop and sets focus.
A dialog already showing is not shown twice.
Returns: HTMLElement|null — null when the dialog cannot be found
DialogManager.createDialog(options)
Builds a dialog from the same options as custom (except onShow / onClose) without
showing it.
Returns: HTMLElement
const dialog = DialogManager.createDialog({title: 'Details', message: detailEl});
dialog.addEventListener('dialog:closed', cleanup);
DialogManager.show(dialog);DialogManager.close(dialog)
Closes one dialog (an element, id, or selector): removes the show class, adds hiding, hides the backdrop, removes the
dialog from the DOM after duration ms, then gives focus back to whatever had it before the dialog opened and dispatches
dialog:closed.
Closing with close() does not run any button callback.
DialogManager.closeAll()
Closes every open dialog.
DialogManager.bringToFront(dialog)
Raises a dialog (an element, id, or selector) to the top, both in z-index and in the order the keyboard acts on.
Other methods
| Method | Purpose |
|---|---|
init(options) |
One-time setup — Now.init() already calls it; a second call does nothing |
setupButtons(dialog, buttons) |
Rebuilds the buttons in .dialog-footer |
setupDraggable(dialog) |
Makes the dialog draggable by .dialog-header |
setupFocusTrap(dialog) |
Keeps Tab inside the dialog |
setupKeyboardEvents() |
Binds Escape at document level |
translate(key, params) |
Translates through I18nManager, returns the key when i18n is absent |
cleanupDialog(dialog) |
Removes a dialog's listeners and attributes (close() does not call it) |
handleError(message, error, type) |
Reports through ErrorManager, then closes every dialog |
destroy() |
Closes every dialog, clears the state and unbinds Escape; init() sets it up again |
Default Button and Keyboard
A dialog opens with focus already where the user acts first, so Enter answers it without reaching for the mouse:
| Dialog | Focus on open | Enter |
|---|---|---|
alert |
OK button | resolves through OK |
confirm |
Confirm button | resolves true |
prompt |
the input, with defaultValue selected |
resolves with the input value |
custom |
the default button (see below) | presses the default button |
The default button is the first footer button with default: true; without one it is the first .btn-primary, then
the first footer button. Only a dialog with no footer buttons at all leaves focus on the header close button.
| Key | Effect |
|---|---|
| Enter | Presses the focused button (the default button on open) · in the prompt input, presses the default button |
| Escape | Closes the topmost dialog wherever focus is (closeOnEscape) |
| Tab / Shift+Tab | Cycles focus inside the dialog (focusTrap) |
DialogManager.custom({
title: 'Unsaved changes',
message: 'Leave this page?',
buttons: {
leave: {text: 'Leave', class: 'btn-primary', callback: () => leave()},
stay: {text: 'Stay', default: true, callback: () => {}} // Enter = Stay
}
});Stacking and Backdrop
- The first dialog takes its z-index from the CSS variable
--z-index-alert(default 1100), orbaseZIndexwhen the
variable is absent; each stacked dialog adds 2 - Each dialog has its own backdrop (one below the dialog's z-index) from BackdropManager; clicking it closes only that
dialog whencloseOnBackdropis on - Escape closes one layer at a time, from the top
- Dragging is clamped to the window, and buttons in the header do not start a drag
Template
A template is the inner HTML of <div class="dialog" role="dialog" aria-modal="true">. DialogManager finds its parts by
class:
| Class | Used for |
|---|---|
.dialog-header |
Header bar and drag handle |
.dialog-title |
Receives title, and becomes aria-labelledby |
.dialog-close |
Close button (every element with this class) |
.dialog-body |
Receives message when there is no .dialog-message |
.dialog-message |
Receives message (the prompt template) |
.dialog-input |
The prompt field — gets defaultValue and focus, and Enter presses the default button |
.dialog-footer |
Holds the buttons (no footer = no buttons) |
An unknown template name falls back to alert. Templates given to Now.init() are added to the built-in ones:
await Now.init({
dialog: {
templates: {
notice: `
<div class="dialog-header"><h2 class="dialog-title"></h2></div>
<div class="dialog-body"></div>
<div class="dialog-footer"></div>
`
}
}
});
DialogManager.custom({template: 'notice', title: 'Notice', message: '...', buttons: {...}});Events
DOM CustomEvents dispatched on the dialog element. They do not bubble, so listen on the dialog element itself (from
custom / createDialog, or through onShow / onClose) and read the values from event.detail.
| Event | When Triggered | event.detail |
|---|---|---|
dialog:shown |
The frame after show(), once focus is set |
{dialog} |
dialog:closed |
After the dialog is removed from the DOM (every way of closing) | {dialog} |
const dialog = DialogManager.custom({title: 'T', message: 'M'});
dialog.addEventListener('dialog:closed', (e) => {
console.log('closed', e.detail.dialog.id);
});Where the Framework Uses It
These parts call DialogManager.confirm() for you:
| Source | When |
|---|---|
| FormManager | <form data-confirm="message"> asks before submitting |
| EventSystemManager | The requestApi action asks before sending when the element has data-confirm |
| ResponseHandler | The server action {type: 'confirm', message, onConfirm} |
| TableManager | Deleting a row (turn off with data-confirm-delete="false") and row actions that set confirm |
| FileElementFactory | Deleting an already uploaded file |
CSS Styling
Styles live in Now/css/dialog.css (the close button is in components.css).
| Selector | Default |
|---|---|
.dialog |
position: fixed · width: clamp(320px, 500px, 85vw) · starts at opacity: 0 |
.dialog.show / .dialog.hiding |
opacity: 1 / opacity: 0 (transition follows --transition-duration) |
.dialog.dragging |
Transition off while dragging |
.dialog-header, .dialog-body, .dialog-footer |
Flex layout · the body scrolls when long (max-height: calc(90vh - 12rem)) |
.alert .dialog-footer |
Centered buttons (apply with customClass: 'alert') |
CSS variables you can override (variables.css, dark theme in theme-dark.css):
:root {
--dialog-bg: var(--color-background);
--dialog-text: var(--color-text);
--dialog-border: rgba(0, 0, 0, 0.1);
--dialog-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
--z-index-alert: 1100;
}If you change the transition time, set duration to match, or the dialog is removed before it finishes fading.
Common Pitfalls
⚠️ 1. Await the Promise
// ❌ Deletes at once without waiting for the answer
DialogManager.confirm('Delete?');
deleteItem();
// ✅
if (await DialogManager.confirm('Delete?')) {
deleteItem();
}⚠️ 2. prompt returns null on Cancel and '' on OK with nothing typed
const value = await DialogManager.prompt('Name');
if (value === null) return; // cancelled
if (value === '') { ... } // OK without typing⚠️ 3. Build messages with user data as elements
A string message is HTML. What can run code is removed from it (DOMPurify when the page has it, otherwise <script>,
<iframe> and similar elements, on* attributes and script URLs), but markup in the data is still shown as markup. To show
text as text, pass an element filled with textContent.
// ❌ A name containing <b> or <a> changes what the dialog shows
await DialogManager.confirm(`Delete ${user.name}?`);
// ✅
const msg = document.createElement('p');
msg.textContent = `Delete ${user.name}?`;
await DialogManager.confirm(msg);⚠️ 4. Passing buttons to alert/confirm/prompt loses the answer
Your buttons replace the ones that give the answer, so the Promise settles with the "closed" value (false, null) whichever
button is pressed. For other buttons or labels with their own answers, use custom.
⚠️ 5. custom does not translate
alert/confirm/prompt go through Now.translate, but custom, createDialog and button text do not; call
Now.translate() yourself.
Related Documentation
- BackdropManager - The dialog backdrop
- Modal - Windows with a lot of content or forms
- NotificationManager - Messages that need no answer
- I18nManager - Translations of messages and button labels