Skip to main content

Navigating Between Screens

Hosanna UI provides robust navigation primitives for building multi-screen applications, including stack-based navigation (NavController) and tab-based navigation (TabController). This guide explains how to use these controllers to manage screen transitions and navigation patterns in your app.

NavController manages a stack of views, allowing you to push, pop, reset, and replace screens. Only one view is visible at a time (or two during transitions), making it ideal for workflows like wizards, detail pages, or modal flows.

Key Features

  • Push new screens onto the stack
  • Pop screens off the stack
  • Reset or replace the current screen
  • Listen for when the last view is removed

Present and Dismiss from any View

Every BaseView has convenience helpers to present or dismiss screens:

  /**
* Presents a new screen in the navigation stack or as a dialog.
*
* @param screen - The screen to be presented, represented by a `ViewStruct<ViewState>`.
* @param options - Optional presentation options.
* @param options.isDialog - If true, the screen will be presented as a dialog.
* @param options.animated - If true, the screen transition will be animated.
*/
present(screen: ViewStruct<ViewState>, options?: IScreenPresentationOptions) {
if (options?.isDialog) {
this.showDialog(screen);
} else {
//hs: disable-next-line
this.getNavController()?.push(screen, options?.animated);
}
}

dismiss(options?: IScreenDismissOptions) {
this.getNavController()?.pop(options?.animated);
}

Usage:

// Present a screen onto the nearest NavController
this.present(DetailsView({ id: 'details' }), { animated: true });

// Present as a dialog overlay
this.present(MyDialog({ id: 'dialog1' }), { isDialog: true, animated: true });

// Dismiss current screen (or active dialog when handled by the controller)
this.dismiss({ animated: true });

Example: Basic Usage

import { NavController } from 'hosanna-ui/views/aggregate-views/NavController';
import { NavigationChildView } from './NavigationChildView';

const navController = NavController({
id: 'mainNav',
initialView: NavigationChildView({ id: 'root' })
});

// Push a new view
navController.push(NavigationChildView({ id: 'details' }));

// Pop the current view
navController.pop();

// Replace the current view
navController.replace(NavigationChildView({ id: 'replacement' }));

// Reset the stack
navController.reset(0, NavigationChildView({ id: 'root' }));

// Or from a view instance
this.present(NavigationChildView({ id: 'details' }), { animated: true });

Example: Integrating in a View

protected getViews(): ViewStruct<ViewState>[] {
return [
Group([
Rectangle({ color: '#00ff00' }).size(1920, 1080),
VGroup([
NavController({ id: 'navController', initialView: NavigationChildView({ id: 'root' }) })
.canReceiveFocus(true)
.isInitialFocus()
])
])
];
}

TabController: Tabbed Navigation

TabController manages a set of tabs, each with its own independent navigation stack (backed by a NavController). This is useful for apps with multiple main sections, like dashboards or settings screens.

Key Features

  • Define multiple tabs, each with its own view
  • Switch between tabs, each maintaining its own navigation history
  • Add or remove tabs dynamically
  • Listen for tab selection and hiding events

Example: Basic Usage

import { TabController } from 'hosanna-ui/views/aggregate-views/TabController';

const tabs = [
{ id: 'home', title: 'Home', view: HomeView({ id: 'Home' }) },
{ id: 'settings', title: 'Settings', view: SettingsView({ id: 'Settings' }) }
];

const tabController = TabController({
id: 'mainTabs',
tabs,
selectedTab: 'home',
onTabSelected: (tabId) => console.info(`Tab selected: ${tabId}`),
onTabHidden: (tabId) => console.info(`Tab hidden: ${tabId}`)
});

Example: Integrating in a View

protected getViews(): ViewStruct<ViewState>[] {
return [
Group([
Rectangle({ color: '#00ff00' }).size(1920, 1080),
TabController({
id: 'tabController',
tabs: this.tabs,
selectedTab: this.selectedButtonId,
onTabSelected: (tabId) => { this.selectedButtonId = tabId; },
onTabHidden: (tabId) => { if (tabId === '') this.selectedButtonId = 'home'; }
})
])
];
}
  • Stack Navigation: Use NavController for flows where users move forward and backward through screens (e.g., detail pages, wizards).
  • Tabbed Navigation: Use TabController for top-level navigation between major sections, each with its own stack.
  • Combining Patterns: You can nest NavController inside tabs, or use a TabController as the root with each tab containing its own navigation stack.

Best Practices

  • Use NavController for modal or linear flows.
  • Use TabController for persistent top-level navigation.
  • Listen to navigation events to update UI or state as needed.
  • Keep navigation logic in controllers, not in individual views, for maintainability.

For more advanced navigation scenarios, see the Hosanna UI examples and API documentation.