import { createEvents } from '@/core/modules/createEvents'
import eventBus from '@/core/modules/eventBus'
import { composeProps } from '@/core/modules/refs'
import { preventClick, activeLinks, localLinks } from './utils/links'
import historyManager from './history'
import cache from './cache'
import request from './request'
import lifecycle from './lifecycle'
import lazyload from './lazyload'
import * as Action from './actions'
/**
* @namespace RouterUtils
*/
export default (() => {
const defaultRoutes = [
{
path: '/',
view: {}
},
{
path: '*',
view: {}
}
]
/**
* Create a router
* @namespace Router
*
* @class Router
*
* @param {Object} options
* @param {String} options.routes - an array of routes
* @param {HTMLElement} options.rootNode - the root html element
* @param {Array} options.navLinks - any array of anchors to update on transition
* @param {Object} options.classes.match - The class applied to matching linkes
* @param {Object} options.classes.root - The class applied to matching root link
* @param {Object} options.classes.parent - The class applied to mathcing parent link
*
*/
return class Router {
constructor({
routes,
rootNode,
navLinks,
classes,
onEnter,
onExit,
prefetchTargets = '[data-prefetch]'
}) {
// bootup the lifecycle
lifecycle
.addRoutes(routes || defaultRoutes)
.setWrapper(rootNode)
.onLoad(window.location.pathname)
this.prefetchTargets = prefetchTargets
// the root node...
this.$wrapper = rootNode
this.$links = activeLinks({ scope: navLinks, classes })
// set the dom events
this.$events = createEvents.call(this, document, {
'click a': 'onClick',
'mouseover a': 'onMouseEnter'
})
eventBus.on(Action.ROUTE_TRANSITION_BEFORE_DOM_UPDATE, (...props) => {
onExit(...props)
})
eventBus.on(Action.ROUTE_TRANSITION_AFTER_DOM_UPDATE, (...props) => {
onEnter(...props)
})
return this
}
/** *
* @static goTo
* @memberof Router
* @param {Object} options
* @param {String} options.pathname - pathname for the page to navigate to
* @param {String} options.action - the type of history action
* @param {Object} options.dataAttrs - any data attributes on the link clicked
* @param {Object} transition - transition, a custom transition object
*
* @return {void}
*/
static goTo = ({ pathname, action, dataAttrs }, transition) => {
lifecycle
.transition({ pathname, action, transition, dataAttrs })
.then(({ action, newHtml }) => {
if (action === 'PUSH') {
historyManager.push(pathname, { attr: dataAttrs })
}
log(newHtml)
localLinks(newHtml)
})
.catch(err => {
eventBus.emit(Action.ROUTER_PAGE_NOT_FOUND, err)
// eslint-disable-next-line
console.warn(`[PREFETCH] no page found at ${err.url}`)
})
}
/** *
* @method onMouseEnter
* @memberof Router
* @description mouse enter event, triggers a fetch
* @param {Object} e - event object
* @param {HTMLElement} elm - the html element entered
*
* @return {void}
*/
onMouseEnter = (e, elm) => {
const { pathname } = elm
if (
!preventClick(e, elm) ||
cache.get(pathname) ||
elm.classList.contains('---is-fetching---')
) {
return
}
elm.classList.add('---is-fetching---')
request(pathname)
.then(() => {
elm.classList.remove('---is-fetching---')
})
.catch(err => {
// eslint-disable-next-line
console.warn(`[PREFETCH] no page found at ${pathname}`, err)
})
}
/** *
* @method onClick
* @memberof Router
* @description mouse click event, triggers a fetch
* @param {Object} e - event object
* @param {HTMLElement} elm - the html element entered
*
* @return {void}
*/
onClick = (e, elm) => {
const { pathname } = elm
if (!preventClick(e, elm)) {
return
}
e.stopPropagation()
e.preventDefault()
if (pathname === window.location.pathname) return
const dataAttrs = composeProps([...elm.attributes])
Router.goTo({ pathname, dataAttrs, action: 'PUSH' })
}
/** *
* @method mount
* @memberof Router
* @description Called after instantiation, boots everything up
*
* @return {Router}
*/
mount = () => {
eventBus.on(Action.ROUTER_POP_EVENT, ({ pathname }) => {
lifecycle.transition({ pathname, action: 'POP' })
})
eventBus.on(
Action.ROUTE_TRANSITION_AFTER_DOM_UPDATE,
({ to: { params: { raw: url } } }) => {
this.$links(url)
}
)
localLinks(document)
this.$events.attachAll()
return this
}
/** *
* @method lazyload
* @memberof Router
* @description prefetch content on a service worker
*
* @return {Router}
*/
lazyload = () => {
const items = [...document.querySelectorAll(this.prefetchTargets)]
lazyload(items)
return this
}
}
})()