Router is a concept that decouples application's page-routing logic than view layer in order to make application more
There are 3 types of routers
Router already comes preinstalled in the Smartface Workspace. If you have accidently deleted it or want to install at someplace else, use this command
yarn add @smartface/router
NativeRouter
is the root routerStackRouter
is to create route stackRoute
is a definition of a pathBottomTabBarRouter
is to manage BottomTabBarController'simport {
NativeRouter: Router,
Router: RouterBase,
NativeStackRouter: StackRouter,
BottomTabBarRouter,
Route
} from "@smartface/router";
const router = Router.of({
path: "/",
to: "/pages/page1",
isRoot: true,
routes: [
Route.of({
path: "/pages/page1",
build: (router, route) => {
let Page2 = require("pages/page2");
return new Page2();
}
}),
Route.of({
path: "/pages/page2",
build: (router, route) => {
const { routeData, view } = route.getState();
let Page1 = require("pages/page1");
return new Page1(routeData, router);
}
})
]})
// push path
router.push("/pages/page1");
goBack method is functional only if it's used on a StackRouter. And if provided, related page of the url parameter must be in the same stack history. Otherwise goBack does nothing.
// Add essential require statements
const router = Router.of({
path: "/",
isRoot: true,
routes: [
StackRouter.of({
path: "/pages",
routes: [
Route.of({
path: "/pages/page1",
build: (router, route) => {
const Page = require("pages/page1");
return new Page({ label: 1 }, router, "/pages2/page2");
}
}),
Route.of({
path: "/pages/page2",
build: (router, route) => {
const Page = require("pages/page1");
return new Page({ label: 2 }, router, "/pages2/page3");
}
}),
Route.of({
path: "/pages/page3",
build: (router, route) => {
const Page = require("pages/page1");
return new Page({ label: 3 }, router, "/pages2/page4");
}
}),
Route.of({
path: "/pages/page4",
build: (router, route) => {
const Page = require("pages/page2");
return new Page({}, router, -2);
}
})
]
})
]
});
router.push("/pages/page1");
router.push("/pages/page2");
router.push("/pages/page3");
router.push("/pages/page4");
// page2.ts
import System from '@smartface/native/device/system';
import Application from '@smartface/native/application';
import AlertView from '@smartface/native/ui/alertview';
import { NativeStackRouter } from '@smartface/router';
import Page2Design from 'generated/page2';
export default class Page2 {
router: any;
constructor(data, router) {
super();
this._router = router;
if (this.router instanceof NativeStackRouter) {
this.router.setHeaderBarParams({visible: false});
}
}
}
// Other stuff
function btn_onPress() {
// Go back "/pages/page1" which is in history stack
this.router.goBacktoUrl("/pages/page1");
// Go to the first page in the stack
this.router.goBacktoHome();
// Go 3 steps back
this.router.goBackto(-3);
// Test if router can go 2 steps back
if (this.router.canGoBack(-2)) {
// do something
} else if (this.router.canGoBacktoUrl("/some/path/to/back")) {
// do something else
} else {
// do something else
}
}
Replace action provides rerendering for opened route.
import {
NativeRouter: Router,
Router: RouterBase,
NativeStackRouter: StackRouter,
BottomTabBarRouter,
Route
} from "@smartface/router";
const router = Router.of({
path: "/",
to: "/pages/page1",
isRoot: true,
routes: [
StackRouter.of({
path: "/bottom/stack2",
to: "/bottom/stack2/path1",
headerBarParams: () => ({ ios: { translucent: false } }),
routes: [
Route.of({
path: "/pages/page1",
build: (router, route) => {
let Page2 = require("pages/page2");
return new Page2();
}
}),
Route.of({
path: "/pages/page2",
build: (router, route) => {
const { routeData, view } = route.getState();
let Page1 = require("pages/page1");
return new Page1(routeData, router);
}
})
]
})
]
});
// Go to page1
router.push("/pages/page1");
export = StackRouter.of({
path: "/example/modal",
to: "/example/modal/page1",
routes: [
StackRouter.of({
path: "/example/modal/modalpages",
modal: true,
routes: [
Route.of({
path: "/example/modal/modalpages/page1",
build: (router, route) => {
let Page = require("pages/page1");
return new Page({ label: 1 }, router);
}
}),
Route.of({
path: "/example/modal/modalpages/page2",
build: (router, route) => {
let Page = require("pages/page2");
return new Page({ label: 2 }, router);
}
})
]
})
]
});
// To close a modal StackRouter, call dismiss method
router.dismiss();
// dismiss method may take a callback function as parameter which is called
// right after dismiss operation is completed
router.dismiss(() => router.push("/to/another/page"));
import {
NativeRouter: Router,
Router: RouterBase,
NativeStackRouter: StackRouter,
BottomTabBarRouter,
Route
} from "@smartface/router";
import Color from '@smartface/native/ui/color';
const router = Router.of({
path: "/",
to: "/pages/page1",
isRoot: true,
routes: [
BottomTabBarRouter.of({
path: "/bottom",
to: "/bottom/stack2/path1",
// UI propperties of the BottomTabBarController
tabbarParams: () => ({
ios: { translucent: false },
itemColor: {
normal: Color.RED,
selected: Color.YELLOW
},
backgroundColor: Color.BLUE
}),
// TabBarItem's of the BottomTabBarController
items: () => [{ title: "page1" }, { title: "page2" }, { title: "page3" }],
// tab routes
routes: [
// tab 1
StackRouter.of({
path: "/bottom/stack",
to: "/bottom/stack/path1",
headerBarParams: () => ({ ios: { translucent: false } }),
routes: [
Route.of({
path: "/bottom/stack/path1",
build: (router, route) =>
new Page1(route.getState().routeData, router, "/stack/path2")
}),
Route.of({
path: "/bottom/stack/path2",
build: (router, route) => {
const { routeData, view } = route.getState();
return new Page2(routeData, router, "/bottom/stack2/path1");
}
})
]
}),
// tab 2
StackRouter.of({
path: "/bottom/stack2",
to: "/bottom/stack2/path1",
headerBarParams: () => ({ ios: { translucent: false } }),
routes: [
Route.of({
path: "/bottom/stack2/path1",
build: (router, route) =>
new Page1(
route.getState().routeData,
router,
"/bottom/stack/path2"
)
}),
Route.of({
path: "/bottom/stack2/path2",
build: (router, route) => {
return new Page2(route.getState().routeData, router);
}
})
]
}),
// tab 3
Route.of({
path: "/bottom/page1",
build: (router, route) => {
console.log(`route ${route}`);
return new Page1(
route.getState().routeData,
router,
"/bottom/stack/path1"
);
}
})
]
})
]
});
// Go to page1
router.push("/pages/page1");
// router
const router = Router.of({
path: "/",
routes: [
StackRouter.of({
path: "/bottom/stack2",
to: "/bottom/stack2/path1",
headerBarParams: () => ({ ios: { translucent: false } }),
routes: [
Route.of({
path: "/bottom/stack2/path1",
build: (router, route) =>
new Page1(route.getState().routeData, router)
}),
Route.of({
path: "/bottom/stack2/path2",
build: (router, route) => {
return new Page2(route.getState().routeData, router);
}
})
]
})
]
});
router.push("/bottom/stack2/path1", { sort: "ASC" });
// page1.js
import Page2Design from 'generated/page2';
export default class Page2 {
sort: any;
router: any;
constructor(data, router) {
super();
this.sort = routeData.sort;
this.router = router;
}
}
// Other stuff
// When btnNext is pressed in Page1
function btnNext_onPress() {
const page = this;
page.router.push("/path/to/another", {
message: "Hello World!"
});
}
homeRoute property of StackRouter is the index of the first route in the stack.
// Other stuff
StackRouter.of({
path: "/bottom/stack2",
to: "/bottom/stack2/path1",
homeRoute: 0, // it means /bottom/stack2/path
headerBarParams: () => ({ ios: { translucent: false } }),
routes: [
Route.of({
path: "/bottom/stack2/path1",
build: (router, route) =>
new Page1(route.getState().routeData, router, "/bottom/stack/path2")
}),
Route.of({
path: "/bottom/stack2/path2",
build: (router, route) => {
return new Page2(route.getState().routeData, router);
}
})
]
});
// Other stuff
const route = Router.of({
StackRouter.of({
path: "/bottom/stack2",
to: "/bottom/stack2/path1",
homeRoute: 0, // it means /bottom/stack2/path
headerBarParams: () => { ios: { translucent: false } },
routes: [
Route.of({
path: "/bottom/stack2/path1",
build: (router, route) => {
const sort = route.getState().query.sort;
return new Page1(route.getState().routeData, router, sort, "/bottom/stack/path2")
}
}),
Route.of({
path: "/bottom/stack2/path2",
build: (router, route) => {
return new Page2(route.getState().routeData, router);
}
})
]
})
});
router.push('/bottom/stack2/path1?sort=ASC&groupby=user')
import {
Route,
Router: RouterBase,
NativeRouter: Router
} from "@smartface/router";
const deeplinkRouter = new RouterBase({
path: "/deeplink",
routes: [
Route.of({
path: "/deeplink/products/:id",
to: (router, route) => {
const state = route.getState();
return "/nav/tabs/discover/products" + state.match.params.id + '/' + state.rawQuery;
}
}),
Route.of({
path: "/deeplink/product/:id",
to: (router, route) => {
return "/nav/product/display/" + route.getState().match.params.id;
}
}),
Route.of({
path: "/deeplink/search",
to: "/nav/tabs/shop/searchResults"
})
]
});
const router = Router.of({
///
routes: [deeplinkRouter, ...]
///
});
// Handle push notification
Application.onReceivedNotification = function(e) {
// assume that receivedUrl is equal to "http://yourdomain.com/product/12345"
const receivedUrl = e.remote.url;
const domain = 'http://yourdomain.com';
// url is now equal to "/deeplink/product/12345"
const url = receivedUrl.replace(domain, '/deeplink');
deeplinkRouter.push(receivedUrl);
// or you can directly push the url using any other router
router.push(receivedUrl);
};
Routes have some life-cycle events :
routeDidEnter
is triggered when route is activated by exact match to the requested urlrouteDidExit
triggered when route is deactivated by exact match to an another routebuild
is a builder function to create view instance associated with specified router and route or notrouteShouldMatch
is triggered when route is matched as exact and then route will be blocked or not by regarding the return value of the method
Route.of({
path: "/bottom/page1",
routeDidEnter: (router, route) => {
// if view is singleton and visited before
const { view } = route.getState();
view?.onRouteEnter && view.onRouteEnter(router, route);
},
routeDidExit: (router, route) => {
const { view } = route.getState();
view.onRouteExit && view.onRouteExit(router, route);
},
routeShouldMatch: (route, nextState) => {
if (!nextState.routeData.applied) {
// blocks route changing
return false;
}
return false;
},
build: (router, route) => {
const { view } = route.getState();
// singleton view
return (
view ||
new Page1(route.getState().routeData, router, "/bottom/stack/path1")
);
}
});
///
const unload = router.addRouteBlocker((path, routeData, action, ok) => {
alert({
message: "Would you like to answer?",
title: "Question",
buttons: [
{
text: "Yes",
type: AlertView.Android.ButtonType.POSITIVE,
onClick: () => {
ok(true);
}
},
{
text: "No",
type: AlertView.Android.ButtonType.NEGATIVE,
onClick: () => {
ok(false);
}
}
]
});
});
unload();
Following cases cannot be handled by the blocker:
const unlisten = router.listen((location, action) => {
console.log(`new route action : ${action} path : ${location.url}`);
});
unlisten();
yarn
commandyarn run build
or invoke it in watch mode by yarn run watch
scripts/node_modules/@smartface/router
.
Example command(don't forget to delete the installed lib directory): ln -s /path/to/your/smartface/workspace/scripts/node_modules@smartface/router/lib /path/to/your/router/installation/lib
You can also use NPM Workspaces feature to link two directories. However, this method will cause your
node_modules
files to merge and the development environment will increase in size dramatically.
Tests will run on before every publish and all of the test cases are required to pass before a new version can be published.
yarn test -- --watch
Documentation will be updated every time when changes are pushed into the master
branch.
To do it manually or oversee the documentation beforehand:
yarn run docs
open ./docs/index.html // Mac Only
Generated using TypeDoc