SPAC: Web APIs for Page Transitions & Persistence

Persisting State

Web Storage API

  • setItem(key, value) - Sets a new item with the given key and value
  • getItem(key) - Retrieves the stored value for the given key
  • removeItem(key) - Removes the value for the given key
  • key(int) - Retrieves the stored value for the given integer values
  • clear() - Removes all keys and values ...

SPAC Implementation

class StorageProviderInterface extends Interface(
'init',
'load',
'persist',
'clear'
) {}
  • init(): Initialize the storage structure, e.g. setup database tables or define the JSON structure
  • load(): Load the persisted data from the configured store
  • persist(): Persists the data to the configured store
  • clear(): Deletes all data
class BrowserStorage extends StorageProviderInterface {
constructor (appName) {
super()
this.appName = appName
}
init () {} persist (appState) {
document.localStorage.setItem(
this.appName,
JSON.stringify({ ...this.load(), ...appState })
)
}
load () {
return JSON.parse(document.localStorage.getItem(this.appName))
}
clear () {
document.localStorage.clear()
}
}

Router

Web Apis

  • pushState(state, title [, url]): The new URL can be any domain within the current domain, and it even does not need to change at all.
  • replaceState(stateObj, title, [url]): Replaces the current state, and changes the browser location to the specified URL (weird: There is no check that this URL actually exists)
  • back(): Go back one step in the history
  • forward(): Go forward one step in the history
  • go(int): Go to the specified index in the history

Router Requirements

  • Define an inventory of all internal routes
  • Resolve those routes and update the displayed DOM to match the current route
  • Intercept all location changes that point to defined routes (preventing a page reload)
  • Not intercept calls to internal resources (css, non-framework related js, images)
  • Not intercept location changes to external resources (but probably warn the user)

SPAC Implementation

pages = {
'Index' => {
route: '/index',
obj: [Function: IndexPage]
},
'SearchApiElements' => {
route: '/search-api-elements',
obj: [Function: SearchApiElementsPage]
}
}
goTo (url) {
const isLocalURL = url.match('^/') ? true : false
const isInternalURL = url.match(new RegExp(this.baseHostname)) ? true : false
const isRessourceFile = url.match(/[.]\w+$/) ? true : false
if (!isRessourceFile && (isLocalURL || isInternalURL)) {
isInternalURL ? (url = `/` + url.split('/').pop()) : ''
let loadablePage = false
this.pages.forEach((obj, key) => {
obj.route == url ? (loadablePage = key) : ''
})
if (loadablePage) {
window.history.pushState({ emptyState: 'empty' }, `Change to ${url}`, url)
this.display(loadablePage)
} else {
console.error(`Route ${url} not defined, staying on current page`)
}
} else if (isRessourceFile) {
window.history.pushState({ application: 'exit' }, 'Exiting App', this.baseHostname)
window.location.replace(url)
} else {
window.history.pushState({ application: 'exit' }, 'Exiting App', this.baseHostname)
window.location.replace(url)
}
}
  • Line 2 & 3: Determine if the URL is either a local path or if it includes the hostname
  • If either condition is true, then …
  • =>Determine the route
  • => Check that for this route, there is an entry in the pages map
  • =>If yes, make an entry on the history and mount the page
  • =>If no, log an error but stay on the current page
  • if the URL points to a file, load it
  • If none of the above conditions is true, then
  • =>make a final entry to the History API
  • =>assign a new window.location, forcing a page reload
class Controller() {
/...
_interceptLinkResolutions () {
window.addEventListener('load', () => {
window.document.querySelectorAll('a').forEach(link => {
link.addEventListener('click', e => {
e.preventDefault()
goTo(e.target.href)
})
})
})
}
}
class Controller() {
//...
interceptPageRefresh() {
window.addEventListener('beforeunload', (e) => {
e.preventDefault();
e.returnValue = ''
if (window.confirm('The page will be reloaded. All session date is delete. Continue?')) {
return;
}
})
})
}

Conclusion

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store