Skip to content

A step that waits for a URL

Two links with no handler written for the tour, one through a small router and one a plain hash link, and each step moves because the URL came to match.

The story
// A query rather than a path, so a reload at the checkout URL is still this
// page on a host that serves one file per path, as the site does. Both look
// only before the `#`, which is this case's own and never holds `view`.
const VIEW_CHECKOUT = /^[^#]*[?&]view=checkout(?:[&#]|$)/
const VIEW_HOME = /^(?![^#]*[?&]view=checkout(?:[&#]|$))/
// Home is "not at the checkout hash", because a host that does not route by
// hash shows this case with no hash at all.
const HASH_CHECKOUT = new RegExp(`#${CASE_ID}/checkout(?:[/?]|$)`)
const HASH_HOME = new RegExp(`^(?!.*#${CASE_ID}/checkout(?:[/?]|$))`)
const queryRouter = {
id: 'query-router',
steps: [
{
id: 'go-to-checkout',
target: { elements: '[data-route-link]', interactive: true },
message:
'Press this. The router pushes a URL with ?view=checkout in it, and that is what this step is waiting for.',
awaits: { url: VIEW_CHECKOUT },
},
{
id: 'arrived-by-query',
target: '[data-confirm]',
message: 'The URL with ?view=checkout in it moved this step, not the click.',
},
{
id: 'go-back',
target: { elements: '[data-back]', interactive: true },
message:
'Press Back. Leaving ?view=checkout is a URL change too, and a step can wait for that one just the same.',
awaits: { url: VIEW_HOME },
},
{
id: 'arrived-by-back',
target: '[data-current-path]',
message: 'history.back() moved this step. Nothing here told the tour a button was pressed.',
},
],
} satisfies LekoStory
const hashRouter = {
id: 'hash-router',
steps: [
{
id: 'go-to-checkout-hash',
target: { elements: '[data-hash-link]', interactive: true },
message:
'Press this plain link. There is no click handler on it — the browser changes the hash by itself.',
awaits: { url: HASH_CHECKOUT },
},
{
id: 'arrived-by-hash',
target: '[data-confirm]',
message: 'The hash ending in /checkout moved this step. No router was needed to write it.',
},
{
id: 'go-back-hash',
target: { elements: '[data-back]', interactive: true },
message:
'Press Back. It leaves the /checkout hash the same way traversal does anywhere else.',
awaits: { url: HASH_HOME },
},
{
id: 'arrived-by-back-hash',
target: '[data-confirm]',
message: 'Traversal moved this step too — forward or back, only the URL matters.',
},
],
} satisfies LekoStory
The page's own router
const goToCheckout = (event: MouseEvent): void => {
event.preventDefault()
const url = new URL(location.href)
url.searchParams.set('view', 'checkout')
history.pushState(null, '', url)
render()
}
routeLink.addEventListener('click', goToCheckout)
backButton.addEventListener('click', () => history.back())