Skip to content

Commit

Permalink
updated: Convert the route hoc into a pure component
Browse files Browse the repository at this point in the history
  • Loading branch information
GianlucaGuarini committed Oct 27, 2023
1 parent cdfcc06 commit 352a3c9
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 79 deletions.
4 changes: 2 additions & 2 deletions demos/riot-history.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Riot.js history</title>
<script src="https://unpkg.com/riot/riot.min.js"></script>
<script src="../route.js"></script>
<script src="https://unpkg.com/riot/riot.js"></script>
<script src="../index.umd.js"></script>
</head>
<body>
<div id="app"></div>
Expand Down
2 changes: 1 addition & 1 deletion demos/standalone-hash.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Standalone Hash Demo</title>
<script src="../route.standalone.js"></script>
<script src="../index.standalone.umd.js"></script>
</head>
<body>
<nav>
Expand Down
2 changes: 1 addition & 1 deletion demos/standalone-history.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Standalone History Demo</title>
<script src="../route.standalone.js"></script>
<script src="../index.standalone.umd.js"></script>
</head>
<body>
<nav>
Expand Down
122 changes: 122 additions & 0 deletions src/components/route-hoc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { PATH_ATTRIBUTE } from '../constants.js'
import {
route,
toRegexp,
match,
router,
createURLStreamPipe,
} from '../index.js'
import getCurrentRoute from '../get-current-route.js'
import { get as getAttr } from 'bianco.attr'
import { createDefaultSlot, getAttribute } from '../util.js'
import compose from 'cumpa'

const getInitialRouteValue = (pathToRegexp, path, options) => {
const route = compose(
...createURLStreamPipe(pathToRegexp, options).reverse(),
)(path)

return route.params ? route : null
}

const clearDOMBetweenNodes = (first, last, includeBoundaries) => {
const clear = (node) => {
if (!node || (node === last && !includeBoundaries)) return
const { nextSibling } = node
node.remove()
clear(nextSibling)
}

clear(includeBoundaries ? first : first.nextSibling)
}

export const routeHoc = ({ slots, attributes }) => {
return {
mount(el, context) {
// create the component state
const currentRoute = getCurrentRoute()
const path =
getAttribute(attributes, PATH_ATTRIBUTE) || getAttr(el, PATH_ATTRIBUTE)
const pathToRegexp = toRegexp(path, [])
const state = {
pathToRegexp,
route:
currentRoute && match(currentRoute, pathToRegexp)
? getInitialRouteValue(pathToRegexp, currentRoute, {})
: null,
}
this.el = el
this.slot = createDefaultSlot([
{
isBoolean: false,
name: 'route',
evaluate: () => this.state.route,
},
])
this.context = context
this.state = state
// set the route listeners
this.boundOnBeforeRoute = this.onBeforeRoute.bind(this)
this.boundOnRoute = this.onRoute.bind(this)
router.on.value(this.boundOnBeforeRoute)
this.stream = route(path).on.value(this.boundOnRoute)
// update the DOM
this.beforePlaceholder = document.createTextNode('')
this.afterPlaceholder = document.createTextNode('')
el.replaceWith(this.beforePlaceholder)
this.beforePlaceholder.parentNode.insertBefore(
this.afterPlaceholder,
this.beforePlaceholder.nextSibling,
)
},
update(context) {
this.context = context
if (this.state.route) this.slot.update({}, context)
},
mountSlot(context) {
this.beforePlaceholder.parentNode.insertBefore(
this.el,
this.beforePlaceholder.nextSibling,
)
this.slot.mount(
this.el,
{
slots,
},
context,
)
},
clearDOM(includeBoundaries) {
clearDOMBetweenNodes(
this.beforePlaceholder,
this.afterPlaceholder,
includeBoundaries,
)
},
unmount() {
router.off.value(this.boundOnBeforeRoute)
this.slot.unmount({}, this.context, true)
this.clearDOM(true)
this.stream.end()
},
onBeforeRoute(path) {
if (this.state.route && !match(path, this.state.pathToRegexp)) {
this.callLifecycleProperty('onBeforeUnmount', route)
this.slot.unmount({}, this.context, true)
this.clearDOM(false)
this.state.route = null
this.callLifecycleProperty('onUnmounted', route)
}
},
onRoute(route) {
this.callLifecycleProperty('onBeforeMount', route)
this.state.route = route
this.mountSlot(this.context)
this.callLifecycleProperty('onMounted', route)
},
callLifecycleProperty(method, ...params) {
const attr = getAttribute(attributes, method)
if (attr) attr(...params)
},
}
}
60 changes: 3 additions & 57 deletions src/components/route-hoc.riot
Original file line number Diff line number Diff line change
@@ -1,62 +1,8 @@
<route-hoc>
<template if={state.route}>
<slot route={state.route}/>
</template>

<script>
import {route, toRegexp, match, router, createURLStreamPipe} from '../index.js'
import getCurrentRoute from '../get-current-route.js'
import {getLocation} from '../util.js'
import compose from 'cumpa'
const getInitialRouteValue = (pathToRegexp, path, options) => {
const route = compose(
...createURLStreamPipe(pathToRegexp, options).reverse()
)(path)
return route.params ? route : null
}
export default {
onBeforeMount(props) {
const currentRoute = getCurrentRoute()
const pathToRegexp = toRegexp(props.path, [], props)
this.state = {
pathToRegexp,
route: currentRoute && match(currentRoute, pathToRegexp) ?
getInitialRouteValue(pathToRegexp, currentRoute, props) :
null
}
router.on.value(this.onBeforeRoute)
this.stream = route(props.path, props).on.value(this.onRoute)
},
onBeforeRoute(path, pathToRegexp) {
if (this.state.route && !match(path, this.state.pathToRegexp)) {
this.callLifecycleProperty('onBeforeUnmount', route)
this.update({
route: null
})
this.callLifecycleProperty('onUnmounted', route)
}
},
onRoute(route) {
const loc = getLocation()
import { pure } from 'riot'
import { routeHoc } from './route-hoc.js'
this.callLifecycleProperty('onBeforeMount', route)
this.update({route})
// make browser scroll to fragment
if (route.hash && loc.hash !== route.hash) loc.hash = route.hash
this.callLifecycleProperty('onMounted', route)
},
callLifecycleProperty(method, ...params) {
if (this.props[method]) this.props[method](...params)
},
onUnmounted() {
router.off.value(this.onBeforeRoute)
this.stream.end()
}
}
export default pure(routeHoc)
</script>
</route-hoc>
13 changes: 3 additions & 10 deletions src/components/router-hoc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { router } from '../index.js'
import { defer, cancelDefer, getAttribute } from '../util.js'
import { __ } from 'riot'
import { defer, cancelDefer, getAttribute, createDefaultSlot } from '../util.js'
import getCurrentRoute from '../get-current-route.js'
import setBase from '../set-base.js'
import { panic } from '@riotjs/util/misc'
Expand All @@ -9,9 +8,8 @@ import initDomListeners from '../dom.js'
const BASE_ATTRIBUTE_NAME = 'base'
const INITIAL_ROUTE = 'initialRoute'
const ON_STARTED_ATTRIBUTE_NAME = 'onStarted'
const { template, bindingTypes } = __.DOMBindings

export function routerHoc({ slots, attributes, props }) {
export const routerHoc = ({ slots, attributes, props }) => {
if (routerHoc.wasInitialized)
panic('Multiple <router> components are not supported')

Expand Down Expand Up @@ -48,12 +46,7 @@ export function routerHoc({ slots, attributes, props }) {
if (!slots || !slots.length) return
const onStartedAttr = getAttribute(attributes, ON_STARTED_ATTRIBUTE_NAME)

this.slot = template(null, [
{
type: bindingTypes.SLOT,
name: 'default',
},
])
this.slot = createDefaultSlot()

this.slot.mount(
this.el,
Expand Down
11 changes: 5 additions & 6 deletions src/components/router-hoc.riot
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<router-hoc>
<script>
import { pure } from 'riot'
import { routerHoc } from './router-hoc.js'
export default pure(routerHoc)
</script>
<script>
import { pure } from 'riot'
import { routerHoc } from './router-hoc.js'
export default pure(routerHoc)
</script>
</router-hoc>
1 change: 1 addition & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export const TARGET_SELF_LINK_ATTRIBUTE = '_self'
export const LINK_TAG_NAME = 'A'
export const HASH = '#'
export const SLASH = '/'
export const PATH_ATTRIBUTE = 'path'
export const RE_ORIGIN = /^.+?\/\/+[^/]+/
6 changes: 5 additions & 1 deletion src/dom.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
CLICK_EVENT,
DOWNLOAD_LINK_ATTRIBUTE,
HASH,
HREF_LINK_ATTRIBUTE,
LINK_TAG_NAME,
RE_ORIGIN,
Expand Down Expand Up @@ -28,6 +29,7 @@ const onRouterPush = (path) => {
const getLinkElement = (node) =>
node && !isLinkNode(node) ? getLinkElement(node.parentNode) : node
const isLinkNode = (node) => node.nodeName === LINK_TAG_NAME
const isHashLink = (path) => path.includes(HASH)
const isCrossOriginLink = (path) =>
path.indexOf(getLocation().href.match(RE_ORIGIN)[0]) === -1
const isTargetSelfLink = (el) =>
Expand Down Expand Up @@ -64,7 +66,9 @@ const onClick = (event) => {

router.push(path)

event.preventDefault()
// prevent default only links that are not hash links
// otherwise just let the browser scroll to the linked id
if (!isHashLink(el.href)) event.preventDefault()
}

/**
Expand Down
16 changes: 16 additions & 0 deletions src/util.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { dashToCamelCase } from '@riotjs/util/strings'
import { __ } from 'riot'

export const getGlobal = () => getWindow() || global
export const getWindow = () => (typeof window === 'undefined' ? null : window)
Expand All @@ -25,3 +26,18 @@ export const cancelDefer = (() => {

export const getAttribute = (attributes, name) =>
attributes && attributes.find((a) => dashToCamelCase(a.name) === name)

export const createDefaultSlot = (attributes = []) => {
const { template, bindingTypes, expressionTypes } = __.DOMBindings

return template(null, [
{
type: bindingTypes.SLOT,
name: 'default',
attributes: attributes.map((attr) => ({
...attr,
type: expressionTypes.ATTRIBUTE,
})),
},
])
}
2 changes: 1 addition & 1 deletion test/components.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe('components', function () {
router.push('/')
})

it('The router contents get properly rendered', async function () {
it.only('The router contents get properly rendered', async function () {
const el = document.createElement('div')

const comp = component(HistoryRouterApp)(el, {
Expand Down

0 comments on commit 352a3c9

Please sign in to comment.