In Nimbus, the Route
is the link between Application State, the View, and Browser History.
As we saw in Routing, under non-routed circumstances, a user will perform some navigation by clicking, we detect the click Event, and then modify Application State. However, we are inserting a routing layer into that process. So when the user clicks, we change the URL hash, which in turn creates a Hash Change Event. We then cue off of the Hash Change Event, and modify Application State. This then creates a History entry, in which case the user can use the browser's back and forward buttons, the same Hash Change Event is generated again. Thus, the user can use History move throughout the application.
Note: Much of this is project specific. It has not yet been moved into libraries for general use.
First off, we are going to create a ExampleRoute.tsx
file in the application/main/routes
folder.
Import React
and Router
. We should also import Button
from Recoil. This will be changed later.
import * as React from 'react';
import Router from '@cubex/router';
import { Button } from '@cubex/recoil';
Next, we need to load in our project specific implementations.
import { Route } from '../../../base/implementations/Route';
import MenuItem from '../../../base/components/MenuItem';
import Navigation from '../../../implementations/states/Navigation';
import Example from '../../../views/Example/Example';
import AppState from '../AppState';
We should now create our ExampleRoute
class. It should extend from Route
and contain the following empty methods.
Our Router.init(router: Router)
method sets up the Router
itself. We can modify the Router
object directly and return it.
The other methods are related to the Views.
Route.title()
returns astring
to be displayed as the Title.Route.body()
returns aJSX.Element
to be displayed as the Body.Route.header()
returns aJSX.Element
to be displayed as the Header.Route.breadcrumb()
returns aJSX.Element
to be displayed as the Breadcrumb.Route.menu(active: boolean)
returns aJSX.Element
to be displayed as the Menu.
export default class ExampleRoute extends Route {
state: AppState;
constructor(appState: AppState) {
super();
this.state = appState
}
init(router: Router): Router {
}
title(): string {
}
body(): JSX.Element {
}
header(): JSX.Element {
}
breadcrumb(): JSX.Element {
}
menu(active: boolean): JSX.Element {
}
}
We will need to fill in each of the empty methods.
First we should implement the Router.init(router: Router)
method. Here we will use the Router.addFunctionGroup()
method, passing in our hash key, of "example"
. This means any time we navigate to #example
, we will execute this route.
Our first argument is an array with two methods. Notice that the first takes a string
parameter, and the second takes no parameter. This means that when our hash is #example
we will use the second, and when it is #example/[string]
we will use the first.
For our function that uses a parameter, this must always be a string
. We can then cast it to a number with parseInt()
. For our purposes, let's pass it into some state called ExampleState
.
For our other function, we will initialize ExampleState
without a parameter.
Our last argument is a single function. This will be called when the user exits the route entirely. So we call our clear
method.
init(router: Router): Router {
return router.addFunctionGroup('example', [
(productId: string) => {
this.state.states.exampleState.initFromHistory(router.changedRouteGroup, false, parseInt(productId));
},
() => {
this.state.states.exampleState.init();
}
], () => {
this.state.states.exampleState.clear();
});
}
For Route.title()
we will return "Example"
.
title(): string {
return 'Example';
}
For Route.body()
we will return some Component to be displayed in the Body. For our purposes, let's say we have some Component called Example
.
body() {
let { exampleState } = this.state.states;
return (
<Example key="example" exampleState={exampleState} />
);
}
For Route.header()
we will return a bit of formatted JSX.
header(): JSX.Element {
return (
<h1 key="example" className="dinblock">Example</h1>
);
}
For Route.breadcrumb()
we will return some formatted JSX. Notice that our buttons have an onClick
Event handler. We pass in a method Exampleroute.gotoExampleIndex
which we will define later. We also mark the first button as checked
if we are viewing an item.
breadcrumb(): JSX.Element {
let { exampleState } = this.state.states;
let exampleItemView = !!exampleState.productManager.item;
return (
<span key="example">
<Button simple advanced tabIndex={-1} onClick={this.gotoExampleIndex.bind(this, 0)} pointer="right" checked={!exampleItemView} size="small">Example</Button>
{exampleItemView ? <Button simple advanced tabIndex={-1} pointer="right" checked size="small">Example Item</Button> : null}
</span>
);
}
For our Route.menu()
we will return a MenuItem
Component. This has been specifically designed to work with the dashboard menu. Notice that the location
property matches our URL hash.
menu(active: boolean): JSX.Element {
let { example } = PermissionState;
let { isMobile } = MobileState;
return (
<MenuItem
label="Example"
location="example"
icon="archive"
checked={active}
hide={!example}
simple={!isMobile}
theme={isMobile ? 'error' : undefined}
/>
);
}
We now must define ExampleRoute.gotoExampleIndex(n: number)
. When our parameter is 0, we will use Navigation.goToRoute('example');
. This will cause the URL hash to change to #example
, and run the Route.
gotoExampleIndex(n: number) {
if (n === 0) {
Navigation.goToRoute('example');
}
}
}
Our ExampleRoute
file should be done. But we still need to add it to our application. Open up applications/main/MainRouter
.
First, let's import our ExampleRoute
.
import QBudRoute from './routes/QBudRoute';
Next, we need to add ExampleRoute
to the MainRouter
. Inside MainRouter.init
add the line:
this.addRoute('example', new ExampleRoute(appState));
Note that again our example
matches the URL hash.
That should be it! Try going to the dashboard and using the Menu to navigate to our ExampleRoute!