@trullock/page-manager 1.2.1 → 1.3.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/src/index.js +116 -59
- package/tests/dist/index.html +20 -0
- package/tests/dist/loading.htm +3 -0
- package/tests/dist/main.js +1648 -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.0",
|
|
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,7 +220,7 @@ async function doUpdate(page, data) {
|
|
|
222
220
|
document.title = page.title
|
|
223
221
|
}
|
|
224
222
|
|
|
225
|
-
function handleHistoryAction(event, url, data, page) {
|
|
223
|
+
async function handleHistoryAction(event, url, data, page) {
|
|
226
224
|
if (event.action == 'push') {
|
|
227
225
|
let newUid = stack[stackPointer].uid + 1;
|
|
228
226
|
|
|
@@ -235,19 +233,22 @@ function handleHistoryAction(event, url, data, page) {
|
|
|
235
233
|
stack.push({ uid: newUid, data, page });
|
|
236
234
|
stackPointer++;
|
|
237
235
|
}
|
|
238
|
-
else if (event.action == 'replace')
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
// BUG: you can replace the current state with the same url as the previous state, which shouldnt be allowed,
|
|
242
|
-
// you cant have the same url in the history twice (next to each other).
|
|
243
|
-
// Update this to check for such a case and handle it
|
|
244
|
-
|
|
236
|
+
else if (event.action == 'replace')
|
|
237
|
+
{
|
|
245
238
|
let currentUid = stack[stackPointer].uid;
|
|
246
239
|
window.history.replaceState({ uid: currentUid }, null, url);
|
|
247
240
|
emitUrlChanged(url);
|
|
241
|
+
stack[stackPointer] = { uid: currentUid, data, page };
|
|
242
|
+
|
|
243
|
+
if(stack[stackPointer].data.route.url == stack[stackPointer - 1]?.data?.route?.url)
|
|
244
|
+
{
|
|
245
|
+
let statesToKeep = [];
|
|
246
|
+
for (var i = 0; i < stackPointer - 1; i++)
|
|
247
|
+
statesToKeep.push(stack[i]);
|
|
248
|
+
statesToKeep.push(stack[stackPointer]);
|
|
248
249
|
|
|
249
|
-
|
|
250
|
-
|
|
250
|
+
await modifyHistory(statesToKeep);
|
|
251
|
+
}
|
|
251
252
|
}
|
|
252
253
|
else if(event.action == 'back')
|
|
253
254
|
{
|
|
@@ -299,7 +300,6 @@ export async function init(opts) {
|
|
|
299
300
|
}
|
|
300
301
|
|
|
301
302
|
await initLoading();
|
|
302
|
-
|
|
303
303
|
await showLoading();
|
|
304
304
|
}
|
|
305
305
|
|
|
@@ -312,18 +312,18 @@ export async function ready()
|
|
|
312
312
|
|
|
313
313
|
if (e instanceof PageShowError)
|
|
314
314
|
{
|
|
315
|
-
return showPage(e.url, e.data, { action: 'load', distance: 0 }).then(page => {
|
|
315
|
+
return showPage(e.url, e.data, { action: 'load', distance: 0 }).then(async page => {
|
|
316
316
|
if(e.action == 'replace')
|
|
317
|
-
handleHistoryAction({ action: e.action }, e.url, e.data, page);
|
|
317
|
+
await handleHistoryAction({ action: e.action }, e.url, e.data, page);
|
|
318
318
|
return page;
|
|
319
319
|
});
|
|
320
320
|
}
|
|
321
321
|
});
|
|
322
322
|
|
|
323
|
-
function handlePopstate(context, direction, distance) {
|
|
323
|
+
async function handlePopstate(context, direction, distance) {
|
|
324
324
|
|
|
325
325
|
if (manuallyAdjustingHistory) {
|
|
326
|
-
manuallyAdjustingHistory(context, { action: direction, distance });
|
|
326
|
+
await manuallyAdjustingHistory(context, { action: direction, distance });
|
|
327
327
|
return;
|
|
328
328
|
}
|
|
329
329
|
|
|
@@ -343,7 +343,7 @@ export async function ready()
|
|
|
343
343
|
})
|
|
344
344
|
}
|
|
345
345
|
|
|
346
|
-
function handleBeforeHidePart1() {
|
|
346
|
+
function handleBeforeHidePart1(lastNavDirection) {
|
|
347
347
|
// if we're ignoring beforeHide this navigation
|
|
348
348
|
if (handlingBeforeHide === 'ignore') {
|
|
349
349
|
handlingBeforeHide = false;
|
|
@@ -352,6 +352,8 @@ export async function ready()
|
|
|
352
352
|
|
|
353
353
|
// if we have a before-unload confirm to show
|
|
354
354
|
if (stack[stackPointer].page.beforeHide && options.beforeHide && handlingBeforeHide === false) {
|
|
355
|
+
beforeHideNavDirection = lastNavDirection;
|
|
356
|
+
|
|
355
357
|
var interrupt = stack[stackPointer].page.beforeHide();
|
|
356
358
|
if (interrupt) {
|
|
357
359
|
handlingBeforeHide = 'step1';
|
|
@@ -359,9 +361,9 @@ export async function ready()
|
|
|
359
361
|
// do this in a new thread, you cant call history actions from inside a history-aciton-handler
|
|
360
362
|
window.setTimeout(() => {
|
|
361
363
|
// undo the navigation so the URL remains correct whilst we show the confirm dialog
|
|
362
|
-
if (
|
|
364
|
+
if (beforeHideNavDirection == 'fwd')
|
|
363
365
|
history.back();
|
|
364
|
-
else if (
|
|
366
|
+
else if (beforeHideNavDirection == 'back')
|
|
365
367
|
history.forward();
|
|
366
368
|
}, 1);
|
|
367
369
|
|
|
@@ -371,7 +373,10 @@ export async function ready()
|
|
|
371
373
|
|
|
372
374
|
// we've finished beforeHiding
|
|
373
375
|
if (handlingBeforeHide === 'step2')
|
|
376
|
+
{
|
|
374
377
|
handlingBeforeHide = false;
|
|
378
|
+
beforeHideNavDirection = null;
|
|
379
|
+
}
|
|
375
380
|
|
|
376
381
|
return false;
|
|
377
382
|
}
|
|
@@ -380,20 +385,26 @@ export async function ready()
|
|
|
380
385
|
if (handlingBeforeHide !== 'step1')
|
|
381
386
|
return false;
|
|
382
387
|
|
|
388
|
+
if(manuallyAdjustingHistory)
|
|
389
|
+
return false;
|
|
390
|
+
|
|
383
391
|
// do the beforeHide action, then...
|
|
384
392
|
options.beforeHide(stack[stackPointer].page.beforeHide()).then(result => {
|
|
385
393
|
|
|
386
394
|
// if the user confirmed, redo the original action
|
|
387
|
-
if (result)
|
|
388
|
-
|
|
395
|
+
if (result)
|
|
396
|
+
{
|
|
389
397
|
handlingBeforeHide = 'step2';
|
|
390
398
|
|
|
391
|
-
if (
|
|
399
|
+
if (beforeHideNavDirection == 'fwd')
|
|
392
400
|
history.forward();
|
|
393
|
-
else if (
|
|
401
|
+
else if (beforeHideNavDirection == 'back')
|
|
394
402
|
history.back();
|
|
395
|
-
}
|
|
403
|
+
}
|
|
404
|
+
else
|
|
405
|
+
{
|
|
396
406
|
handlingBeforeHide = false;
|
|
407
|
+
beforeHideNavDirection = null;
|
|
397
408
|
}
|
|
398
409
|
});
|
|
399
410
|
|
|
@@ -401,23 +412,22 @@ export async function ready()
|
|
|
401
412
|
}
|
|
402
413
|
|
|
403
414
|
// listen for browser navigations
|
|
404
|
-
window.addEventListener("popstate", e => {
|
|
405
|
-
var interrupted = handleBeforeHidePart2();
|
|
406
|
-
if (interrupted)
|
|
407
|
-
return;
|
|
408
|
-
|
|
415
|
+
window.addEventListener("popstate", async e => {
|
|
409
416
|
let newUid = e.state?.uid || 0;
|
|
410
417
|
let previousUid = stack[stackPointer].uid;
|
|
411
|
-
|
|
412
|
-
lastNavigationDirection = newUid > previousUid ? 'fwd' : 'back';
|
|
418
|
+
let lastNavigationDirection = newUid > previousUid ? 'fwd' : 'back';
|
|
413
419
|
let distance = Math.abs(newUid - previousUid);
|
|
414
420
|
|
|
415
|
-
var interrupted =
|
|
421
|
+
var interrupted = handleBeforeHidePart2();
|
|
422
|
+
if (interrupted)
|
|
423
|
+
return;
|
|
424
|
+
|
|
425
|
+
var interrupted = handleBeforeHidePart1(lastNavigationDirection);
|
|
416
426
|
if (interrupted)
|
|
417
427
|
return;
|
|
418
428
|
|
|
419
429
|
var context = findContext(newUid);
|
|
420
|
-
handlePopstate(context, lastNavigationDirection, distance);
|
|
430
|
+
await handlePopstate(context, lastNavigationDirection, distance);
|
|
421
431
|
});
|
|
422
432
|
|
|
423
433
|
if(storageAvailable())
|
|
@@ -505,34 +515,46 @@ export function printStack() {
|
|
|
505
515
|
console.log(stack[i]);
|
|
506
516
|
}
|
|
507
517
|
|
|
508
|
-
|
|
518
|
+
function modifyHistory(statesToKeep)
|
|
509
519
|
{
|
|
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?
|
|
520
|
+
// 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)
|
|
521
|
+
// For clarity, you can remove (replace) frame 0 (1st), and frame 2+ (3rd+)
|
|
522
|
+
// As a workaround, we replace the 2nd stack frame, if it is the tail, with the root "/".
|
|
523
|
+
// This at least hopefully removes any meaning from it, simply taking you "home" rather than to wherever you were likely trying to remove.
|
|
518
524
|
|
|
519
525
|
if (statesToKeep.length == stack.length)
|
|
520
526
|
return Promise.resolve();
|
|
521
527
|
|
|
528
|
+
return new Promise(async (resolve, reject) => {
|
|
522
529
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
let backsToDo = stackPointer - 1;
|
|
530
|
+
let backsToDo = stackPointer;
|
|
527
531
|
let currentUid = -1;
|
|
528
532
|
|
|
529
533
|
// TODO: handle stack pointer not being at the tail when this process starts
|
|
534
|
+
|
|
535
|
+
// see BUG comments at the top of this method
|
|
536
|
+
let apply2ndFrameHack = statesToKeep.length == 1 && stack.length > 1
|
|
537
|
+
let defaultPageRouteResult = null;
|
|
538
|
+
let defaultPage = null;
|
|
539
|
+
if(apply2ndFrameHack)
|
|
540
|
+
{
|
|
541
|
+
let url = getPath(options.defaultPageName);
|
|
542
|
+
defaultPageRouteResult = router.parse(url);
|
|
543
|
+
defaultPage = await loadPage(defaultPageRouteResult, {});
|
|
544
|
+
}
|
|
530
545
|
|
|
531
|
-
manuallyAdjustingHistory = _ => {
|
|
546
|
+
manuallyAdjustingHistory = async _ => {
|
|
532
547
|
// rewind to the first history position
|
|
533
548
|
if(backsToDo > 0)
|
|
534
549
|
{
|
|
535
550
|
window.setTimeout(() => {
|
|
551
|
+
// see BUG comments at the top of this method
|
|
552
|
+
if(apply2ndFrameHack && backsToDo == 1)
|
|
553
|
+
{
|
|
554
|
+
window.history.replaceState({ uid: stack[1].uid }, defaultPage.title, defaultPageRouteResult.path);
|
|
555
|
+
document.title = defaultPage.title;
|
|
556
|
+
}
|
|
557
|
+
|
|
536
558
|
backsToDo--;
|
|
537
559
|
history.back();
|
|
538
560
|
}, 1);
|
|
@@ -540,6 +562,7 @@ export function removeHistory(predicate)
|
|
|
540
562
|
}
|
|
541
563
|
|
|
542
564
|
// reset the stack
|
|
565
|
+
|
|
543
566
|
stack = [];
|
|
544
567
|
|
|
545
568
|
for (var k = 0; k < statesToKeep.length; k++) {
|
|
@@ -547,29 +570,63 @@ export function removeHistory(predicate)
|
|
|
547
570
|
currentState.uid = ++currentUid;
|
|
548
571
|
|
|
549
572
|
if (k == 0)
|
|
550
|
-
window.history.replaceState({ uid: currentState.uid },
|
|
573
|
+
window.history.replaceState({ uid: currentState.uid }, currentState.page.title, currentState.data.route.url);
|
|
551
574
|
else
|
|
552
|
-
window.history.pushState({ uid: currentState.uid },
|
|
575
|
+
window.history.pushState({ uid: currentState.uid }, currentState.page.title, currentState.data.route.url);
|
|
553
576
|
|
|
554
|
-
|
|
555
|
-
url: currentState.data.route.url
|
|
556
|
-
}))
|
|
577
|
+
emitUrlChanged(currentState.data.route.url)
|
|
557
578
|
|
|
558
|
-
|
|
579
|
+
// TODO: this doesnt seem to work when k=0
|
|
559
580
|
document.title = currentState.page.title;
|
|
560
581
|
|
|
561
582
|
stack.push(currentState);
|
|
562
583
|
}
|
|
563
584
|
|
|
564
|
-
|
|
565
|
-
|
|
585
|
+
// see BUG comments at the top of this method
|
|
586
|
+
if(apply2ndFrameHack)
|
|
587
|
+
{
|
|
588
|
+
stack.push({
|
|
589
|
+
uid: 1,
|
|
590
|
+
data: {
|
|
591
|
+
route: {
|
|
592
|
+
url: defaultPageRouteResult.path,
|
|
593
|
+
path: defaultPageRouteResult.path,
|
|
594
|
+
name: defaultPageRouteResult.name,
|
|
595
|
+
params: defaultPageRouteResult.params
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
page: defaultPage
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
stackPointer = 0;
|
|
602
|
+
}
|
|
603
|
+
else
|
|
604
|
+
{
|
|
605
|
+
stackPointer = stack.length - 1;
|
|
606
|
+
}
|
|
607
|
+
|
|
566
608
|
manuallyAdjustingHistory = false;
|
|
609
|
+
|
|
610
|
+
resolve();
|
|
567
611
|
};
|
|
568
612
|
|
|
569
613
|
history.back();
|
|
570
614
|
});
|
|
571
615
|
}
|
|
572
616
|
|
|
617
|
+
|
|
618
|
+
export function removeHistory(predicate)
|
|
619
|
+
{
|
|
620
|
+
let statesToKeep = [];
|
|
621
|
+
for (var i = 0; i < stack.length; i++)
|
|
622
|
+
{
|
|
623
|
+
if (!predicate(stack[i], i))
|
|
624
|
+
statesToKeep.push(stack[i]);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return modifyHistory(statesToKeep);
|
|
628
|
+
}
|
|
629
|
+
|
|
573
630
|
export function purgeCache() {
|
|
574
631
|
for (const key in pageCache)
|
|
575
632
|
{
|
|
@@ -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>
|