Pages and tabs
Give part of your app pages to move between, and tabs that each keep their place, with a router.
Make a router
UiElement:router turns an element into a region of pages and returns
its Router. Declare each page with Router:route, giving it a builder
function that fills the page it is handed. Router:route returns a
Route, which you move to. A page you move away from is kept, hidden,
so going back to it finds its scroll position and widgets as you left
them.
local ui = datumhue.ui
local router, tabs
local app = ui.root:panel({
flex_direction = "row",
width = "100%",
height = "100%",
gap = 12,
})
app:list({
items = { "Songs", "Settings" },
selected = { 1 },
width = 160,
on_change = function(i)
router:show(tabs[i])
end,
})
router = app:panel({ flex_grow = 1 }):router()
local song = router:route(function(page, params)
page:label({ text = "Song " .. params.id, font_size = "1.5rem" })
page:button({
text = "Back",
on_click = function()
router:pop()
end,
})
end, { title = "Song" })
local songs = router:route(function(page)
page:update({ gap = 8, align_items = "start" })
for id = 1, 3 do
page:button({
text = "Song " .. id,
on_click = function()
router:push(song, { id = id })
end,
})
end
end, { title = "Songs" })
local settings = router:route(function(page)
page:checkbox({ label = "Shuffle" })
end, { title = "Settings" })
tabs = { songs, settings }
router:show(songs)
In this example: datumhue.ui datumhue.ui.root UiElement:panel UiElement:list Router:show UiElement:router Router:route UiElement:label UiElement:button UiElement:checkbox
- The list on the left is the tab bar. Picking a row calls
Router:show, which makes that tab's route the root of the router. - A song button pushes the
songroute with{ id = id }, and its builder reads them asparams. Back pops it again. - The page a builder is handed is a
UiElement, soUiElement:updatelays it out like any panel.
Move between pages
| Call | Moves |
|---|---|
Router:push | to a new page on top |
Router:pop | back one page |
Router:replace | to a new page in place of the top one |
Router:to_root | back to the first page |
Router:go | back to a route already open, or on to it |
Router:show | to another root: another tab |
Pass small identifiers as params, never the data itself: a builder
fetches what it shows from your own store. Router.depth,
Router.active, Router.params and Router.can_go_back tell you where
the router is.
Tabs
Tabs are several roots switched with Router:show. Each keeps its own
stack of pages, so a tab you come back to shows the page you left it on:
open Song 2, switch to Settings and back, and Song 2 is still open.
Routers nest, too: a page can make a router of its own. A router lasts
as long as the element it was made on; once that is removed, its calls
raise and its state reads empty.
Start and leave a page
A route's options can carry on_enter, called once its page is built,
and on_leave, called before the page is discarded: popped, replaced,
or left behind by Router:to_root or Router:go. A page with another
pushed over it, or in a tab you switch away from, stays and hears
nothing. Start work for the page in on_enter. Return false from
on_leave to keep the page, for example while it holds unsaved edits:
local edit = router:route(build_editor, {
title = "Edit",
on_leave = function()
return not unsaved
end,
})
Moving to a page moves keyboard focus into it, and a screen reader
announces its route's title. Going back puts focus where it was.
Go back with Escape and B
Make the router with pop_on_cancel = true, and Escape, gamepad B and a
browser's Back go back a page, as Router:pop does; on_leave still has
its say. At the first page the request goes on past the router:
Handle Back follows it.
See also
- Handle Back: what Escape, B and Back do in your app.
- Keyboard and gamepad: how focus moves within a page.
RouterandRoute: every call and property.
Reference used on this page: datumhue.ui datumhue.ui.root UiElement:panel UiElement:list Router:show UiElement:router Router:route UiElement:label UiElement:button UiElement:checkbox Router:push Router:pop Router:replace Router:to_root Router:go UiElement:update Router.depth Router.active Router.params Router.can_go_back