@trullock/page-manager 1.2.1 → 1.3.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/package.json +2 -2
- package/src/index.js +138 -66
- package/tests/dist/index.html +20 -0
- package/tests/dist/loading.htm +3 -0
- package/tests/dist/main.js +1661 -0
- package/tests/dist/main.js.map +1 -0
- package/tests/dist/page1.htm +6 -0
- package/tests/dist/page2.htm +6 -0
- package/tests/dist/page3.htm +5 -0
- package/tests/dist/page4.htm +4 -0
- package/tests/dist/page404.htm +3 -0
- package/tests/dist/pageX.htm +4 -0
- package/tests/helpers.js +11 -0
- package/tests/index.html +0 -3
- package/tests/loading.htm +3 -0
- package/tests/lolpack.js +5 -4
- package/tests/page-loading.js +11 -4
- package/tests/page-page1.js +17 -11
- package/tests/page-page2.js +18 -7
- package/tests/page-page3.js +16 -10
- package/tests/page-page4.js +14 -8
- package/tests/page-page404.js +10 -4
- package/tests/page-pageX.js +15 -9
- package/tests/page-show-fail.js +14 -9
- package/tests/page2.htm +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trullock/page-manager",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Single page app manager",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/index.js"
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"build": "webpack --mode=development",
|
|
12
12
|
"tests": "node ./tests/tests.js"
|
|
13
13
|
},
|
|
14
|
-
"author": "",
|
|
14
|
+
"author": "Trullock",
|
|
15
15
|
"license": "ISC",
|
|
16
16
|
"publishConfig": {
|
|
17
17
|
"access": "public"
|
package/src/index.js
CHANGED
|
@@ -7,7 +7,7 @@ var pageCache = {},
|
|
|
7
7
|
|
|
8
8
|
var manuallyAdjustingHistory = false;
|
|
9
9
|
var handlingBeforeHide = false;
|
|
10
|
-
var
|
|
10
|
+
var beforeHideNavDirection = null;
|
|
11
11
|
|
|
12
12
|
var goal = null;
|
|
13
13
|
var backData = {};
|
|
@@ -25,14 +25,12 @@ export function registerPage(opts) {
|
|
|
25
25
|
|
|
26
26
|
let loadPageClass = opts.pageClass ? (() => opts.pageClass) : opts.loadPageClass;
|
|
27
27
|
let namedRoutes = opts.namedRoutes || { [opts.name]: opts.route };
|
|
28
|
-
let auth = opts.auth || null;
|
|
29
28
|
let cacheMarkupBy = opts.cacheMarkupBy || 'path'
|
|
30
29
|
let extra = opts.extra || {}
|
|
31
30
|
|
|
32
31
|
for (const [name, route] of Object.entries(namedRoutes)) {
|
|
33
32
|
router.addRoute(name, route, {
|
|
34
33
|
route,
|
|
35
|
-
auth,
|
|
36
34
|
loadPageClass,
|
|
37
35
|
loadMarkup: opts.loadMarkup,
|
|
38
36
|
cacheMarkupBy,
|
|
@@ -67,8 +65,8 @@ function emitUrlChanged(url)
|
|
|
67
65
|
function initLoading()
|
|
68
66
|
{
|
|
69
67
|
var url = router.interpolate(options.loadingPageName, {});
|
|
70
|
-
var
|
|
71
|
-
return loadPage(
|
|
68
|
+
var routeResult = router.parse(url);
|
|
69
|
+
return loadPage(routeResult, {});
|
|
72
70
|
}
|
|
73
71
|
|
|
74
72
|
function showLoading() {
|
|
@@ -222,9 +220,19 @@ async function doUpdate(page, data) {
|
|
|
222
220
|
document.title = page.title
|
|
223
221
|
}
|
|
224
222
|
|
|
225
|
-
function
|
|
223
|
+
function getNewUid()
|
|
224
|
+
{
|
|
225
|
+
let uid = -1;
|
|
226
|
+
|
|
227
|
+
for(let i = 0; i < stack.length; i++)
|
|
228
|
+
uid = Math.max(uid, stack[i].uid)
|
|
229
|
+
|
|
230
|
+
return uid + 1;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function handleHistoryAction(event, url, data, page) {
|
|
226
234
|
if (event.action == 'push') {
|
|
227
|
-
let newUid =
|
|
235
|
+
let newUid = getNewUid()
|
|
228
236
|
|
|
229
237
|
window.history.pushState({ uid: newUid }, null, url);
|
|
230
238
|
emitUrlChanged(url);
|
|
@@ -235,19 +243,24 @@ function handleHistoryAction(event, url, data, page) {
|
|
|
235
243
|
stack.push({ uid: newUid, data, page });
|
|
236
244
|
stackPointer++;
|
|
237
245
|
}
|
|
238
|
-
else if (event.action == 'replace')
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
window.history.replaceState({ uid: currentUid }, null, url);
|
|
247
|
-
emitUrlChanged(url);
|
|
246
|
+
else if (event.action == 'replace')
|
|
247
|
+
{
|
|
248
|
+
if(data.route.url == stack[stackPointer - 1]?.data?.route?.url)
|
|
249
|
+
{
|
|
250
|
+
let statesToKeep = [];
|
|
251
|
+
for (var i = 0; i < stackPointer - 1; i++)
|
|
252
|
+
statesToKeep.push(stack[i]);
|
|
253
|
+
statesToKeep.push({ uid: stack[stackPointer - 1].uid, data, page });
|
|
248
254
|
|
|
249
|
-
|
|
250
|
-
|
|
255
|
+
await modifyHistory(statesToKeep);
|
|
256
|
+
}
|
|
257
|
+
else
|
|
258
|
+
{
|
|
259
|
+
let newId = getNewUid();
|
|
260
|
+
window.history.replaceState({ uid: newId }, null, url);
|
|
261
|
+
stack[stackPointer] = { uid: newId, data, page };
|
|
262
|
+
emitUrlChanged(url);
|
|
263
|
+
}
|
|
251
264
|
}
|
|
252
265
|
else if(event.action == 'back')
|
|
253
266
|
{
|
|
@@ -299,7 +312,6 @@ export async function init(opts) {
|
|
|
299
312
|
}
|
|
300
313
|
|
|
301
314
|
await initLoading();
|
|
302
|
-
|
|
303
315
|
await showLoading();
|
|
304
316
|
}
|
|
305
317
|
|
|
@@ -312,18 +324,18 @@ export async function ready()
|
|
|
312
324
|
|
|
313
325
|
if (e instanceof PageShowError)
|
|
314
326
|
{
|
|
315
|
-
return showPage(e.url, e.data, { action: 'load', distance: 0 }).then(page => {
|
|
327
|
+
return showPage(e.url, e.data, { action: 'load', distance: 0 }).then(async page => {
|
|
316
328
|
if(e.action == 'replace')
|
|
317
|
-
handleHistoryAction({ action: e.action }, e.url, e.data, page);
|
|
329
|
+
await handleHistoryAction({ action: e.action }, e.url, e.data, page);
|
|
318
330
|
return page;
|
|
319
331
|
});
|
|
320
332
|
}
|
|
321
333
|
});
|
|
322
334
|
|
|
323
|
-
function handlePopstate(context, direction, distance) {
|
|
335
|
+
async function handlePopstate(context, direction, distance) {
|
|
324
336
|
|
|
325
337
|
if (manuallyAdjustingHistory) {
|
|
326
|
-
manuallyAdjustingHistory(context, { action: direction, distance });
|
|
338
|
+
await manuallyAdjustingHistory(context, { action: direction, distance });
|
|
327
339
|
return;
|
|
328
340
|
}
|
|
329
341
|
|
|
@@ -343,7 +355,7 @@ export async function ready()
|
|
|
343
355
|
})
|
|
344
356
|
}
|
|
345
357
|
|
|
346
|
-
function handleBeforeHidePart1() {
|
|
358
|
+
function handleBeforeHidePart1(lastNavDirection) {
|
|
347
359
|
// if we're ignoring beforeHide this navigation
|
|
348
360
|
if (handlingBeforeHide === 'ignore') {
|
|
349
361
|
handlingBeforeHide = false;
|
|
@@ -352,6 +364,8 @@ export async function ready()
|
|
|
352
364
|
|
|
353
365
|
// if we have a before-unload confirm to show
|
|
354
366
|
if (stack[stackPointer].page.beforeHide && options.beforeHide && handlingBeforeHide === false) {
|
|
367
|
+
beforeHideNavDirection = lastNavDirection;
|
|
368
|
+
|
|
355
369
|
var interrupt = stack[stackPointer].page.beforeHide();
|
|
356
370
|
if (interrupt) {
|
|
357
371
|
handlingBeforeHide = 'step1';
|
|
@@ -359,9 +373,9 @@ export async function ready()
|
|
|
359
373
|
// do this in a new thread, you cant call history actions from inside a history-aciton-handler
|
|
360
374
|
window.setTimeout(() => {
|
|
361
375
|
// undo the navigation so the URL remains correct whilst we show the confirm dialog
|
|
362
|
-
if (
|
|
376
|
+
if (beforeHideNavDirection == 'fwd')
|
|
363
377
|
history.back();
|
|
364
|
-
else if (
|
|
378
|
+
else if (beforeHideNavDirection == 'back')
|
|
365
379
|
history.forward();
|
|
366
380
|
}, 1);
|
|
367
381
|
|
|
@@ -371,7 +385,10 @@ export async function ready()
|
|
|
371
385
|
|
|
372
386
|
// we've finished beforeHiding
|
|
373
387
|
if (handlingBeforeHide === 'step2')
|
|
388
|
+
{
|
|
374
389
|
handlingBeforeHide = false;
|
|
390
|
+
beforeHideNavDirection = null;
|
|
391
|
+
}
|
|
375
392
|
|
|
376
393
|
return false;
|
|
377
394
|
}
|
|
@@ -380,20 +397,26 @@ export async function ready()
|
|
|
380
397
|
if (handlingBeforeHide !== 'step1')
|
|
381
398
|
return false;
|
|
382
399
|
|
|
400
|
+
if(manuallyAdjustingHistory)
|
|
401
|
+
return false;
|
|
402
|
+
|
|
383
403
|
// do the beforeHide action, then...
|
|
384
404
|
options.beforeHide(stack[stackPointer].page.beforeHide()).then(result => {
|
|
385
405
|
|
|
386
406
|
// if the user confirmed, redo the original action
|
|
387
|
-
if (result)
|
|
388
|
-
|
|
407
|
+
if (result)
|
|
408
|
+
{
|
|
389
409
|
handlingBeforeHide = 'step2';
|
|
390
410
|
|
|
391
|
-
if (
|
|
411
|
+
if (beforeHideNavDirection == 'fwd')
|
|
392
412
|
history.forward();
|
|
393
|
-
else if (
|
|
413
|
+
else if (beforeHideNavDirection == 'back')
|
|
394
414
|
history.back();
|
|
395
|
-
}
|
|
415
|
+
}
|
|
416
|
+
else
|
|
417
|
+
{
|
|
396
418
|
handlingBeforeHide = false;
|
|
419
|
+
beforeHideNavDirection = null;
|
|
397
420
|
}
|
|
398
421
|
});
|
|
399
422
|
|
|
@@ -401,23 +424,23 @@ export async function ready()
|
|
|
401
424
|
}
|
|
402
425
|
|
|
403
426
|
// listen for browser navigations
|
|
404
|
-
window.addEventListener("popstate", e => {
|
|
405
|
-
|
|
406
|
-
if (interrupted)
|
|
407
|
-
return;
|
|
408
|
-
|
|
427
|
+
window.addEventListener("popstate", async e => {
|
|
428
|
+
// TODO: is 0 sensible here, or should we call getNewId()?
|
|
409
429
|
let newUid = e.state?.uid || 0;
|
|
410
430
|
let previousUid = stack[stackPointer].uid;
|
|
411
|
-
|
|
412
|
-
lastNavigationDirection = newUid > previousUid ? 'fwd' : 'back';
|
|
431
|
+
let lastNavigationDirection = newUid > previousUid ? 'fwd' : 'back';
|
|
413
432
|
let distance = Math.abs(newUid - previousUid);
|
|
414
433
|
|
|
415
|
-
var interrupted =
|
|
434
|
+
var interrupted = handleBeforeHidePart2();
|
|
435
|
+
if (interrupted)
|
|
436
|
+
return;
|
|
437
|
+
|
|
438
|
+
var interrupted = handleBeforeHidePart1(lastNavigationDirection);
|
|
416
439
|
if (interrupted)
|
|
417
440
|
return;
|
|
418
441
|
|
|
419
442
|
var context = findContext(newUid);
|
|
420
|
-
handlePopstate(context, lastNavigationDirection, distance);
|
|
443
|
+
await handlePopstate(context, lastNavigationDirection, distance);
|
|
421
444
|
});
|
|
422
445
|
|
|
423
446
|
if(storageAvailable())
|
|
@@ -505,30 +528,37 @@ export function printStack() {
|
|
|
505
528
|
console.log(stack[i]);
|
|
506
529
|
}
|
|
507
530
|
|
|
508
|
-
|
|
531
|
+
function modifyHistory(statesToKeep)
|
|
509
532
|
{
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
statesToKeep.push(stack[i]);
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
// TODO: ensure we always have at least 1 state to keep - must/can this always be the current page?
|
|
533
|
+
// BUG: You can't remove the 2nd stack frame. This is because you'd have to go back to -1, to then go forward to 0 (removing frame 1)
|
|
534
|
+
// For clarity, you can remove (replace) frame 0 (1st), and frame 2+ (3rd+)
|
|
535
|
+
// As a workaround, we replace the 2nd stack frame, if it is the tail, with the root "/".
|
|
536
|
+
// This at least hopefully removes any meaning from it, simply taking you "home" rather than to wherever you were likely trying to remove.
|
|
518
537
|
|
|
519
538
|
if (statesToKeep.length == stack.length)
|
|
520
539
|
return Promise.resolve();
|
|
521
540
|
|
|
541
|
+
return new Promise(async (resolve, reject) => {
|
|
522
542
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
let backsToDo = stackPointer - 1;
|
|
527
|
-
let currentUid = -1;
|
|
543
|
+
let backsToDo = stackPointer;
|
|
528
544
|
|
|
529
545
|
// TODO: handle stack pointer not being at the tail when this process starts
|
|
546
|
+
|
|
547
|
+
// see BUG comments at the top of this method
|
|
548
|
+
let apply2ndFrameHack = statesToKeep.length == 1 && stack.length > 1
|
|
549
|
+
let secondFrameHackUid = -1
|
|
550
|
+
let defaultPageRouteResult = null;
|
|
551
|
+
let defaultPage = null;
|
|
552
|
+
if(apply2ndFrameHack)
|
|
553
|
+
{
|
|
554
|
+
let url = getPath(options.defaultPageName);
|
|
555
|
+
defaultPageRouteResult = router.parse(url);
|
|
556
|
+
defaultPage = await loadPage(defaultPageRouteResult, {});
|
|
557
|
+
|
|
558
|
+
secondFrameHackUid = getNewUid();
|
|
559
|
+
}
|
|
530
560
|
|
|
531
|
-
manuallyAdjustingHistory = _ => {
|
|
561
|
+
manuallyAdjustingHistory = async _ => {
|
|
532
562
|
// rewind to the first history position
|
|
533
563
|
if(backsToDo > 0)
|
|
534
564
|
{
|
|
@@ -540,36 +570,78 @@ export function removeHistory(predicate)
|
|
|
540
570
|
}
|
|
541
571
|
|
|
542
572
|
// reset the stack
|
|
573
|
+
|
|
543
574
|
stack = [];
|
|
544
575
|
|
|
545
576
|
for (var k = 0; k < statesToKeep.length; k++) {
|
|
546
577
|
let currentState = statesToKeep[k];
|
|
547
|
-
currentState.uid = ++currentUid;
|
|
548
578
|
|
|
549
579
|
if (k == 0)
|
|
550
|
-
window.history.replaceState({ uid: currentState.uid },
|
|
580
|
+
window.history.replaceState({ uid: currentState.uid }, currentState.page.title, currentState.data.route.url);
|
|
551
581
|
else
|
|
552
|
-
window.history.pushState({ uid: currentState.uid },
|
|
582
|
+
window.history.pushState({ uid: currentState.uid }, currentState.page.title, currentState.data.route.url);
|
|
553
583
|
|
|
554
|
-
|
|
555
|
-
url: currentState.data.route.url
|
|
556
|
-
}))
|
|
557
|
-
|
|
558
|
-
// TODO: this doesnt seem to work when k=0
|
|
584
|
+
// TODO: this doesnt seem to work when k=0
|
|
559
585
|
document.title = currentState.page.title;
|
|
560
586
|
|
|
561
587
|
stack.push(currentState);
|
|
562
|
-
}
|
|
563
588
|
|
|
564
|
-
|
|
589
|
+
emitUrlChanged(currentState.data.route.url)
|
|
590
|
+
}
|
|
565
591
|
|
|
592
|
+
// see BUG comments at the top of this method
|
|
593
|
+
if(apply2ndFrameHack)
|
|
594
|
+
{
|
|
595
|
+
stack.push({
|
|
596
|
+
uid: secondFrameHackUid,
|
|
597
|
+
data: {
|
|
598
|
+
route: {
|
|
599
|
+
url: defaultPageRouteResult.path,
|
|
600
|
+
path: defaultPageRouteResult.path,
|
|
601
|
+
name: defaultPageRouteResult.name,
|
|
602
|
+
params: defaultPageRouteResult.params
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
page: defaultPage
|
|
606
|
+
})
|
|
607
|
+
|
|
608
|
+
stackPointer = 0;
|
|
609
|
+
}
|
|
610
|
+
else
|
|
611
|
+
{
|
|
612
|
+
stackPointer = stack.length - 1;
|
|
613
|
+
}
|
|
614
|
+
|
|
566
615
|
manuallyAdjustingHistory = false;
|
|
616
|
+
|
|
617
|
+
resolve();
|
|
567
618
|
};
|
|
568
619
|
|
|
620
|
+
// see BUG comments at the top of this method
|
|
621
|
+
if(apply2ndFrameHack && backsToDo == 1)
|
|
622
|
+
{
|
|
623
|
+
window.history.replaceState({ uid: secondFrameHackUid }, defaultPage.title, defaultPageRouteResult.path);
|
|
624
|
+
document.title = defaultPage.title;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
backsToDo--;
|
|
569
628
|
history.back();
|
|
570
629
|
});
|
|
571
630
|
}
|
|
572
631
|
|
|
632
|
+
|
|
633
|
+
export function removeHistory(predicate)
|
|
634
|
+
{
|
|
635
|
+
let statesToKeep = [];
|
|
636
|
+
for (var i = 0; i < stack.length; i++)
|
|
637
|
+
{
|
|
638
|
+
if (!predicate(stack[i], i))
|
|
639
|
+
statesToKeep.push(stack[i]);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return modifyHistory(statesToKeep);
|
|
643
|
+
}
|
|
644
|
+
|
|
573
645
|
export function purgeCache() {
|
|
574
646
|
for (const key in pageCache)
|
|
575
647
|
{
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<html>
|
|
2
|
+
<head>
|
|
3
|
+
<script defer src="/main.js"></script></head>
|
|
4
|
+
<body style="height: 100%;">
|
|
5
|
+
<div style="position: fixed; top: 0; background-color: white;">
|
|
6
|
+
<a href="/page1">Page 1</a>
|
|
7
|
+
<a href="/page2">Page 2</a>
|
|
8
|
+
<a href="/page3">Page 3</a>
|
|
9
|
+
<a href="/page4">Page 4</a>
|
|
10
|
+
<a href="/page/A">Page A</a>
|
|
11
|
+
<a href="/page/B">Page B</a>
|
|
12
|
+
<a href="/404">404</a>
|
|
13
|
+
<a href="/dont-exist">I dont exist</a>
|
|
14
|
+
<a href="/show-fail">I fail to show</a>
|
|
15
|
+
</div>
|
|
16
|
+
<div>
|
|
17
|
+
<button class="btnStack">Print stack</button>
|
|
18
|
+
</div>
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|