Skip to main content

Dialogs

Hosanna dialogs are ordinary Hosanna views hosted by the application dialog manager. Present one from a view with:

this.present(dialog, { isDialog: true });

Dialogs are placed on a separate navigation stack, focus is locked to that stack, and the previous application focus is restored when the stack empties.

Use the built-in dialog

Dialog provides a title, message, styled button list, and dismissal callback:

const dialog = Dialog({
id: 'deleteConfirmation',
title: 'Delete download?',
message: 'You can download it again later.',
buttonTexts: ['Delete', 'Cancel'],
buttonCallbacks: [
() => this.deleteDownload(),
() => {},
],
onDismissed: () => {
this.setFocusedSubview('downloadsButton');
},
});

this.present(dialog, { isDialog: true });

Each button callback runs first; the built-in dialog then calls dismiss(). onDismissed runs when the dialog has been removed from its aggregate view, regardless of which button dismissed it.

Other built-in appearance fields include buttonStyles, buttonGap, contentWidth, paddingX, paddingY, titleStyleKey, messageStyleKey, backgroundColor, contentBackgroundColor, and contentBackgroundUri.

Build a typed custom dialog

For a custom result, make the dialog view implement IDialogView<T>, store the selected result, and call its callback from onDidRemoveFromAggregateView():

export interface ConfirmDialogState extends ViewState {
onDismissed?: DialogDismissedWithValueCallback<boolean>;
}

@view('ConfirmDialog')
export class ConfirmDialogView
extends BaseView<ConfirmDialogState>
implements IDialogView<boolean> {

@state
onDismissed: DialogDismissedWithValueCallback<boolean> = () => {};

private accepted = false;

protected override getViews() {
return [
VGroup([
Label({ text: 'Continue?' }),
Button({ id: 'cancel', text: 'Cancel' })
.isInitialFocus()
.onClick(() => {
this.accepted = false;
this.dismiss();
}),
Button({ id: 'continue', text: 'Continue' })
.onClick(() => {
this.accepted = true;
this.dismiss();
}),
]),
];
}

override onDidRemoveFromAggregateView(owner: IAggregateView): void {
this.onDismissed(this.accepted);
}
}

The callbacks above are created inside the dialog class, so this.dismiss() correctly refers to the dialog view. A free-standing DSL factory has no dialog instance to use as this.

Dialog guidelines

  • Give the first intended action initial focus and test directional focus inside the modal.
  • Put outcome data in a typed onDismissed callback rather than reaching into the dialog after removal.
  • Restore focus in the presenting view when a specific control should regain focus.
  • Use the keyboard dialog utility for the framework's keyboard flow; do not recreate its native/platform behavior as a generic dialog.
Talk to us