xiǎo: small, young
Xiao (pronounced "Shh!" + "Ow!") is a small, accessible, framework agnostic, dependency free, browser-driven routing system. Make single-page applications with progressive enhancement. See the docs folder for the working demo, or view it here.
npm i xiao-router
Xiao is just a small script you include in your web page. You have the option of using the ES5 or slightly smaller but less well-supported ES6 version.
<script src="../node_modules/xiao-router/xiao-es5.min.js"></script>
<script src="../node_modules/xiao-router/xiao.min.js"></script>
In Xiao, routes are just subdocuments of web pages with metadata and (sometimes) methods attached to them. Each is identified by an id
which corresponds to both a page element (say id="home"
) and a hash fragment (say #home
).
Before initializing your Xiao app, you define a routes array. Only the route id
is mandatory.
const routes = [
{
id: 'home'
},
{
id: 'about'
},
{
id: 'upload'
}
]
This routes array is supplied as the first argument of the instantiated Xiao app. The default route — the one the user is directed to when hitting the root of the application — is the second mandatory argument.
const app = new Xiao(routes, 'home')
Whether written by hand or via templating, each route is just an element with an id
in an HTML document:
<div id="home">
<!-- the (initially) static content of the route -->
</div>
On initialization, Xiao gives each element corresponding to a route role="region"
then calculates a value for an aria-label
, to further identify the route to assistive technologies. The label is:
- The text content of the route element's first
<h1>
or<h2>
if it exists - Or the content of the route object's
label
property (e.g.label: 'About my project'
) - Or — as a fallback — the route object's
id
<div id="about" role="region" aria-label="About my project">
<!-- the (initially) static content of the route -->
</div>
When a user navigates to a hash fragment, Xiao determines if that hash fragment either
- Corresponds to an element that corresponds to a route in the routes array
- Corresponds to an element inside an element that corresponds to a route in the routes array
In default operation, whether you navigate to a route element or a route child element, the previous route element is hidden and the new one revealed. For keyboard accessibility, focus is sent to the newly revealed route element.
Links corresponding to currently active routes receive the aria-current="true"
attribution:
<a href="#home" aria-current="true">Home</a>
This identifies current links to assistive technologies and doubles as a styling hook where desired.
nav [aria-current] {
border-bottom: 2px solid;
}
It is recommended that the <title>
value you supply is the name of the app. Xiao appends the label for the current route after a separator.
<title>My App | Home</title>
You can hook into lifecycle events for routes to perform operations. In Xiao, these are named arrived
and departed
. You simply add them as properties on the route object.
{
id: 'about',
label: 'About my project'.
arrived(elem, params, routes) {
// Add a class, pull in some dynamic content, whatever
},
departed(elem, params, routes) {
// Save some settings, remove some content, whatever
}
}
As you can see, there are three parameters available in each case:
- elem (node): the HTML element that corresponds to the route (carries the route
id
) - params (object): Any params passed to the route via a query string (e.g.
?foo=bar&ding=dong
will be passed as{foo: 'bar', ding: 'dong'}
). In a well-formed URL, the parameters should precede the hash (e.g.href="?foo=bar#myRouteElem"
) - routes (object): The whole routes array, for convenience
Whenever a new route is invoked, the reroute
event is dispatched from window
. This allows you to affect any part of the page in response to a reroute. Secreted in this CustomEvent
's details
object are the old and new route objects.
window.addEventListener('reroute', e => {
console.log('Old route:', e.detail.oldRoute)
console.log('New route:', e.detail.newRoute)
})
Xiao capitalizes on standard browser behavior, letting you use links and hash fragments to invoke routes. However, there will be times you want to reroute the user programmatically. A redirect maybe. For this, you can use the reroute
method.
The first argument is the desired route (or route child element) id and the second any params you may want to supply.
app.reroute('login', '?redirect=true')
The third (optional) argument when instantiating a Xiao app is the settings object. These are the options:
- separator: The string used to separate the app's name from the route label in the
<title>
(default: "|") - showHide: Whether to show only one route at a time. If set to
false
, routes are all persistently visible and the browser navigates between them like standard hash fragments (default:true
) - arrived: Like the arrived method available for individual routes, but applies to all routes (see above)
- departed: Like the departed method available for individual routes, but applies to all routes (see above)
Xiao is just a simple router which respects browser standards. The actual functionality you provide within individual Xiao routes is totally up to you. You can use plain JavaScript, React or Vue components, whatever you like. With Xiao, simple single-page applications can be just that: simple. But you can add as many dependencies and as much code to a Xiao skeleton as you like.