@akc42/app-utils 3.6.3 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -0
- package/location.js +171 -0
- package/package.json +1 -1
- package/route.js +233 -0
package/README.md
CHANGED
|
@@ -152,3 +152,102 @@ In doing this it can traverse inside custom elements looking for input elements.
|
|
|
152
152
|
# switch-path
|
|
153
153
|
|
|
154
154
|
Changed window location based on parameters provided
|
|
155
|
+
|
|
156
|
+
# distributed-router
|
|
157
|
+
Distributed Client Side Router for use with a hierarchy of custom components in an Single Page Application
|
|
158
|
+
|
|
159
|
+
This is a series of javascript modules that you can link together to form a distributed client router system. It links
|
|
160
|
+
the browsers URL bar into a chained list of `Routee` objects that process a part of the segmented url. It takes in a `route` object
|
|
161
|
+
and passes out a `subRoute` object. These are chained together, the subRoute at one level being fed into route of the next level down
|
|
162
|
+
the hierarchy.
|
|
163
|
+
|
|
164
|
+
At the top level is a pair of functions `connectUrl` and `disconnectUrl`. Which ever element will be your master controller (I generally
|
|
165
|
+
have a `<main-app>` element which has a `<session-manager>` and `<page-manager>`, both of which are controlling pages. A `<session-manager>`
|
|
166
|
+
page doesn't reflect in the url bar at all as the user navigates the sign in process. But once authorised, the `<page-manager>` takes over
|
|
167
|
+
and controls which page is displayed based on the url. So it is the `<page-manager>` custom element that calls `connectUrl` in its `connectedCallback`
|
|
168
|
+
function, and `disconnectUrl` in its `disconnectedCallback` function. `connectUrl` uses a callback function as its only parameter and this callback
|
|
169
|
+
function gets called on url change, passing the top level `route` object.
|
|
170
|
+
|
|
171
|
+
The next piece in this arrangement is a router. This is a class called `Route` and is instanciated with one required parameter and one
|
|
172
|
+
optional parameter. The required parameter is a string containing "/" separated segments, which must either literally match the part of
|
|
173
|
+
the url, or can start with a ":" character followed by a name, in which case we assume that that part of the url should be interpreted
|
|
174
|
+
as a parameter. We process a new `route` (however we receive it - either via the `connectUrl` callback, or being passed into a custom
|
|
175
|
+
element via a property/attribute) by calling the `routeChange` method, this returns a `route` object which the part of the url segment
|
|
176
|
+
checked against the specification provdied in the `new Route()` call. `route` has an `active` property to determine if it matched and
|
|
177
|
+
a `params` property the value of any of the ":" segments. Any queryString is also decoded and placed in the `query` property of objects.
|
|
178
|
+
|
|
179
|
+
If the `active` propery of a route is false, the subRoute will also have an `active` value of false. A `query` property is always passed
|
|
180
|
+
straight through and it is up to the application to decided how and when to use it.
|
|
181
|
+
|
|
182
|
+
The optional second parameter to the `new Route()` call is a matching string for the previous route up the chain. It consists of a string
|
|
183
|
+
which contains a single ":" character. The left of the ":" character is a parameter name, and to the right a parameter value. The incoming
|
|
184
|
+
route's `params` property must contain the "name" and it must have the value "value" for the subRoute to be active (as well as matching the url).
|
|
185
|
+
|
|
186
|
+
This is usually used with something like this
|
|
187
|
+
```
|
|
188
|
+
const topLevel = new Route('/:page');
|
|
189
|
+
const firstLevel = new Route('/:id', 'page:appointments');
|
|
190
|
+
connectUrl(route => {
|
|
191
|
+
const subRoute = topLevel.routeChange(route);
|
|
192
|
+
if (subRoute.active) {
|
|
193
|
+
...
|
|
194
|
+
|
|
195
|
+
const subSubRoute = firstLevel.routeChange(subRoute);
|
|
196
|
+
if (subSubRoute.active) {
|
|
197
|
+
readDatabaseRecord(subSubRoute.params.id)
|
|
198
|
+
}
|
|
199
|
+
...
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
(I have simplified what happens - subRoute would probably be passed in as the `route` property to a custom element which might at some point want
|
|
205
|
+
read a database record based on id).
|
|
206
|
+
|
|
207
|
+
In this example we only want to read the (lets say) the appointment record from the database if the `<appointment-manager>` element had been activated
|
|
208
|
+
with a url of the form "/appointements/53" and not (say) when the url was "/user/53", when the `<user-manager>` element is in the dom and the `<appointment-manager>` is still in the dom, but not doing anything. The other obvious question is why not do this:-
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
const firstLevel = new Route('/appointments/:id');
|
|
212
|
+
connectUrl(route => {
|
|
213
|
+
const subRoute = topLevel.routeChange(route);
|
|
214
|
+
if (subRoute.active) {
|
|
215
|
+
...
|
|
216
|
+
|
|
217
|
+
const subSubRoute = firstLevel.routeChange(subRoute);
|
|
218
|
+
if (subSubRoute.active) {
|
|
219
|
+
readDatabaseRecord(subSubRoute.params.id)
|
|
220
|
+
}
|
|
221
|
+
...
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
and the answer to that is that I have an element `<route-manager>` which in fact something like `<page-manager>` extends
|
|
227
|
+
which then allows me to do (in `lit-element`s `render` function)
|
|
228
|
+
```
|
|
229
|
+
${ {
|
|
230
|
+
home: html`<app-home></app-home>`,
|
|
231
|
+
user: html`<app-user managed-page .route=${this.subRoute}></app-user>
|
|
232
|
+
appointments: html`<app-appointments managed-page .route=${this.subRoute}></app-appointments`
|
|
233
|
+
}[this.page]
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
The route manager users `new Route('/:page')` to translate the incoming `route` to the `page` property.
|
|
238
|
+
|
|
239
|
+
Internally the `Route` class uses a `route-changed` event which this overall module listens to on the window and this can be used to change the url.
|
|
240
|
+
the `Route` class has three properties that can be set and which can change the url.
|
|
241
|
+
|
|
242
|
+
- `connection` which if set `true` join the input and output of the route managed by this instance provided only that the route doesn't have any ":" segment,
|
|
243
|
+
and change the url accordingly. If set to `false` it will always make the output disconnected.
|
|
244
|
+
- `params` which when set with an object which maps the properties of an active `params` in the `subRoute` will change the url - so for instance in the example
|
|
245
|
+
above calling `firstlevel.params = {id: 20}` will change the url to `/appointments/20`.
|
|
246
|
+
- `query` we can set a query set of parameters and these will then change the url to have those query parameters.
|
|
247
|
+
|
|
248
|
+
Other modules that wish to change the url can do so, but they need to dispatch a `location-altered` event in the window. A helper
|
|
249
|
+
class `LocationAltered` can generate it for you, so to change the location do:-
|
|
250
|
+
```
|
|
251
|
+
history.pushState({}, null, '/user/23');
|
|
252
|
+
window.dispatchEvent(new LocationAltered());
|
|
253
|
+
```
|
package/location.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@licence
|
|
3
|
+
Copyright (c) 2020 Alan Chandler, all rights reserved
|
|
4
|
+
|
|
5
|
+
This file is part of Distributed Router.
|
|
6
|
+
|
|
7
|
+
Distributed Router is free software: you can redistribute it and/or modify
|
|
8
|
+
it under the terms of the GNU General Public License as published by
|
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
(at your option) any later version.
|
|
11
|
+
|
|
12
|
+
Distributed Router is distributed in the hope that it will be useful,
|
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
GNU General Public License for more details.
|
|
16
|
+
|
|
17
|
+
You should have received a copy of the GNU General Public License
|
|
18
|
+
along with Distributed Router. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import {Debug} from './debug.js';
|
|
22
|
+
let routeCallback = null;
|
|
23
|
+
let lastChangedAt;
|
|
24
|
+
let route = null;
|
|
25
|
+
|
|
26
|
+
const debug = Debug('router');
|
|
27
|
+
|
|
28
|
+
const dwellTime = () => {
|
|
29
|
+
return parseInt(localStorage.getItem('dwellTime') || 2000, 10); //dwell time might not be set initially, so keep retrying.
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function connectUrl(callback) {
|
|
33
|
+
debug('connectUrl called')
|
|
34
|
+
if (routeCallback === null) {
|
|
35
|
+
window.addEventListener('hashchange', urlChanged);
|
|
36
|
+
window.addEventListener('popstate', urlChanged);
|
|
37
|
+
window.addEventListener('location-altered', urlChanged);
|
|
38
|
+
window.addEventListener('route-changed', routeChanged);
|
|
39
|
+
}
|
|
40
|
+
routeCallback = callback;
|
|
41
|
+
route = null;
|
|
42
|
+
Promise.resolve().then(() => {
|
|
43
|
+
urlChanged();
|
|
44
|
+
lastChangedAt = window.performance.now() - (dwellTime() - 200); //first time we need to adjust for dwell time
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
export function disconnectUrl() {
|
|
49
|
+
debug('disconnectUrl called')
|
|
50
|
+
routeCallback = null;
|
|
51
|
+
window.removeEventListener('hashchange',urlChanged);
|
|
52
|
+
window.removeEventListener('popstate', urlChanged);
|
|
53
|
+
window.removeEventListener('location-altered',urlChanged);
|
|
54
|
+
window.removeEventListener('route-changed', routeChanged);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function urlChanged() {
|
|
58
|
+
let path = window.decodeURIComponent(window.location.pathname);
|
|
59
|
+
const slashIndex = path.lastIndexOf('/');
|
|
60
|
+
if (path.substring(slashIndex + 1).indexOf('.') >= 0) {
|
|
61
|
+
//we have a '.' in the last part of the path, so cut off this segment
|
|
62
|
+
path = slashIndex < 0 ? '/' : path.substring(0,slashIndex);
|
|
63
|
+
}
|
|
64
|
+
const query = decodeParams(window.location.search.substring(1));
|
|
65
|
+
if (route && route.path === path && JSON.stringify(route.query) === JSON.stringify(query)) return;
|
|
66
|
+
debug('url change route to path',path, 'has query', Object.keys(query).length > 0 )
|
|
67
|
+
lastChangedAt = window.performance.now();
|
|
68
|
+
route = {
|
|
69
|
+
path: path ,
|
|
70
|
+
segment: 0,
|
|
71
|
+
params: {},
|
|
72
|
+
query: query,
|
|
73
|
+
active: true
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
if (routeCallback) routeCallback(route);
|
|
77
|
+
}
|
|
78
|
+
function routeChanged(e) {
|
|
79
|
+
let newPath = route.path;
|
|
80
|
+
if(e.detail.path !== undefined) {
|
|
81
|
+
if (Number.isInteger(e.detail.segment)) {
|
|
82
|
+
debug('route change called path', e.detail.path, 'segments', d.detail.segment, 'current path', route.path )
|
|
83
|
+
let segments = route.path.split('/');
|
|
84
|
+
if (segments[0] === '') segments.shift(); //loose leeding
|
|
85
|
+
if(segments.length < e.detail.segment) {
|
|
86
|
+
throw new Error('routeUpdated with a segment longer than current route');
|
|
87
|
+
}
|
|
88
|
+
if(segments.length > e.detail.segment) segments.length = e.detail.segment; //truncate to just before path
|
|
89
|
+
if (e.detail.path.length > 1) {
|
|
90
|
+
const newPaths = e.detail.path.split('/');
|
|
91
|
+
if (newPaths[0] === '') newPaths.shift(); //ignore blank if first char of path is '/'
|
|
92
|
+
segments = segments.concat(newPaths);
|
|
93
|
+
}
|
|
94
|
+
newPath = '/' + segments.join('/');
|
|
95
|
+
//lose trailing slash if not just a single '/'
|
|
96
|
+
if (newPath.slice(-1) === '/' && newPath.length > 1) newPath = newPath.slice(0,-1);
|
|
97
|
+
} else {
|
|
98
|
+
throw new Error('Invalid segment info in route-updated event');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
let query = Object.assign({}, route.query);
|
|
102
|
+
if (e.detail.query !== undefined) {
|
|
103
|
+
query = e.detail.query;
|
|
104
|
+
}
|
|
105
|
+
let newUrl = window.encodeURI(newPath).replace(/#/g, '%23').replace(/\?/g, '%3F');
|
|
106
|
+
if (Object.keys(query).length > 0) {
|
|
107
|
+
newUrl += '?' + encodeParams(query)
|
|
108
|
+
.replace(/%3F/g, '?')
|
|
109
|
+
.replace(/%2F/g, '/')
|
|
110
|
+
.replace(/'/g, '%27')
|
|
111
|
+
.replace(/#/g, '%23')
|
|
112
|
+
;
|
|
113
|
+
|
|
114
|
+
}
|
|
115
|
+
newUrl += window.location.hash;
|
|
116
|
+
// Tidy up if base tag in header
|
|
117
|
+
const fullUrl = new URL(newUrl, window.location.protocol + '//' + window.location.host).href;
|
|
118
|
+
if (fullUrl !== window.location.href) { //has it changed?
|
|
119
|
+
let now = window.performance.now();
|
|
120
|
+
if (lastChangedAt + dwellTime() > now) {
|
|
121
|
+
window.history.replaceState({}, '', fullUrl);
|
|
122
|
+
} else {
|
|
123
|
+
window.history.pushState({}, '', fullUrl);
|
|
124
|
+
}
|
|
125
|
+
urlChanged();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function encodeParams(params) {
|
|
129
|
+
const encodedParams = [];
|
|
130
|
+
|
|
131
|
+
for (let key in params) {
|
|
132
|
+
const value = params[key];
|
|
133
|
+
if (value === '') {
|
|
134
|
+
encodedParams.push(encodeURIComponent(key) + '=');
|
|
135
|
+
} else {
|
|
136
|
+
encodedParams.push(
|
|
137
|
+
encodeURIComponent(key) + '=' +
|
|
138
|
+
encodeURIComponent(value.toString()));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return encodedParams.join('&');
|
|
142
|
+
}
|
|
143
|
+
function decodeParams(paramString) {
|
|
144
|
+
var params = {};
|
|
145
|
+
// Work around a bug in decodeURIComponent where + is not
|
|
146
|
+
// converted to spaces:
|
|
147
|
+
paramString = (paramString || '').replace(/\+/g, '%20');
|
|
148
|
+
var paramList = paramString.split('&');
|
|
149
|
+
for (var i = 0; i < paramList.length; i++) {
|
|
150
|
+
var param = paramList[i].split('=');
|
|
151
|
+
if (param.length === 2) {
|
|
152
|
+
let value;
|
|
153
|
+
try {
|
|
154
|
+
value = decodeURIComponent(param[1]);
|
|
155
|
+
if (value === 'true') {
|
|
156
|
+
value = true;
|
|
157
|
+
} else if (value === 'false') {
|
|
158
|
+
value = false;
|
|
159
|
+
} else if (/^-?(0|[1-9]\d*)$/.test(value)) {
|
|
160
|
+
//convert to integer, only if not a value with leading zero (like phone number)
|
|
161
|
+
value = parseInt(value,10);
|
|
162
|
+
}
|
|
163
|
+
} catch (e) {
|
|
164
|
+
value = '';
|
|
165
|
+
}
|
|
166
|
+
params[decodeURIComponent(param[0])] = value;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return params;
|
|
170
|
+
}
|
|
171
|
+
|
package/package.json
CHANGED
package/route.js
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
@licence
|
|
3
|
+
Copyright (c) 2020 Alan Chandler, all rights reserved
|
|
4
|
+
|
|
5
|
+
This file is part of Distributed Router.
|
|
6
|
+
|
|
7
|
+
Distributed Router is free software: you can redistribute it and/or modify
|
|
8
|
+
it under the terms of the GNU General Public License as published by
|
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
(at your option) any later version.
|
|
11
|
+
|
|
12
|
+
Distributed Router is distributed in the hope that it will be useful,
|
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
GNU General Public License for more details.
|
|
16
|
+
|
|
17
|
+
You should have received a copy of the GNU General Public License
|
|
18
|
+
along with Distributed Router. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
+
*/
|
|
20
|
+
import Debug from './debug.js';
|
|
21
|
+
|
|
22
|
+
const debug = Debug('router');
|
|
23
|
+
|
|
24
|
+
export default class Route {
|
|
25
|
+
constructor(match = '', ifmatched = '') {
|
|
26
|
+
//set default values
|
|
27
|
+
this.preroute = {active: false, segment: 0, path: '', params: {}, query: {}};
|
|
28
|
+
if (ifmatched.length > 0) {
|
|
29
|
+
this.matcher = ifmatched.split(':');
|
|
30
|
+
if (this.matcher.length !== 2) throw new Error('pas-route: Invalid ifmatched String');
|
|
31
|
+
} else {
|
|
32
|
+
this.matcher = [];
|
|
33
|
+
}
|
|
34
|
+
this.match = match;
|
|
35
|
+
//this is our output
|
|
36
|
+
this._route = {active: false, segment: 0, path: '', params: {}, query: {}};
|
|
37
|
+
this.sentRouteChanged = false;
|
|
38
|
+
debug('new route made with match', match, 'ifmatched', ifmatched)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
routeChange(preroute) {
|
|
42
|
+
debug('routeChanged called with preroute of', JSON.stringify(preroute));
|
|
43
|
+
this.preroute = preroute; //remember it
|
|
44
|
+
if (preroute !== undefined && preroute.active && preroute.path.length > 0 &&
|
|
45
|
+
this._ifMatches(preroute.params) ) {
|
|
46
|
+
if (this.match.length > 0) {
|
|
47
|
+
let completed = false;
|
|
48
|
+
if (this.match.slice(-1) === '/') {
|
|
49
|
+
completed = true; //Special meaning
|
|
50
|
+
if (this.match.length > 1) {
|
|
51
|
+
this.match = this.match.slice(0,-1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const matchedPieces = this.match.split('/');
|
|
55
|
+
if (matchedPieces[0] === '') matchedPieces.shift(); //not interested in blank front
|
|
56
|
+
|
|
57
|
+
const urlPieces = preroute.path.split('/');
|
|
58
|
+
if (urlPieces.length < 2 || urlPieces[0] !== '') {
|
|
59
|
+
//something is wrong with path as it should have started with a '/'
|
|
60
|
+
this._route.active = false;
|
|
61
|
+
throw new Error('Route: Invalid path (should start with /) in route');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
urlPieces.shift();
|
|
65
|
+
let j = urlPieces.length;
|
|
66
|
+
const newRoute = {
|
|
67
|
+
segment: preroute.segment + matchedPieces.length,
|
|
68
|
+
params: {},
|
|
69
|
+
active: true,
|
|
70
|
+
query: preroute.query
|
|
71
|
+
};
|
|
72
|
+
for(let i = 0; i < matchedPieces.length; i++) {
|
|
73
|
+
if (j <= 0) {
|
|
74
|
+
return this._clearOutActive();
|
|
75
|
+
}
|
|
76
|
+
let segment = urlPieces.shift();
|
|
77
|
+
j--;
|
|
78
|
+
if (matchedPieces[i].length !== 0) {
|
|
79
|
+
if (matchedPieces[i].substring(0,1) === ':') {
|
|
80
|
+
const key = matchedPieces[i].substring(1);
|
|
81
|
+
if (key.length > 0) {
|
|
82
|
+
if (/^-?\d+$/.test(segment)) {
|
|
83
|
+
segment = parseInt(segment,10);
|
|
84
|
+
}
|
|
85
|
+
newRoute.params[key] = segment;
|
|
86
|
+
} else {
|
|
87
|
+
throw new Error('Route: Match pattern missing parameter name');
|
|
88
|
+
}
|
|
89
|
+
} else if (matchedPieces[i] !== segment) {
|
|
90
|
+
return this._clearOutActive();
|
|
91
|
+
}
|
|
92
|
+
} else if (segment.length > 0 ){
|
|
93
|
+
return this._clearOutActive();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (completed || preroute.path === '/') {
|
|
97
|
+
newRoute.path = '';
|
|
98
|
+
} else if (j == 0) {
|
|
99
|
+
newRoute.path = '/';
|
|
100
|
+
} else {
|
|
101
|
+
newRoute.path = '/' + urlPieces.join('/');
|
|
102
|
+
}
|
|
103
|
+
if (!this._route.active ||
|
|
104
|
+
JSON.stringify(this._route.params) !== JSON.stringify(newRoute.params) ||
|
|
105
|
+
JSON.stringify(this._route.query) !== JSON.stringify(newRoute.query) ||
|
|
106
|
+
this._route.path !== newRoute.path || this._route.segment !== newRoute.segment) {
|
|
107
|
+
this._route = newRoute;
|
|
108
|
+
this.sentRouteChanged = true;
|
|
109
|
+
}
|
|
110
|
+
debug('Route Change returning (no clear active)', JSON.stringify(this._route));
|
|
111
|
+
} else {
|
|
112
|
+
throw new Error('Route: Match String Required');
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
this._clearOutActive();
|
|
116
|
+
}
|
|
117
|
+
return this._route;
|
|
118
|
+
}
|
|
119
|
+
/*
|
|
120
|
+
* set new paramters provided route is active
|
|
121
|
+
*/
|
|
122
|
+
set params(value) {
|
|
123
|
+
if (this._route.active) {
|
|
124
|
+
let match = this.match;
|
|
125
|
+
if (match.slice(-1) === '/' && match.length > 1) match = this.match.slice(0,-1);
|
|
126
|
+
const matchedPieces = match.split('/');
|
|
127
|
+
if (matchedPieces[0] === '') matchedPieces.shift(); //not interested in blank front
|
|
128
|
+
|
|
129
|
+
let urlPieces = this.preroute.path.split('/');
|
|
130
|
+
urlPieces.shift(); //loose blank front
|
|
131
|
+
let changeMade = false;
|
|
132
|
+
for (let i = 0; i < matchedPieces.length; i++) {
|
|
133
|
+
if (urlPieces.length < i) urlPieces.push(''); //ensure there is a url segment for this match
|
|
134
|
+
if (matchedPieces[i].length !== 0) {
|
|
135
|
+
if (matchedPieces[i].substring(0,1) === ':') {
|
|
136
|
+
const key = matchedPieces[i].substring(1);
|
|
137
|
+
if (value[key] !== undefined) {
|
|
138
|
+
if (Number.isInteger(value[key])) {
|
|
139
|
+
if (urlPieces[i] !== value[key].toString()) {
|
|
140
|
+
urlPieces[i] = value[key].toString();
|
|
141
|
+
changeMade = true;
|
|
142
|
+
}
|
|
143
|
+
} else if (typeof value[key] === 'string') {
|
|
144
|
+
if (value[key].length > 0) {
|
|
145
|
+
if (urlPieces[i] !== value[key]) {
|
|
146
|
+
urlPieces[i] = value[key];
|
|
147
|
+
changeMade = true;
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
//terminate url here
|
|
151
|
+
urlPieces.length = i;
|
|
152
|
+
changeMade = true;
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
} else if (value[key] === null) {
|
|
156
|
+
//terminate url here
|
|
157
|
+
urlPieces.length = i;
|
|
158
|
+
changeMade = true;
|
|
159
|
+
break;
|
|
160
|
+
} else {
|
|
161
|
+
throw new Error('Route: Invalid params.' + key + ' provided (should be a String or an Integer)');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (changeMade) {
|
|
168
|
+
const path = '/' + urlPieces.join('/');
|
|
169
|
+
debug('params set, about to set path',path,'segment', this.preroute.segment );
|
|
170
|
+
window.dispatchEvent(new CustomEvent ('route-changed',{bubbles: true, composed: true, detail:{
|
|
171
|
+
segment: this.preroute.segment,
|
|
172
|
+
path: path
|
|
173
|
+
}}));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/*
|
|
178
|
+
* Set a new query value provided route is active
|
|
179
|
+
*/
|
|
180
|
+
set query(value) {
|
|
181
|
+
const jsv = JSON.stringify(value)
|
|
182
|
+
debug('set query to value', jsv)
|
|
183
|
+
if (this._route.active && JSON.stringify(this._route.query) !== jsv) {
|
|
184
|
+
window.dispatchEvent(new CustomEvent('route-changed',{bubbles: true, composed: true, detail:{query: value}}));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/*
|
|
188
|
+
* We can set or break the connection between a pre-route and its route
|
|
189
|
+
*/
|
|
190
|
+
set connection(value) {
|
|
191
|
+
debug('set connection called with value', value);
|
|
192
|
+
if (this.preroute.active) {
|
|
193
|
+
if (this._route.active) {
|
|
194
|
+
if (value) return; //can't set a matched route active
|
|
195
|
+
//just reset to a url
|
|
196
|
+
window.dispatchEvent(new CustomEvent('route-changed', { bubbles: true, composed: true, detail:{
|
|
197
|
+
segment: this.preroute.segment,
|
|
198
|
+
path: '/'
|
|
199
|
+
}}));
|
|
200
|
+
} else {
|
|
201
|
+
if (value) {
|
|
202
|
+
let match = this.match;
|
|
203
|
+
if (match.slice(-1) === '/' && match.length > 1) match = this.match.slice(0,-1);
|
|
204
|
+
const matchedPieces = match.split('/');
|
|
205
|
+
if (matchedPieces[0] === '') matchedPieces.shift(); //not interested in blank front
|
|
206
|
+
if (matchedPieces.length < 1) return;
|
|
207
|
+
if (matchedPieces.every(piece => piece.length > 0 && piece.indexOf(':') < 0)) {
|
|
208
|
+
window.dispatchEvent(new CustomEvent('route-changed', {bubbles: true, composed: true, detail:{
|
|
209
|
+
segment: this.preroute.segment,
|
|
210
|
+
path: '/' + matchedPieces.join('/')
|
|
211
|
+
}}));
|
|
212
|
+
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
_ifMatches (params) {
|
|
220
|
+
if (this.matcher.length === 0) return true; //Empty String always matches
|
|
221
|
+
return (params[this.matcher[0]] !== undefined && params[this.matcher[0]] === this.matcher[1]);
|
|
222
|
+
}
|
|
223
|
+
_clearOutActive () {
|
|
224
|
+
if (this._route === undefined) return;
|
|
225
|
+
if (this._route.active || !this.sentRouteChanged) {
|
|
226
|
+
this._route = Object.assign({}, this._route, {active:false});
|
|
227
|
+
this.sentRouteChanged = true;
|
|
228
|
+
}
|
|
229
|
+
debug('clearOutActive returning route', JSON.stringify(this._route));
|
|
230
|
+
return this._route;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|