@trullock/page-manager 1.2.0 → 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 +119 -60
- 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,16 +25,16 @@ 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'
|
|
29
|
+
let extra = opts.extra || {}
|
|
30
30
|
|
|
31
31
|
for (const [name, route] of Object.entries(namedRoutes)) {
|
|
32
32
|
router.addRoute(name, route, {
|
|
33
33
|
route,
|
|
34
|
-
auth,
|
|
35
34
|
loadPageClass,
|
|
36
35
|
loadMarkup: opts.loadMarkup,
|
|
37
|
-
cacheMarkupBy
|
|
36
|
+
cacheMarkupBy,
|
|
37
|
+
extra
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -65,8 +65,8 @@ function emitUrlChanged(url)
|
|
|
65
65
|
function initLoading()
|
|
66
66
|
{
|
|
67
67
|
var url = router.interpolate(options.loadingPageName, {});
|
|
68
|
-
var
|
|
69
|
-
return loadPage(
|
|
68
|
+
var routeResult = router.parse(url);
|
|
69
|
+
return loadPage(routeResult, {});
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
function showLoading() {
|
|
@@ -220,7 +220,7 @@ async function doUpdate(page, data) {
|
|
|
220
220
|
document.title = page.title
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
function handleHistoryAction(event, url, data, page) {
|
|
223
|
+
async function handleHistoryAction(event, url, data, page) {
|
|
224
224
|
if (event.action == 'push') {
|
|
225
225
|
let newUid = stack[stackPointer].uid + 1;
|
|
226
226
|
|
|
@@ -233,19 +233,22 @@ function handleHistoryAction(event, url, data, page) {
|
|
|
233
233
|
stack.push({ uid: newUid, data, page });
|
|
234
234
|
stackPointer++;
|
|
235
235
|
}
|
|
236
|
-
else if (event.action == 'replace')
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
// BUG: you can replace the current state with the same url as the previous state, which shouldnt be allowed,
|
|
240
|
-
// you cant have the same url in the history twice (next to each other).
|
|
241
|
-
// Update this to check for such a case and handle it
|
|
242
|
-
|
|
236
|
+
else if (event.action == 'replace')
|
|
237
|
+
{
|
|
243
238
|
let currentUid = stack[stackPointer].uid;
|
|
244
239
|
window.history.replaceState({ uid: currentUid }, null, url);
|
|
245
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]);
|
|
246
249
|
|
|
247
|
-
|
|
248
|
-
|
|
250
|
+
await modifyHistory(statesToKeep);
|
|
251
|
+
}
|
|
249
252
|
}
|
|
250
253
|
else if(event.action == 'back')
|
|
251
254
|
{
|
|
@@ -297,7 +300,6 @@ export async function init(opts) {
|
|
|
297
300
|
}
|
|
298
301
|
|
|
299
302
|
await initLoading();
|
|
300
|
-
|
|
301
303
|
await showLoading();
|
|
302
304
|
}
|
|
303
305
|
|
|
@@ -310,18 +312,18 @@ export async function ready()
|
|
|
310
312
|
|
|
311
313
|
if (e instanceof PageShowError)
|
|
312
314
|
{
|
|
313
|
-
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 => {
|
|
314
316
|
if(e.action == 'replace')
|
|
315
|
-
handleHistoryAction({ action: e.action }, e.url, e.data, page);
|
|
317
|
+
await handleHistoryAction({ action: e.action }, e.url, e.data, page);
|
|
316
318
|
return page;
|
|
317
319
|
});
|
|
318
320
|
}
|
|
319
321
|
});
|
|
320
322
|
|
|
321
|
-
function handlePopstate(context, direction, distance) {
|
|
323
|
+
async function handlePopstate(context, direction, distance) {
|
|
322
324
|
|
|
323
325
|
if (manuallyAdjustingHistory) {
|
|
324
|
-
manuallyAdjustingHistory(context, { action: direction, distance });
|
|
326
|
+
await manuallyAdjustingHistory(context, { action: direction, distance });
|
|
325
327
|
return;
|
|
326
328
|
}
|
|
327
329
|
|
|
@@ -341,7 +343,7 @@ export async function ready()
|
|
|
341
343
|
})
|
|
342
344
|
}
|
|
343
345
|
|
|
344
|
-
function handleBeforeHidePart1() {
|
|
346
|
+
function handleBeforeHidePart1(lastNavDirection) {
|
|
345
347
|
// if we're ignoring beforeHide this navigation
|
|
346
348
|
if (handlingBeforeHide === 'ignore') {
|
|
347
349
|
handlingBeforeHide = false;
|
|
@@ -350,6 +352,8 @@ export async function ready()
|
|
|
350
352
|
|
|
351
353
|
// if we have a before-unload confirm to show
|
|
352
354
|
if (stack[stackPointer].page.beforeHide && options.beforeHide && handlingBeforeHide === false) {
|
|
355
|
+
beforeHideNavDirection = lastNavDirection;
|
|
356
|
+
|
|
353
357
|
var interrupt = stack[stackPointer].page.beforeHide();
|
|
354
358
|
if (interrupt) {
|
|
355
359
|
handlingBeforeHide = 'step1';
|
|
@@ -357,9 +361,9 @@ export async function ready()
|
|
|
357
361
|
// do this in a new thread, you cant call history actions from inside a history-aciton-handler
|
|
358
362
|
window.setTimeout(() => {
|
|
359
363
|
// undo the navigation so the URL remains correct whilst we show the confirm dialog
|
|
360
|
-
if (
|
|
364
|
+
if (beforeHideNavDirection == 'fwd')
|
|
361
365
|
history.back();
|
|
362
|
-
else if (
|
|
366
|
+
else if (beforeHideNavDirection == 'back')
|
|
363
367
|
history.forward();
|
|
364
368
|
}, 1);
|
|
365
369
|
|
|
@@ -369,7 +373,10 @@ export async function ready()
|
|
|
369
373
|
|
|
370
374
|
// we've finished beforeHiding
|
|
371
375
|
if (handlingBeforeHide === 'step2')
|
|
376
|
+
{
|
|
372
377
|
handlingBeforeHide = false;
|
|
378
|
+
beforeHideNavDirection = null;
|
|
379
|
+
}
|
|
373
380
|
|
|
374
381
|
return false;
|
|
375
382
|
}
|
|
@@ -378,20 +385,26 @@ export async function ready()
|
|
|
378
385
|
if (handlingBeforeHide !== 'step1')
|
|
379
386
|
return false;
|
|
380
387
|
|
|
388
|
+
if(manuallyAdjustingHistory)
|
|
389
|
+
return false;
|
|
390
|
+
|
|
381
391
|
// do the beforeHide action, then...
|
|
382
392
|
options.beforeHide(stack[stackPointer].page.beforeHide()).then(result => {
|
|
383
393
|
|
|
384
394
|
// if the user confirmed, redo the original action
|
|
385
|
-
if (result)
|
|
386
|
-
|
|
395
|
+
if (result)
|
|
396
|
+
{
|
|
387
397
|
handlingBeforeHide = 'step2';
|
|
388
398
|
|
|
389
|
-
if (
|
|
399
|
+
if (beforeHideNavDirection == 'fwd')
|
|
390
400
|
history.forward();
|
|
391
|
-
else if (
|
|
401
|
+
else if (beforeHideNavDirection == 'back')
|
|
392
402
|
history.back();
|
|
393
|
-
}
|
|
403
|
+
}
|
|
404
|
+
else
|
|
405
|
+
{
|
|
394
406
|
handlingBeforeHide = false;
|
|
407
|
+
beforeHideNavDirection = null;
|
|
395
408
|
}
|
|
396
409
|
});
|
|
397
410
|
|
|
@@ -399,23 +412,22 @@ export async function ready()
|
|
|
399
412
|
}
|
|
400
413
|
|
|
401
414
|
// listen for browser navigations
|
|
402
|
-
window.addEventListener("popstate", e => {
|
|
403
|
-
var interrupted = handleBeforeHidePart2();
|
|
404
|
-
if (interrupted)
|
|
405
|
-
return;
|
|
406
|
-
|
|
415
|
+
window.addEventListener("popstate", async e => {
|
|
407
416
|
let newUid = e.state?.uid || 0;
|
|
408
417
|
let previousUid = stack[stackPointer].uid;
|
|
409
|
-
|
|
410
|
-
lastNavigationDirection = newUid > previousUid ? 'fwd' : 'back';
|
|
418
|
+
let lastNavigationDirection = newUid > previousUid ? 'fwd' : 'back';
|
|
411
419
|
let distance = Math.abs(newUid - previousUid);
|
|
412
420
|
|
|
413
|
-
var interrupted =
|
|
421
|
+
var interrupted = handleBeforeHidePart2();
|
|
422
|
+
if (interrupted)
|
|
423
|
+
return;
|
|
424
|
+
|
|
425
|
+
var interrupted = handleBeforeHidePart1(lastNavigationDirection);
|
|
414
426
|
if (interrupted)
|
|
415
427
|
return;
|
|
416
428
|
|
|
417
429
|
var context = findContext(newUid);
|
|
418
|
-
handlePopstate(context, lastNavigationDirection, distance);
|
|
430
|
+
await handlePopstate(context, lastNavigationDirection, distance);
|
|
419
431
|
});
|
|
420
432
|
|
|
421
433
|
if(storageAvailable())
|
|
@@ -503,34 +515,46 @@ export function printStack() {
|
|
|
503
515
|
console.log(stack[i]);
|
|
504
516
|
}
|
|
505
517
|
|
|
506
|
-
|
|
518
|
+
function modifyHistory(statesToKeep)
|
|
507
519
|
{
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
statesToKeep.push(stack[i]);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
// 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.
|
|
516
524
|
|
|
517
525
|
if (statesToKeep.length == stack.length)
|
|
518
526
|
return Promise.resolve();
|
|
519
527
|
|
|
528
|
+
return new Promise(async (resolve, reject) => {
|
|
520
529
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
let backsToDo = stackPointer - 1;
|
|
530
|
+
let backsToDo = stackPointer;
|
|
525
531
|
let currentUid = -1;
|
|
526
532
|
|
|
527
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
|
+
}
|
|
528
545
|
|
|
529
|
-
manuallyAdjustingHistory = _ => {
|
|
546
|
+
manuallyAdjustingHistory = async _ => {
|
|
530
547
|
// rewind to the first history position
|
|
531
548
|
if(backsToDo > 0)
|
|
532
549
|
{
|
|
533
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
|
+
|
|
534
558
|
backsToDo--;
|
|
535
559
|
history.back();
|
|
536
560
|
}, 1);
|
|
@@ -538,6 +562,7 @@ export function removeHistory(predicate)
|
|
|
538
562
|
}
|
|
539
563
|
|
|
540
564
|
// reset the stack
|
|
565
|
+
|
|
541
566
|
stack = [];
|
|
542
567
|
|
|
543
568
|
for (var k = 0; k < statesToKeep.length; k++) {
|
|
@@ -545,29 +570,63 @@ export function removeHistory(predicate)
|
|
|
545
570
|
currentState.uid = ++currentUid;
|
|
546
571
|
|
|
547
572
|
if (k == 0)
|
|
548
|
-
window.history.replaceState({ uid: currentState.uid },
|
|
573
|
+
window.history.replaceState({ uid: currentState.uid }, currentState.page.title, currentState.data.route.url);
|
|
549
574
|
else
|
|
550
|
-
window.history.pushState({ uid: currentState.uid },
|
|
575
|
+
window.history.pushState({ uid: currentState.uid }, currentState.page.title, currentState.data.route.url);
|
|
551
576
|
|
|
552
|
-
|
|
553
|
-
url: currentState.data.route.url
|
|
554
|
-
}))
|
|
577
|
+
emitUrlChanged(currentState.data.route.url)
|
|
555
578
|
|
|
556
|
-
|
|
579
|
+
// TODO: this doesnt seem to work when k=0
|
|
557
580
|
document.title = currentState.page.title;
|
|
558
581
|
|
|
559
582
|
stack.push(currentState);
|
|
560
583
|
}
|
|
561
584
|
|
|
562
|
-
|
|
563
|
-
|
|
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
|
+
|
|
564
608
|
manuallyAdjustingHistory = false;
|
|
609
|
+
|
|
610
|
+
resolve();
|
|
565
611
|
};
|
|
566
612
|
|
|
567
613
|
history.back();
|
|
568
614
|
});
|
|
569
615
|
}
|
|
570
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
|
+
|
|
571
630
|
export function purgeCache() {
|
|
572
631
|
for (const key in pageCache)
|
|
573
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>
|