blob: 083071238bc68da89275e115018b7add5f639566 [file] [log] [blame]
Deepanjan Roy112ff6a2018-09-10 08:31:43 -04001// Copyright (C) 2018 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import * as m from 'mithril';
16
Hector Dearman4d8d73d2018-10-04 13:16:29 +010017import {Actions, DeferredAction} from '../common/actions';
Deepanjan Roy112ff6a2018-09-10 08:31:43 -040018
19interface RouteMap {
20 [route: string]: m.Component;
21}
22
23export const ROUTE_PREFIX = '#!';
24
25export class Router {
26 constructor(
27 private defaultRoute: string, private routes: RouteMap,
Hector Dearman4d8d73d2018-10-04 13:16:29 +010028 private dispatch: (a: DeferredAction) => void) {
Deepanjan Roy112ff6a2018-09-10 08:31:43 -040029 if (!(defaultRoute in routes)) {
30 throw Error('routes must define a component for defaultRoute.');
31 }
32
33 window.onhashchange = () => this.navigateToCurrentHash();
34 }
35
36 /**
37 * Parses and returns the current route string from |window.location.hash|.
38 * May return routes that are not defined in |this.routes|.
39 */
40 getRouteFromHash(): string {
41 const prefixLength = ROUTE_PREFIX.length;
42 const hash = window.location.hash;
43
44 // Do not try to parse route if prefix doesn't match.
45 if (hash.substring(0, prefixLength) !== ROUTE_PREFIX) return '';
46
47 return hash.split('?')[0].substring(prefixLength);
48 }
49
50 /**
51 * Sets |route| on |window.location.hash|. If |route| if not defined in
52 * |this.routes|, dispatches a navigation to |this.defaultRoute|.
53 */
54 setRouteOnHash(route: string) {
55 history.pushState(undefined, undefined, ROUTE_PREFIX + route);
56
57 if (!(route in this.routes)) {
Hector Dearman4544f372018-09-19 12:32:36 +010058 console.info(
59 `Route ${route} not known redirecting to ${this.defaultRoute}.`);
Hector Dearman4d8d73d2018-10-04 13:16:29 +010060 this.dispatch(Actions.navigate({route: this.defaultRoute}));
Deepanjan Roy112ff6a2018-09-10 08:31:43 -040061 }
62 }
63
64 /**
65 * Dispatches navigation action to |this.getRouteFromHash()| if that is
66 * defined in |this.routes|, otherwise to |this.defaultRoute|.
67 */
68 navigateToCurrentHash() {
69 const hashRoute = this.getRouteFromHash();
70 const newRoute = hashRoute in this.routes ? hashRoute : this.defaultRoute;
Hector Dearman4d8d73d2018-10-04 13:16:29 +010071 this.dispatch(Actions.navigate({route: newRoute}));
Deepanjan Roy112ff6a2018-09-10 08:31:43 -040072 // TODO(dproy): Handle case when new route has a permalink.
73 }
74
75 /**
76 * Returns the component for given |route|. If |route| is not defined, returns
77 * component of |this.defaultRoute|.
78 */
79 resolve(route: string|null): m.Component {
80 if (!route || !(route in this.routes)) {
81 return this.routes[this.defaultRoute];
82 }
83 return this.routes[route];
84 }
85
86 param(key: string) {
87 const hash = window.location.hash;
88 const paramStart = hash.indexOf('?');
89 if (paramStart === -1) return undefined;
90 return m.parseQueryString(hash.substring(paramStart))[key];
91 }
92}