Skip to main content

Dialogs

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

this.showDialog(dialog);

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.showDialog(dialog);

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.

Choose the presentation transition​

showDialog(dialog, options) accepts INavigationPresentationOptions for animation and a named transition:

this.showDialog(dialog, {
animated: true,
transitionStyleKey: 'controls.NavController.transition.slide',
});
OptionDefaultPurpose
animatedtrueRuns the selected transition visually. Set it to false to preserve lifecycle and focus behavior without visual animation.
transitionStyleKeycontrols.NavController.transition.defaultUses one named controls.NavController.transition.* configuration for this presentation.

When transitionStyleKey is omitted, the dialog stack resolves controls.NavController.transition.default, then Hosanna's internal fade fallback. Configured Fade and Slide transitions handle the empty-to-first dialog boundary as well as transitions between dialogs.

Use the options object rather than the old boolean second argument:

this.showDialog(dialog, { animated: false });

present() exposes the same fields when isDialog is set:

this.present(dialog, {
isDialog: true,
animated: true,
transitionStyleKey: 'controls.NavController.transition.slide',
});

See Aggregate View Transitions for named transition configuration and the available Simple, Fade, and Slide options.

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