index.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import { transition } from "alpinejs/src/directives/x-transition"
  2. import { finishAndHideProgressBar, showAndStartProgressBar } from "./bar"
  3. import { fetchHtml } from "./fetch"
  4. import { updateCurrentPageHtmlInHistoryStateForLaterBackButtonClicks, updateUrlAndStoreLatestHtmlForFutureBackButtons, whenTheBackOrForwardButtonIsClicked } from "./history"
  5. import { extractDestinationFromLink, hijackNewLinksOnThePage, whenALinkIsClicked, whenALinkIsHovered } from "./links"
  6. import { swapCurrentPageWithNewHtml } from "./page"
  7. import { putPersistantElementsBack, storePersistantElementsForLater } from "./persist"
  8. import { getPretchedHtmlOr, prefetchHtml, storeThePrefetchedHtmlForWhenALinkIsClicked } from "./prefetch"
  9. import { restoreScrollPosition, storeScrollInformationInHtmlBeforeNavigatingAway } from "./scroll"
  10. let enablePrefetch = true
  11. let enablePersist = true
  12. let showProgressBar = false
  13. let restoreScroll = true
  14. let autofocus = false
  15. export default function (Alpine) {
  16. updateCurrentPageHtmlInHistoryStateForLaterBackButtonClicks()
  17. enablePrefetch && whenALinkIsHovered((el) => {
  18. let forDestination = extractDestinationFromLink(el)
  19. prefetchHtml(forDestination, html => {
  20. storeThePrefetchedHtmlForWhenALinkIsClicked(html, forDestination)
  21. })
  22. })
  23. whenALinkIsClicked((el) => {
  24. showProgressBar && showAndStartProgressBar()
  25. let fromDestination = extractDestinationFromLink(el)
  26. fetchHtmlOrUsePrefetchedHtml(fromDestination, html => {
  27. restoreScroll && storeScrollInformationInHtmlBeforeNavigatingAway()
  28. updateCurrentPageHtmlInHistoryStateForLaterBackButtonClicks()
  29. showProgressBar && finishAndHideProgressBar()
  30. preventAlpineFromPickingUpDomChanges(Alpine, andAfterAllThis => {
  31. enablePersist && storePersistantElementsForLater()
  32. swapCurrentPageWithNewHtml(html, () => {
  33. enablePersist && putPersistantElementsBack()
  34. hijackNewLinksOnThePage()
  35. restoreScroll && restoreScrollPosition()
  36. fireEventForOtherLibariesToHookInto()
  37. updateUrlAndStoreLatestHtmlForFutureBackButtons(html, fromDestination)
  38. andAfterAllThis(() => {
  39. autofocus && autofocusElementsWithTheAutofocusAttribute()
  40. nowInitializeAlpineOnTheNewPage(Alpine)
  41. })
  42. })
  43. })
  44. })
  45. })
  46. whenTheBackOrForwardButtonIsClicked((html) => {
  47. // @todo: see if there's a way to update the current HTML BEFORE
  48. // the back button is hit, and not AFTER:
  49. storeScrollInformationInHtmlBeforeNavigatingAway()
  50. // updateCurrentPageHtmlInHistoryStateForLaterBackButtonClicks()
  51. preventAlpineFromPickingUpDomChanges(Alpine, andAfterAllThis => {
  52. enablePersist && storePersistantElementsForLater()
  53. swapCurrentPageWithNewHtml(html, andThen => {
  54. enablePersist && putPersistantElementsBack()
  55. hijackNewLinksOnThePage()
  56. restoreScroll && restoreScrollPosition()
  57. fireEventForOtherLibariesToHookInto()
  58. andAfterAllThis(() => {
  59. autofocus && autofocusElementsWithTheAutofocusAttribute()
  60. nowInitializeAlpineOnTheNewPage(Alpine)
  61. })
  62. })
  63. })
  64. })
  65. }
  66. function fetchHtmlOrUsePrefetchedHtml(fromDestination, callback) {
  67. getPretchedHtmlOr(fromDestination, callback, () => {
  68. fetchHtml(fromDestination, callback)
  69. })
  70. }
  71. function preventAlpineFromPickingUpDomChanges(Alpine, callback) {
  72. Alpine.stopObservingMutations()
  73. callback((afterAllThis) => {
  74. Alpine.startObservingMutations()
  75. setTimeout(() => {
  76. afterAllThis()
  77. })
  78. })
  79. }
  80. function fireEventForOtherLibariesToHookInto() {
  81. document.dispatchEvent(new CustomEvent('alpine:navigated', { bubbles: true }))
  82. }
  83. function nowInitializeAlpineOnTheNewPage(Alpine) {
  84. Alpine.initTree(document.body, undefined, (el, skip) => {
  85. if (el._x_wasPersisted) skip()
  86. })
  87. }
  88. function autofocusElementsWithTheAutofocusAttribute() {
  89. document.querySelector('[autofocus]') && document.querySelector('[autofocus]').focus()
  90. }
  91. // Alpine.magic('history', (el, { interceptor }) => {
  92. // let alias
  93. // return interceptor((initialValue, getter, setter, path, key) => {
  94. // let pause = false
  95. // let queryKey = alias || path
  96. // let value = initialValue
  97. // let url = new URL(window.location.href)
  98. // if (url.searchParams.has(queryKey)) {
  99. // value = url.searchParams.get(queryKey)
  100. // }
  101. // setter(value)
  102. // let object = { value }
  103. // url.searchParams.set(queryKey, value)
  104. // replace(url.toString(), path, object)
  105. // window.addEventListener('popstate', (e) => {
  106. // if (! e.state) return
  107. // if (! e.state.alpine) return
  108. // Object.entries(e.state.alpine).forEach(([newKey, { value }]) => {
  109. // if (newKey !== key) return
  110. // pause = true
  111. // Alpine.disableEffectScheduling(() => {
  112. // setter(value)
  113. // })
  114. // pause = false
  115. // })
  116. // })
  117. // Alpine.effect(() => {
  118. // let value = getter()
  119. // if (pause) return
  120. // let object = { value }
  121. // let url = new URL(window.location.href)
  122. // url.searchParams.set(queryKey, value)
  123. // push(url.toString(), path, object)
  124. // })
  125. // return value
  126. // }, func => {
  127. // func.as = key => { alias = key; return func }
  128. // })
  129. // })
  130. // }
  131. // function replace(url, key, object) {
  132. // let state = window.history.state || {}
  133. // if (! state.alpine) state.alpine = {}
  134. // state.alpine[key] = object
  135. // window.history.replaceState(state, '', url)
  136. // }
  137. // function push(url, key, object) {
  138. // let state = { alpine: {...window.history.state.alpine, ...{[key]: object}} }
  139. // window.history.pushState(state, '', url)
  140. // }