@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trullock/page-manager",
3
- "version": "1.2.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 lastNavigationDirection = null;
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 route = router.parse(url);
71
- return loadPage(route, {});
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
- // TODO: this case may be buggy
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
- stack.pop();
250
- stack.push({ uid: currentUid, data, page });
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 (lastNavigationDirection == 'fwd')
364
+ if (beforeHideNavDirection == 'fwd')
363
365
  history.back();
364
- else if (lastNavigationDirection == 'back')
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 (lastNavigationDirection == 'fwd')
399
+ if (beforeHideNavDirection == 'fwd')
392
400
  history.forward();
393
- else if (lastNavigationDirection == 'back')
401
+ else if (beforeHideNavDirection == 'back')
394
402
  history.back();
395
- } else {
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 = handleBeforeHidePart1();
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
- export function removeHistory(predicate)
518
+ function modifyHistory(statesToKeep)
509
519
  {
510
- let statesToKeep = [];
511
- for (var i = 0; i < stack.length; i++)
512
- {
513
- if (!predicate(stack[i], i))
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
- return new Promise((resolve, reject) => {
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 }, null, currentState.data.route.url);
573
+ window.history.replaceState({ uid: currentState.uid }, currentState.page.title, currentState.data.route.url);
551
574
  else
552
- window.history.pushState({ uid: currentState.uid }, null, currentState.data.route.url);
575
+ window.history.pushState({ uid: currentState.uid }, currentState.page.title, currentState.data.route.url);
553
576
 
554
- window.dispatchEvent(new CustomEvent('page-manager.url-changed', {
555
- url: currentState.data.route.url
556
- }))
577
+ emitUrlChanged(currentState.data.route.url)
557
578
 
558
- // TODO: this doesnt seem to work when k=0
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
- stackPointer = stack.length - 1;
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>
@@ -0,0 +1,3 @@
1
+ <div>
2
+ Loading
3
+ </div>