UNPKG

4.7 kBJavaScriptView Raw
1import { writable } from 'svelte/store';
2import { Router, parse } from './vendor';
3
4import {
5 ROOT_URL, HASHCHANGE, navigateTo, cleanPath, isActive, router,
6} from './utils';
7
8export var baseRouter = new Router();
9export var routeInfo = writable({});
10
11// private registries
12var onError = {};
13var shared = {};
14
15var errors = [];
16var routers = 0;
17var interval;
18var currentURL;
19
20// take snapshot from current state...
21router.subscribe(function (value) { shared.router = value; });
22routeInfo.subscribe(function (value) { shared.routeInfo = value; });
23
24export function doFallback(failure, fallback) {
25 routeInfo.update(function (defaults) {
26 var obj;
27
28 return (Object.assign({}, defaults,
29 ( obj = {}, obj[fallback] = Object.assign({}, shared.router,
30 {failure: failure}), obj )));
31 });
32}
33
34export function handleRoutes(map, params) {
35 var keys = [];
36
37 map.some(function (x) {
38 if (x.key && x.matches && !shared.routeInfo[x.key]) {
39 if (x.redirect && (x.condition === null || x.condition(shared.router) !== true)) {
40 if (x.exact && shared.router.path !== x.path) { return false; }
41 navigateTo(x.redirect);
42 return true;
43 }
44
45 if (x.exact) {
46 keys.push(x.key);
47 }
48
49 // extend shared params...
50 Object.assign(params, x.params);
51
52 // upgrade matching routes!
53 routeInfo.update(function (defaults) {
54 var obj;
55
56 return (Object.assign({}, defaults,
57 ( obj = {}, obj[x.key] = Object.assign({}, shared.router,
58 x), obj )));
59 });
60 }
61
62 return false;
63 });
64
65 return keys;
66}
67
68export function evtHandler() {
69 var baseUri = !HASHCHANGE ? window.location.href.replace(window.location.origin, '') : window.location.hash || '/';
70 var failure;
71
72 // unprefix active URL
73 if (ROOT_URL !== '/') {
74 baseUri = baseUri.replace(cleanPath(ROOT_URL), '');
75 }
76
77 // skip given anchors if already exists on document, see #43
78 if (
79 /^#[\w-]+$/.test(window.location.hash)
80 && document.querySelector(window.location.hash)
81 && currentURL === baseUri.split('#')[0]
82 ) { return; }
83
84 // trailing slash is required to keep route-info on nested routes!
85 // see: https://github.com/pateketrueke/abstract-nested-router/commit/0f338384bddcfbaee30f3ea2c4eb0c24cf5174cd
86 var ref = baseUri.replace('/#', '#').replace(/^#\//, '/').split('?');
87 var fixedUri = ref[0];
88 var qs = ref[1];
89 var fullpath = fixedUri.replace(/\/?$/, '/');
90 var query = parse(qs);
91 var params = {};
92 var keys = [];
93
94 // reset current state
95 routeInfo.set({});
96
97 if (currentURL !== baseUri) {
98 currentURL = baseUri;
99 router.set({
100 path: cleanPath(fullpath),
101 query: query,
102 params: params,
103 });
104 }
105
106 // load all matching routes...
107 baseRouter.resolve(fullpath, function (err, result) {
108 if (err) {
109 failure = err;
110 return;
111 }
112
113 // save exact-keys for deletion after failures!
114 keys.push.apply(keys, handleRoutes(result, params));
115 });
116
117 var toDelete = {};
118
119 // it's fine to omit failures for '/' paths
120 if (failure && failure.path !== '/') {
121 keys.reduce(function (prev, cur) {
122 prev[cur] = null;
123 return prev;
124 }, toDelete);
125 } else {
126 failure = null;
127 }
128
129 // clear previously failed handlers
130 errors.forEach(function (cb) { return cb(); });
131 errors = [];
132
133 try {
134 // clear routes that not longer matches!
135 baseRouter.find(cleanPath(fullpath))
136 .forEach(function (sub) {
137 if (sub.exact && !sub.matches) {
138 toDelete[sub.key] = null;
139 }
140 });
141 } catch (e) {
142 // this is fine
143 }
144
145 // drop unwanted routes...
146 routeInfo.update(function (defaults) { return (Object.assign({}, defaults,
147 toDelete)); });
148
149 var fallback;
150
151 // invoke error-handlers to clear out previous state!
152 Object.keys(onError).forEach(function (root) {
153 if (isActive(root, fullpath, false)) {
154 var fn = onError[root].callback;
155
156 fn(failure);
157 errors.push(fn);
158 }
159
160 if (!fallback && onError[root].fallback) {
161 fallback = onError[root].fallback;
162 }
163 });
164
165 // handle unmatched fallbacks
166 if (failure && fallback) {
167 doFallback(failure, fallback);
168 }
169}
170
171export function findRoutes() {
172 clearTimeout(interval);
173 interval = setTimeout(evtHandler);
174}
175
176export function addRouter(root, fallback, callback) {
177 if (!routers) {
178 window.addEventListener('popstate', findRoutes, false);
179 }
180
181 // register error-handlers
182 if (!onError[root] || fallback) {
183 onError[root] = { fallback: fallback, callback: callback };
184 }
185
186 routers += 1;
187
188 return function () {
189 routers -= 1;
190
191 if (!routers) {
192 window.removeEventListener('popstate', findRoutes, false);
193 }
194 };
195}