@trullock/page-manager 0.14.3 → 1.0.0
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/package.json +2 -2
- package/readme.md +23 -14
- package/src/index.js +50 -94
- package/src/page.js +0 -2
- package/tests/lolpack.js +1 -1
- package/tests/page.js +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trullock/page-manager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Single page app manager",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/index.js"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@trullock/router": "^0.
|
|
20
|
+
"@trullock/router": "^1.0.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@babel/core": "^7.20.2",
|
package/readme.md
CHANGED
|
@@ -8,16 +8,20 @@ Create a basic page like this:
|
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
import {registerPage, Page} from '@trullock/page-manager';
|
|
11
|
-
registerPage(
|
|
12
|
-
|
|
11
|
+
registerPage({
|
|
12
|
+
name: 'view-thing',
|
|
13
|
+
route: '/things/{thingId}',
|
|
14
|
+
pageClass: class extends Page { }
|
|
13
15
|
});
|
|
14
16
|
```
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
`'view-thing'` is the name of the page, this can be used to build/look-up its url later without needing to hardcode url strings everywhere
|
|
19
|
+
|
|
20
|
+
`'/things/{thingId}'` is the url (route) of the page
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
`pageClass` is a class definition for the page.
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
This can by lazily loaded by providing `pageClassLoader: async () => { ... }` which returns the class type (not an instance of it)
|
|
21
25
|
|
|
22
26
|
### Routing
|
|
23
27
|
|
|
@@ -34,7 +38,11 @@ To build a URL from a route and some parameters, do this:
|
|
|
34
38
|
```
|
|
35
39
|
import { getPath, registerPage } from '@trullock/page-manager';
|
|
36
40
|
|
|
37
|
-
registerPage(
|
|
41
|
+
registerPage({
|
|
42
|
+
name: 'view-thing',
|
|
43
|
+
route: '/things/{thingId}',
|
|
44
|
+
pageClass: class extends Page { }
|
|
45
|
+
});
|
|
38
46
|
|
|
39
47
|
let url = getPath('view-thing', { thingId: 1 })
|
|
40
48
|
// url == '/things/1'
|
|
@@ -298,12 +306,12 @@ In the case that were was no goal (i.e. you went directly to the current page, i
|
|
|
298
306
|
PageManager is built so that it can lazily fetch page markup. To do this, use the following options:
|
|
299
307
|
|
|
300
308
|
```
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
return fetch(
|
|
309
|
+
registerPage({
|
|
310
|
+
name: 'view-thing',
|
|
311
|
+
route: '/things/{thingId}',
|
|
312
|
+
pageClass: class extends Page { },
|
|
313
|
+
loadMarkup: routeResult => {
|
|
314
|
+
return fetch('/pages/' + routeResult.name + '.html')
|
|
307
315
|
.then(r => r.text())
|
|
308
316
|
.then(html => {
|
|
309
317
|
var $div = document.createElement('div');
|
|
@@ -312,10 +320,11 @@ PageManager is built so that it can lazily fetch page markup. To do this, use th
|
|
|
312
320
|
return $div.firstElementChild;
|
|
313
321
|
})
|
|
314
322
|
.then($template => {
|
|
315
|
-
pageTemplateCache[
|
|
323
|
+
pageTemplateCache[routeResult.pattern] = $template;
|
|
316
324
|
return $template;
|
|
317
325
|
});
|
|
318
|
-
}
|
|
326
|
+
}
|
|
327
|
+
});
|
|
319
328
|
|
|
320
329
|
// Once a page has been fetched and rendered from its template, it needs attaching to the DOM. Use this options to control where it gets inserted.
|
|
321
330
|
attachMarkup: $html => document.body.appendChild($html),
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import router from '@trullock/router';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
pageCache = {},
|
|
3
|
+
var pageCache = {},
|
|
5
4
|
pageTemplateCache = {},
|
|
6
5
|
stack = [],
|
|
7
6
|
stackPointer = -1;
|
|
@@ -13,21 +12,7 @@ var lastNavigationDirection = null;
|
|
|
13
12
|
var goal = null;
|
|
14
13
|
var backData = {};
|
|
15
14
|
var options = {
|
|
16
|
-
|
|
17
|
-
fetchPageTemplate: async route => {
|
|
18
|
-
const response = await fetch(options.fetchPath(route));
|
|
19
|
-
const html = await response.text();
|
|
20
|
-
|
|
21
|
-
var $div = document.createElement('div');
|
|
22
|
-
$div.innerHTML = html;
|
|
23
|
-
const $template = $div.firstElementChild;
|
|
24
|
-
|
|
25
|
-
// TODO: why is this in here and not in the caller?
|
|
26
|
-
pageTemplateCache[route.pattern] = $template;
|
|
27
|
-
|
|
28
|
-
return $template;
|
|
29
|
-
},
|
|
30
|
-
pageInterrupt: route => null,
|
|
15
|
+
pageInterrupt: routeResult => null,
|
|
31
16
|
attachMarkup: $html => document.body.appendChild($html),
|
|
32
17
|
prepareMarkup: $html => { },
|
|
33
18
|
loadingPageName: 'loading',
|
|
@@ -36,35 +21,22 @@ var options = {
|
|
|
36
21
|
beforeHide: null
|
|
37
22
|
}
|
|
38
23
|
|
|
39
|
-
export
|
|
40
|
-
|
|
41
|
-
export function registerPage(argA, argB, argC) {
|
|
24
|
+
export function registerPage(opts) {
|
|
42
25
|
|
|
43
|
-
let
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
{
|
|
48
|
-
namedRoutes = argA;
|
|
49
|
-
pageClass = argB;
|
|
50
|
-
} else {
|
|
51
|
-
namedRoutes = {
|
|
52
|
-
[argA]: argB
|
|
53
|
-
};
|
|
54
|
-
pageClass = argC;
|
|
55
|
-
}
|
|
56
|
-
|
|
26
|
+
let loadPageClass = opts.pageClass || opts.pageClassLoader;
|
|
27
|
+
let namedRoutes = opts.namedRoutes || { [opts.name]: opts.route };
|
|
28
|
+
let auth = opts.auth || null;
|
|
29
|
+
let cacheMarkupBy = opts.cacheMarkupBy || 'path'
|
|
57
30
|
|
|
58
31
|
for (const [name, route] of Object.entries(namedRoutes)) {
|
|
59
|
-
router.addRoute(name, route,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
32
|
+
router.addRoute(name, route, {
|
|
33
|
+
route,
|
|
34
|
+
auth,
|
|
35
|
+
loadPageClass,
|
|
36
|
+
loadMarkup: opts.loadMarkup,
|
|
37
|
+
cacheMarkupBy
|
|
38
|
+
});
|
|
65
39
|
}
|
|
66
|
-
|
|
67
|
-
return pageClass;
|
|
68
40
|
}
|
|
69
41
|
|
|
70
42
|
export function getPath(name, values) {
|
|
@@ -92,13 +64,13 @@ function emitUrlChanged(url)
|
|
|
92
64
|
// TODO: 404 and error too?
|
|
93
65
|
function initLoading()
|
|
94
66
|
{
|
|
95
|
-
var entry =
|
|
67
|
+
var entry = router.routesByName(options.loadingPageName);
|
|
96
68
|
var route = router.parse(entry.route);
|
|
97
69
|
return loadPage(route, {});
|
|
98
70
|
}
|
|
99
71
|
|
|
100
72
|
function showLoading() {
|
|
101
|
-
var pageLookup =
|
|
73
|
+
var pageLookup = router.routesByName(options.loadingPageName);
|
|
102
74
|
var route = router.parse(pageLookup.route);
|
|
103
75
|
var data = {
|
|
104
76
|
route: route,
|
|
@@ -113,27 +85,28 @@ function showLoading() {
|
|
|
113
85
|
}
|
|
114
86
|
|
|
115
87
|
function hideLoading() {
|
|
116
|
-
var pageLookup =
|
|
88
|
+
var pageLookup = router.routesByName(options.loadingPageName);
|
|
117
89
|
var route = router.parse(pageLookup.route);
|
|
118
90
|
var page = (pageCache[route.path] || pageCache[route.pattern]).page;
|
|
119
91
|
return Promise.resolve(page.hide());
|
|
120
92
|
}
|
|
121
93
|
|
|
122
|
-
async function loadPage(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
94
|
+
async function loadPage(routeResult, data)
|
|
95
|
+
{
|
|
96
|
+
if(!pageTemplateCache[routeResult.pattern])
|
|
97
|
+
pageTemplateCache[routeResult.pattern] = await routeResult.data.loadMarkup(routeResult);
|
|
98
|
+
let $template = pageTemplateCache[routeResult.pattern]
|
|
127
99
|
|
|
128
100
|
var $html = $template.cloneNode(true);
|
|
129
101
|
options.prepareMarkup($html);
|
|
130
102
|
options.attachMarkup($html);
|
|
131
103
|
|
|
132
|
-
|
|
104
|
+
routeResult.data.pageClass = await Promise.resolve(routeResult.data.loadPageClass());
|
|
105
|
+
let page = new (routeResult.data.pageClass)($html);
|
|
133
106
|
|
|
134
107
|
await Promise.resolve(page.boot(data));
|
|
135
108
|
|
|
136
|
-
let cacheKey =
|
|
109
|
+
let cacheKey = routeResult.data.cacheMarkupBy == 'path' ? routeResult.path : routeResult.pattern;
|
|
137
110
|
pageCache[cacheKey] = {
|
|
138
111
|
$html,
|
|
139
112
|
page
|
|
@@ -142,49 +115,52 @@ async function loadPage(route, data) {
|
|
|
142
115
|
return page;
|
|
143
116
|
}
|
|
144
117
|
|
|
145
|
-
async function showPage(url, data, event)
|
|
146
|
-
|
|
147
|
-
|
|
118
|
+
async function showPage(url, data, event)
|
|
119
|
+
{
|
|
120
|
+
var routeResult = router.parse(url);
|
|
121
|
+
if (routeResult == null) {
|
|
148
122
|
console.error(`Can't find page: '${url}'`);
|
|
149
123
|
|
|
150
|
-
let page404 =
|
|
151
|
-
|
|
124
|
+
let page404 = router.routesByName(options.error404PageName);
|
|
125
|
+
routeResult = router.parse(page404.route)
|
|
152
126
|
}
|
|
153
127
|
|
|
154
128
|
data = data || {};
|
|
155
|
-
for (let key in
|
|
156
|
-
data[key] =
|
|
129
|
+
for (let key in routeResult.params)
|
|
130
|
+
data[key] = routeResult.params[key];
|
|
157
131
|
|
|
158
132
|
data.route = {
|
|
159
133
|
url: url,
|
|
160
|
-
path:
|
|
161
|
-
|
|
162
|
-
params:
|
|
134
|
+
path: routeResult.path,
|
|
135
|
+
name: routeResult.name,
|
|
136
|
+
params: routeResult.params
|
|
163
137
|
};
|
|
164
138
|
data.event = event;
|
|
165
139
|
|
|
166
|
-
let interrupt = await Promise.resolve(options.pageInterrupt(
|
|
140
|
+
let interrupt = await Promise.resolve(options.pageInterrupt(routeResult));
|
|
167
141
|
if(interrupt)
|
|
168
142
|
{
|
|
169
143
|
goal = { url, data };
|
|
170
144
|
return showPage(interrupt.url, null, event);
|
|
171
145
|
}
|
|
172
146
|
|
|
173
|
-
|
|
174
|
-
if (pageCache[route.path])
|
|
175
|
-
return pageCache[route.path].page;
|
|
176
|
-
|
|
177
|
-
if (pageCache[route.pattern])
|
|
178
|
-
return pageCache[route.pattern].page;
|
|
147
|
+
await showLoading();
|
|
179
148
|
|
|
180
|
-
|
|
181
|
-
|
|
149
|
+
var getPage = null
|
|
150
|
+
if (pageCache[routeResult.path])
|
|
151
|
+
getPage = pageCache[routeResult.path].page;
|
|
152
|
+
else if (pageCache[routeResult.pattern])
|
|
153
|
+
getPage = pageCache[routeResult.pattern].page;
|
|
154
|
+
else
|
|
155
|
+
getPage = loadPage(routeResult, data)
|
|
182
156
|
|
|
183
157
|
// handle initial page
|
|
184
158
|
if (event.action == 'load')
|
|
185
159
|
{
|
|
186
160
|
const page = await getPage;
|
|
187
161
|
await doShow(page, data);
|
|
162
|
+
await hideLoading();
|
|
163
|
+
|
|
188
164
|
// clean initial load
|
|
189
165
|
if (stackPointer == -1) {
|
|
190
166
|
stack.push({ uid: 0, data, page });
|
|
@@ -201,10 +177,11 @@ async function showPage(url, data, event) {
|
|
|
201
177
|
|
|
202
178
|
let currentState = stack[stackPointer];
|
|
203
179
|
|
|
204
|
-
if (currentState.data.route.path ==
|
|
180
|
+
if (currentState.data.route.path == routeResult.path) {
|
|
205
181
|
handleHistoryAction(event, url, data, currentState.page);
|
|
206
182
|
const page = await getPage;
|
|
207
183
|
await doUpdate(page, data);
|
|
184
|
+
await hideLoading();
|
|
208
185
|
return page;
|
|
209
186
|
}
|
|
210
187
|
|
|
@@ -225,6 +202,7 @@ async function showPage(url, data, event) {
|
|
|
225
202
|
// else if (event.action == 'fwd')
|
|
226
203
|
// history.go(-1);
|
|
227
204
|
// });
|
|
205
|
+
await hideLoading();
|
|
228
206
|
return page
|
|
229
207
|
}
|
|
230
208
|
|
|
@@ -234,14 +212,12 @@ async function doShow(page, data) {
|
|
|
234
212
|
|
|
235
213
|
await Promise.resolve(page.show(data));
|
|
236
214
|
document.title = page.title;
|
|
237
|
-
await hideLoading();
|
|
238
215
|
}
|
|
239
216
|
|
|
240
217
|
async function doUpdate(page, data) {
|
|
241
218
|
|
|
242
219
|
await Promise.resolve(page.update(data));
|
|
243
220
|
document.title = page.title
|
|
244
|
-
await hideLoading();
|
|
245
221
|
}
|
|
246
222
|
|
|
247
223
|
function handleHistoryAction(event, url, data, page) {
|
|
@@ -320,26 +296,6 @@ export async function init(opts) {
|
|
|
320
296
|
}
|
|
321
297
|
}
|
|
322
298
|
|
|
323
|
-
// handle pages whose markup is already loaded in the page
|
|
324
|
-
for (var key in pageHash) {
|
|
325
|
-
if (pageHash[key].pageClass.existingDomSelector !== undefined) {
|
|
326
|
-
|
|
327
|
-
if(pageHash[key].pageClass.existingDomSelector === null)
|
|
328
|
-
continue;
|
|
329
|
-
|
|
330
|
-
let $html = document.querySelector(pageHash[key].pageClass.existingDomSelector)
|
|
331
|
-
if(!$html)
|
|
332
|
-
{
|
|
333
|
-
console.error(`Unable to find DOM element '${pageHash[key].pageClass.existingDomSelector}' for page '${key}'`)
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// TODO: this is inefficient for non parameterised routes. There will always be HTML in memory and then copied for the page once loaded
|
|
338
|
-
pageTemplateCache[router.routesByName[key]._pattern] = $html;
|
|
339
|
-
$html.parentElement.removeChild($html);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
299
|
await initLoading();
|
|
344
300
|
|
|
345
301
|
// set initial page
|
package/src/page.js
CHANGED
package/tests/lolpack.js
CHANGED
|
@@ -12,7 +12,7 @@ import pageShowFail from './page-show-fail.js'
|
|
|
12
12
|
window.addEventListener('DOMContentLoaded', function () {
|
|
13
13
|
pageManager.init({
|
|
14
14
|
pageContainer: () => document.body,
|
|
15
|
-
fetchPath: route => '/pages/' + route.
|
|
15
|
+
fetchPath: route => '/pages/' + route.name + '.htm',
|
|
16
16
|
error404PageName: 'page404',
|
|
17
17
|
beforeHide: message => new Promise(resolve => {
|
|
18
18
|
resolve(confirm(message))
|
package/tests/page.js
CHANGED