Skip to content

Commit

Permalink
Router: add example
Browse files Browse the repository at this point in the history
  • Loading branch information
Elmi Ahmadov committed Apr 9, 2020
1 parent 02d9318 commit 3bd65a5
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 86 deletions.
7 changes: 7 additions & 0 deletions examples/router/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Example "router"

[![GitPod Logo](../../doc/run-in-gitpod.png)](https://gitpod.io/#example=listview-cells/https://github.com/eclipsesource/tabris-decorators/tree/gplink/examples/router)

## Description

Demonstrates the usage of the router API.
21 changes: 21 additions & 0 deletions examples/router/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "router",
"version": "3.3.0",
"dependencies": {
"reflect-metadata": "^0.1.13"
},
"optionalDependencies": {
"tabris": "^3.3.0",
"tabris-decorators": "3.3.0"
},
"devDependencies": {
"typescript": "3.3.x"
},
"main": "dist/app.js",
"scripts": {
"start": "tabris serve -w -a",
"build": "tsc -p .",
"watch": "tsc -p . -w --preserveWatchOutput --inlineSourceMap",
"gitpod": "tabris serve -a -w --no-intro --port 8080 --external $(gp url 8080):443"
}
}
66 changes: 66 additions & 0 deletions examples/router/src/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {NavigationView, contentView, Button, TextView, TextInput, Stack, Page, Properties} from 'tabris';
import {Router, Route, injectable, create, resolve, property, component } from 'tabris-decorators';

debugger;

const navigationView = new NavigationView({
layoutData: 'stretch'
}).appendTo(contentView);

class MyPage1 extends Page {
constructor(properties?: Properties<MyPage1>) {
super(properties);
this.append(
<Stack stretch alignment="stretchX" padding={[0, 4]}>
<TextInput/>
<Button text="Open" onSelect={() =>
router.goTo({
route: 'MyRoute2',
payload: {
text: this.find(TextInput).only().text
}
})}
/>
</Stack>
);
}
}

@component
class MyPage2 extends Page {
@property text: string;

constructor(properties?: Properties<MyPage2>) {
super(properties);
this.append(
<Stack stretch alignment="stretchX" padding={[0, 4]}>
<TextView alignment="centerX" height={32} bind-text="text"/>
<Button text="Go back" onSelect={() => router.back()}/>
<TextInput bind-text="text" />
<Button text="Open" onSelect={() =>
router.goTo({
route: 'MyRoute2',
payload: {
text: this._find(TextInput).only().text
}
})}
/>
</Stack>
);
}
}

@injectable({ param: 'MyRoute1' })
class MyRoute1 extends Route {
page = new MyPage1({title: 'foo'});
};

@injectable({ param: 'MyRoute2' })
class MyRoute2 extends Route {
page = new MyPage2({title: 'bar'});
};

const router = new Router({
navigationView,
history: [{ route: 'MyRoute1' }]
});
17 changes: 17 additions & 0 deletions examples/router/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"lib": ["es6" ],
"jsx": "react",
"jsxFactory": "JSX.createElement",
"outDir": "dist",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"typeRoots": ["./node_modules/@types"]
},
"include": [
"./src/*.ts",
"./src/*.tsx"
]
}
26 changes: 5 additions & 21 deletions src/api/router/Route.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,8 @@
import { Page, Properties } from "tabris";
import { Page } from "tabris";

export interface RouteOptions {
enableDrawer?: boolean;
toolbarVisible?: boolean;
}

export interface RoutePage {
onPayload?(payload?: any): void;
}

export abstract class RoutePage extends Page {
constructor(properties?: Properties<RoutePage>) {
super(properties);
}
}
export type Dictionary<T> = { [key: string]: T };

export abstract class Route {
page: RoutePage;
options: RouteOptions = {
enableDrawer: false,
toolbarVisible: true
}
export class Route<PayloadType = Dictionary<string>> {
page: Page;
payload?: PayloadType;
}
50 changes: 12 additions & 38 deletions src/api/router/Router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,24 @@ import { ListLike, Mutation } from "../List";
import { RouterMatcher } from "./RouterMatcher";
import { RouterHistory, HistoryItem } from "./RouterHistory";
import { Route } from "./Route";
import { Constructor } from "../../internals/utils";

export type RouterConfig = {
name: string;
route: Constructor<Route>
};

export type RouterProperties = {
navigationView: NavigationView,
routers?: ListLike<RouterConfig>,
defaultRoute?: HistoryItem,
history?: ListLike<HistoryItem>
};

export class Router<ItemType extends HistoryItem = HistoryItem> {

private _navigationView: NavigationView;
private _routes: ListLike<RouterConfig>;

private _routerHistoryObserver: RouterHistory;
private _routerMatcher: RouterMatcher;

constructor({navigationView, routers, defaultRoute, history} : RouterProperties) {
constructor({navigationView, history} : RouterProperties) {
this._navigationView = navigationView;
this.routes = routers || [];
this._routerHistoryObserver = new RouterHistory(this._handleHistoryChange);
this._routerMatcher = new RouterMatcher();
this.history = history || [];
this._routerMatcher = new RouterMatcher(this);
if (defaultRoute) {
this.goTo(defaultRoute as ItemType);
}
this._navigationView.onRemoveChild(this._syncHistoryWithNavigationView.bind(this));
}

Expand All @@ -42,33 +29,18 @@ export class Router<ItemType extends HistoryItem = HistoryItem> {
}

back() {
if (this._routerHistoryObserver.source.length === 0) {
if (this._routerHistoryObserver.history.length === 0) {
throw new Error("Could not call back on empty history stack");
}
this._routerHistoryObserver.pop();
}

set history(value: ListLike<HistoryItem>) {
if (this._routerHistoryObserver.source === value) {
return;
}
this._routerHistoryObserver.source = value;
this._routerHistoryObserver.history = value;
}

get history() {
return this._routerHistoryObserver.source;
}

set routes(value: ListLike<RouterConfig>) {
if (this._routes === value) {
return;
}
this._routes = value;
this._routerMatcher = new RouterMatcher(this);
}

get routes() {
return this._routes;
return this._routerHistoryObserver.history;
}

protected _handleHistoryChange = ({deleteCount, items}: Mutation<ItemType>) => {
Expand Down Expand Up @@ -97,13 +69,15 @@ export class Router<ItemType extends HistoryItem = HistoryItem> {
});
}

private _appendRoute(route: Route, payload?: any) {
if (route.page.onPayload && typeof route.page.onPayload === 'function') {
route.page.onPayload(payload);
private _appendRoute(route: Route, payload?: object) {
if (payload) {
for (const key of Object.keys(payload)) {
if (key in route.page) {
route.page[key] = payload[key];
}
}
}
this._navigationView.append(route.page);
this._navigationView.drawerActionVisible = route.options.enableDrawer;
this._navigationView.toolbarVisible = route.options.toolbarVisible;
}

private _syncHistoryWithNavigationView() {
Expand Down
41 changes: 33 additions & 8 deletions src/api/router/RouterHistory.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,53 @@
import { ListLikeObvserver } from "../../internals/ListLikeObserver";
import { Mutation, List, ListLike } from "../List";
import { Dictionary } from "./Route";

export type HistoryItem = { route: string, payload?: any };
export type HistoryItem = { route: string, payload?: Dictionary<string> };

export class RouterHistory<T extends HistoryItem = HistoryItem> extends ListLikeObvserver<T> {
export class RouterHistory<T extends HistoryItem = HistoryItem> {

private _observer: ListLikeObvserver<T>;

constructor(_callback: (ev: Mutation<T>) => void) {
this._observer = new ListLikeObvserver<T>(_callback);
}

public push(item: T) {
const source = Array.from(this.source);
const source = this.getSource();
source.push(item);
this.source = source;
this._observer.source = source;
}

public pop(): T {
const source = Array.from(this.source);
const source = this.getSource();
const result = source.pop();
this.source = source;
this._observer.source = source;
return result;
}

public removeLast() {
return this.source.pop();
return this._observer.source.pop();
}

get history() {
return this._observer.source;
}

set history(value: ListLike<T>) {
this._observer.source = value;
}

get current() {
return this.source[this.source.length - 1];
return this._observer.source[this._observer.source.length - 1];
}

private getSource() {
if (this._observer.source instanceof Array) {
return Array.from(this._observer.source);
} else if (this._observer.source instanceof List) {
return List.from(this._observer.source);
}
throw new Error('Unsupported type');
}

}
26 changes: 7 additions & 19 deletions src/api/router/RouterMatcher.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,18 @@
import { Router, RouterConfig } from "./Router";
import { Route } from "./Route";
import { HistoryItem } from './RouterHistory';
import { Constructor } from "../../internals/utils";
import { resolve } from "../..";

export class RouterMatcher {
private _nameMap: Map<string, RouterConfig> = new Map();

constructor(router: Router) {
const routes = router.routes || [];
routes.forEach(item => {
if (this._nameMap.has(item.name)) {
throw new Error(`Route with '${item.name}' name already exists!`);
}
this._nameMap.set(item.name, item);
});
}

match(historyItem: HistoryItem): Route {
const name = historyItem.route;
if (this._nameMap.has(name)) {
return this._createRoute(this._nameMap.get(name).route);
const route = this._createRoute(name);
if (!route) {
throw new Error(`Route with '${name}' name does not exist!`);
}
throw new Error(`Route with '${name}' name does not exist!`);
return route;
}

private _createRoute(className: Constructor<Route>): Route {
return new className(); // TODO: support to pass parameters
private _createRoute(name: string): Route {
return resolve(Route, name);
}
}

0 comments on commit 3bd65a5

Please sign in to comment.