Navigation

Up until now, we’ve been building static layouts within our window. This isn’t particularly useful however for most apps, as apps routinely have a lot of different views which are navigated between via user interactions.

For this guide, we’ll learn how to build layouts that can be navigated between in various styles.

Layout Controllers

You may have noticed that the root element of every Prism layout is defined like so:

<layout>
</layout>

The root element of each layout file represents a Layout Controller, which is an object accessible from JavaScript that helps in managing the navigation between layouts, as well as providing the ability to observe layout-specific lifecycle events.

You only usually need to interact with Layout Controllers when dealing with navigation events, as these objects help you present other layouts in specialised ways, for example as a popover, a modal sheet, a tab, etc.

As mentioned each Layout Controller can be accessed in JavaScript:

const myLayoutController = window.layout

The next few sections discuss each Layout Controller that’s available in Prism, how they work, and how to interact with them in JavaScript.

Plain Layout Controllers

Plain Layout Controllers are the most simple of all of the Layout Controllers. They only support the most basic navigation APIs that are generic to all Layout Controllers.

The <layout> element constructs a Plain Layout Controller, like so:

<layout>
</layout>

There are only two navigation APIs available, both of which are generic across any Layout Controller:

// Presents another layout file modally on top of this one
window.layout.presentLayout('myModalLayout', callback)
// Dismisses a modally presented layout
window.layout.dismiss(callback)

In addition, the following elements are supported on a layout:

  • “title”: The title for the layout. This may be used as the Window title, or as the name of a tab associated with the layout.
  • “icon”: The name of an icon for the layout, used when displayed as a tab
  • “genericBackTitle”: When “true”, ‘Back’ is used as the title for the button when navigating back to this layout as part of a stacked layout instead of the title

Stacked Layout Controllers

Stacked Layout Controllers provide the means to display a navigation stack within an application, with the ability to push child layouts onto the stack, and navigate to the previous layout all the way to the root layout.

These layout controllers always require a default child layout to initially display. This child layout will always be accessible at the top of the navigation stack:

<stacked-layout>
  <layout>
    <label>I'm the root layout!</label>
  </layout>
</stacked-layout>

We can also introduce a new element that allows us to modularise our layouts in this situation (full information on this is covered in a future guide):

<!-- index.xml -->
<stacked-layout>
  <include src="root-layout"/>
</stacked-layout>

<!-- root-layout.xml -->
<layout>
  <label>I'm the root layout!</label>
</layout>

There are a few navigation APIs available for interacting with the navigation stack:

// Toggle the visibility of navigation controls
window.layout.navigationControlsHidden = true|false
// Toggle the visibility of navigation controls with animation
window.layout.transitionNavigationControlVisibility(true|false, callback)
// Jumps all the way back to the root layout ignoring any layouts in
// between the root layout and current layout.
window.layout.popToRoot(callback)
// Pushes a new layout onto the stack, immediately presenting it
window.layout.push('other-layout', callback)
// Pops a layout off of the stack, going back to the previous layout
window.layout.pop(callback)
// Retrieves the child layout controller at the specified index of the navigation stack
window.layout.children[0]

// ---------------------------------
// More ways of pushing and popping:
// ---------------------------------

// Pushes many layouts and immediately presents the last one in the list
window.layout.push(['other-layout', 'other-layout-2', 'other-layout-3'], callback)
// Pops two layouts off of the stack, going back to the n-2 layout on the stack
window.layout.pop(2, callback) 

Tabbed Layout Controllers

Tabbed Layout Controllers provide a standard tabbed user interface appropriate to each platform, for example on iOS this would usually be a tab bar at the bottom, whereas on macOS or Windows 10 this would usually be a sidebar pinned to the left of the window.

These layout controllers always require a default series of child layouts to display:

<tabbed-layout>
  <include src="tab-1"/>
  <include src="tab-2"/>
  <include src="tab-3"/>
  <include src="tab-4"/>
</tabbed-layout>

There are a few navigation APIs available for interacting with the tabs:

// Toggle the visibility of navigation controls
window.layout.navigationControlsHidden = true|false
// Toggle the visibility of navigation controls with animation
window.layout.transitionNavigationControlVisibility(true|false, callback)
// Sets the selected tab to the specified zero-based index (4 tabs = indices 0, 1, 2, and 3)
window.layout.selectedTab = 0
// Sets the selected tab to the specified index with animation
window.layout.transitionSelectedTab(0)
// Sets a badge for the specified index, may not be visible on all platforms
window.layout.setBadgeForTab(0, '99+')
// Retrieves the child layout controller at the specified tab index
window.layout.children[0]

Note that a Tabbed Layout Controller cannot be nested.

Detail Layout Controllers

Detail Layout Controllers provide a common interface for master-detail layouts. On small-screen devices, this will act like a stacked layout controller, and on large-screen devices, both child layouts can be visible side by side.

These layout controllers always require two child layouts to display:

<detail-layout>
  <include src="master"/>
  <include src="detail"/>
</detail-layout>

There are two navigation APIs available:

// Toggle the visibility of the master layout
window.layout.masterLayoutHidden = true|false
// Toggle the visibility of the master layout with animation
window.layout.transitionMasterLayoutVisibility(true|false, callback)
// Retrieves the master layout controller
window.layout.master
// Retrieves the detail layout controller
window.layout.detail

Note that a Detail Layout Controller cannot be nested.

Combining Layout Controllers

Just like with Panels and Views, Layout Controllers can be nested fairly easily where permitted:

<detail-layout>
  <stacked-layout>
    <button id="button">Show details</button>
  </stacked-layout>
  <layout>
    <label id="label"></label>
  </layout>
</detail-layout>
window.button.on('click', () => {
  window.layout.transitionMasterLayoutVisibility(false)
  window.label.value = 'Some detailed content'
})

Notes on Views

You’ll notice that even though we have multiple Layout Controllers, our views with identifiers are still accessible via the window object. This is because Prism automatically swaps out which views are accessible on the window object as transitions are made between layouts.

For example:

<!-- index.xml -->
<stacked-layout>
  <layout>
    <label id="myRootLabel" />
  </layout>
</stacked-layout>

<!-- child.xml -->
<layout>
  <label id="myChildLabel" />
</layout>
// Currently accessible, as we're presenting the root layout
window.myRootLabel.value = `I'm the root layout!`

window.layout.push('child', () => {
  // myRootLabel is no longer accessible as it's not currently being displayed
  window.myRootLabel === undefined // returns true
  // myChildLabel is now accessible
  window.myChildLabel.value = `I'm the child layout!`
})

Summing up

In this guide we’ve learnt how to declare various layout controllers, and how to access them from JavaScript.

In the next guide, we’ll run through some of the more advanced views and panels you can use to display dynamic content, like lists, responsive grids, and scrollable content.