• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// 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
17import {Actions, DeferredAction} from '../common/actions';
18
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,
28      private dispatch: (a: DeferredAction) => void) {
29    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, "", ROUTE_PREFIX + route);
56
57    if (!(route in this.routes)) {
58      console.info(
59          `Route ${route} not known redirecting to ${this.defaultRoute}.`);
60      this.dispatch(Actions.navigate({route: this.defaultRoute}));
61    }
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;
71    this.dispatch(Actions.navigate({route: newRoute}));
72    // 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  static 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}
93