Skip to content

Router

Router is JOPS’s hash-based router. It extends View, is declared in a layout as a <Router jops> tag, and is a singleton: one Router per page. Its routes are views; the hash (#home, #detail?id=42) selects the one in front.

import { Router } from "jops-core";

Learn more: Routing.

src/layout/main.html
<Router jops animation="slide">
<Home jops path="/home" src="/src/Home.js"></Home>
<Detail jops path="/detail" src="/src/Detail.js"></Detail>
<View jops path="/about" layout="/src/layout/about.html"></View>
</Router>

Each child tag is a route: a declared view with a path. The tag name is free (<Home>, <view>); jops marks it as a view, src names its class module, layout its layout. The legacy type="jops" and type="jops-event-bind" syntax is still supported.

  • The first route is the default. It is shown on startup when the URL has no known route hash, and whenever the hash becomes empty (href="#", a link to the page itself).
  • Every route gets onLayout() at startup; only the route that is shown gets onResume().
  • A route’s own layout may declare views, like any other layout.
Attribute Values Description
animation "none" (default), "slide", "fade" Transition for stacked navigation, see Animation
screens boolean attribute Gives the Router its own container with full-size screens, see Screens mode
id, class any In screens mode, copied to the Router’s container
Pattern Mechanism Instance Android analogy
href="#path" flat route, hash change existing route view, reused tab navigation
navigate(path, ClassDef?) stack always a new instance startActivity()
replace(path, ClassDef?) resets the stack existing route or new instance startActivity(FLAG_CLEAR_TOP)
<a href="#detail?id=42">Open</a>
Router.singleton(): Router

Returns the page’s Router instance once it has been rendered. Before that (and after the app has ended) there is no instance, so guard calls made outside views:

const router = Router.singleton();
if (router?.canGoBack()) router.back();
navigate(path: string, ClassDef?: typeof View): Promise<void>

Creates a new view instance for path, pushes it onto the navigation stack, animates forward and updates the address bar to #path?params.

Parameters

  • path: the route path with optional query params, for example "/detail?id=42".
  • ClassDef (optional): the view class to create. Required when path is not one of the declared routes.

The current view gets onPause(); the new instance gets onLayout() → onResume().

Router.singleton().navigate("/detail?id=42", DetailView);
replace(path: string, ClassDef?: typeof View): Promise<void>

Clears the navigation stack and shows path: the existing route view when path is a declared route, otherwise a new instance of ClassDef. Replaces the current history entry and keeps its existing history.state.

Router.singleton().replace("/login");
back(): void

Navigates back: pops the navigation stack with an animation when it is not empty, otherwise calls history.back(). The view being left gets onBackPressed().

canGoBack(): boolean

Returns true when there is an earlier JOPS history entry to go back to, like checking the back stack in Android.

getParams(): Record<string, string>

Returns the current URL query params as a plain object. Works for flat routes (href="#path?k=v") and stacked views (navigate("/path?k=v")). Read them in a route’s onResume() (it runs again when the same route is shown with new params); a stacked view can also read them in onLayout().

onResume() {
const { id, mode } = Router.singleton().getParams();
}
destroy(): void

Removes the Router’s history listeners, destroys its routes and stacked views and clears the singleton. Called by unmount(); safe to call more than once.

Not supported on a Router: its subviews are its routes. It logs a warning and does nothing.

Property Type Description
animation string The value of the tag’s animation attribute ("none" when absent)
screens boolean true in screens mode
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 (#details?id=1 → #details?id=2) — 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()

See View lifecycle for each hook.

// flat route with params
// <a href="#form?tab=profile">Profile</a>
// stacked navigation with params
Router.singleton().navigate("/detail?id=42&mode=edit", DetailView);
// read them in the view's onResume()
const { id, mode } = Router.singleton().getParams();

Values are strings. A route shown again with different params gets onResume() again, so read params there rather than in onLayout().

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

Animation only runs on navigate() and on back() while the stack is not empty. Switching between flat routes is always instant, and so is a transition involving a view with several top-level elements (except in screens mode). The Router’s container needs position: relative for the animations.

<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>

With the screens attribute, the Router gets a container of its own and every route and stacked view becomes a full-size screen in it, as in a native app:

  • 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, not in the tag’s parent element.
  • Each route and each view opened with navigate() or replace() is wrapped in <div class="jops-screen">, which fills the container (position: absolute; inset: 0), scrolls on its own (overflow: auto) and has an opaque background.
  • A new view covers the current one, which stays alive underneath. Afterwards the covered screen is visibility: hidden and inert, so it keeps its scroll position and is shown 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.
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;
}

The properties the Router sets inline (position, inset, overflow and background on screens; position and overflow on the container) take precedence over your stylesheet; set the background with --jops-screen-bg.

Without screens (the default), routes are placed in the tag’s parent element and scroll with the page.