Skip to content

Routing

The JOPS Router is a hash-based router: the part of the URL after # decides which screen is shown (/app/#users). There is one Router per app, declared in a layout, and you reach it from anywhere with Router.singleton().

src/layout/main.html
<header>
<a href="#home">Home</a>
<a href="#users">Users</a>
</header>
<Router jops animation="slide">
<Home jops path="/home" src="/src/Home.js"></Home>
<Users jops path="/users" src="/src/Users.js"></Users>
</Router>

Each view inside <Router> is a route, with its path. Link to it with href="#users": no leading slash in the hash, even though the path is /users.

A route can also be a plain view with a layout file: <View jops path="/about" layout="/src/layout/about.html"></View>. A route’s own layout may declare more views with jops tags; they are rendered with the route.

JOPS has two navigation paradigms, both familiar from Android:

Pattern Mechanism Instance Android analogy
href="#path" flat, hash change created once, reused tab navigation
navigate(path, Class?) stack always a new instance startActivity()
replace(path, Class?) resets the stack existing or new startActivity(FLAG_CLEAR_TOP)

The views declared inside <Router> are flat routes. Each is created once at startup, kept alive and shown or hidden, like tabs. Switching between them is a plain link:

<a href="#users">Users</a>
<a href="#details?id=42">Order 42</a>

Flat routes can be deep-linked, bookmarked and reloaded: the route in the URL hash is shown on page load.

const router = Router.singleton();
router.back(); // pops a stacked view with animation, otherwise history.back()
router.canGoBack(); // true if there is an earlier JOPS history entry to return to
// clear the stack and show a route in the current history entry
router.replace("/home");
// or show a view that is not a declared route
router.replace("/welcome", Welcome);

replace() uses the existing flat route instance when there is one, and otherwise creates a new view from the class you pass. It replaces the current history entry instead of adding one, which suits redirects such as “not signed in, go to /login”.

Both kinds of navigation can carry query parameters in the hash, and getParams() returns them as an object of strings:

// a static link with params
// <a href="#form?tab=profile">Profile</a>
// dynamic navigation with params
Router.singleton().navigate("/detail?id=42&mode=edit", DetailView);
// reading them
const { id, mode } = Router.singleton().getParams();

Read params in onResume() of a flat route: it runs again when the same route is shown with new params (#details?id=1 → #details?id=2). A stacked view is always a new instance, so it can read them in onLayout().

The Router calls onPause(), onResume() and onBackPressed() for you. This table shows exactly what runs on each kind of navigation:

Navigation Outgoing view Incoming view
Route link (href="#users"), Forward to a route onPause() onResume()
Back to the previous route onBackPressed(), onPause() onResume()
Same route with new params none onResume()
navigate() (stacked view) onPause() new instance: onLayout() → onResume()
Back from a stacked view onBackPressed(), then destroyed onResume()
Route link while stacked views are open stacked views destroyed (no onPause()) onResume()
  • Every route gets onLayout() at startup, but a route is resumed only when it is shown. At startup only the selected route gets onResume().
  • onPause() always runs before the incoming view’s onResume().
  • Closing logic for a stacked view (saving a draft, for example) belongs in onBackPressed(), the last call before it is destroyed.

The first route declared in <Router> is the default. It is shown on startup when the URL has no known route hash, and whenever the hash becomes empty later (a href="#" link, or a link to the page itself).

Set the transition with the animation attribute on <Router>:

Value Behaviour
"none" (default) Instant switch
"slide" Forward: the new view slides in from the right. Back: the leaving view slides out to the right.
"fade" Crossfade between the two views

Animations only run for stacked views: navigate() forward and back() while the stack is not empty. Switching between flat routes is always instant. A view whose layout has several top-level elements also switches instantly, except in screens mode.

By default, routes are placed in the Router tag’s parent element and scroll with the page. For app-like layouts (a desktop app, a hybrid app, a fixed toolbar or sidebar), add the screens attribute:

src/layout/main.html
<div class="shell">
<nav class="sidebar">…</nav>
<Router jops screens animation="slide" class="content">
<Home jops path="/home" src="/src/Home.js"></Home>
<Files jops path="/files" src="/src/Files.js"></Files>
</Router>
</div>

In screens mode:

  • The <Router> tag is replaced by <div class="jops-router"> (plus the tag’s own id and class), styled position: relative; overflow: hidden. Routes are placed inside it.
  • Each route, and each view opened with navigate() or replace(), is wrapped in a <div class="jops-screen"> that fills the container, scrolls on its own and has an opaque background.
  • A new view covers the current one, which stays alive underneath. After the transition the covered screen is hidden and inert (not painted, not focusable), so it keeps its scroll position and comes back unchanged on Back. Inactive flat routes are hidden the same way.
  • slide and fade move only the upper screen, and also work for views with several top-level elements.

The container needs a size from your CSS. Without a height it has none, and nothing is visible. In a flex column, let it fill the rest:

src/css/styles.css
.jops-router {
flex: 1;
min-height: 0;
--jops-screen-bg: var(--bg); /* screen background, default: Canvas */
}
.jops-screen {
padding: 0 24px 32px; /* padding inside every screen */
}

.jops-router and .jops-screen are hooks for your own CSS. The properties the Router sets inline (position, inset, overflow and background on screens; position and overflow on the container) take precedence over your stylesheet, so set the screen background with the --jops-screen-bg variable. Its default, Canvas, is the browser’s page colour in light and dark mode.