Routes | Lucid.js

Lucid.js / Routes

Introduction 🔗

Routes are a means of making your Lucid application navigable. They work like traditional page-based navigation except no page transitions occur.

Nonethelss, routes represent unique URLs in themselves, and so can be bookmarked or returned to later without having to start from the root of your app.

Routes are particularly useful with conditionals; it's common to show/hide child components based on the current active route.

Routes are useful for apps with multiple views. So a shopping cart might have a route for the product listings view, and another route for the view for info on a specific product.

Routes have data associated with them (from the URL) and can even be associated with data retrieval requests where the route should fetch data from an API.

Creating routes 🔗

Routes are created by declaring them in the routes instantiation param, which is an object of route ID (e.g. "product") to config objects, i.e.

new Lucid({ ... routes: { product: { /* route config */ } }, ... });

The structure of the route config object depends on the route type, denoted in the object's type property.

Segment routes 🔗

Segment routes look like traditional web URIs in that the various parts of them are separated by forward slashes. A segment route may look like:

  • https://example.com/#/product/12345

...where 12345 is a product ID.

With segment routes, the config object has the following members:

  • type (string) - a string denoting the route type - in the case of segment routes, "seg".
  • ptn (REGEX) - a REGEX pattern that is used to identify the route as the URL changes.
  • parts (array) - an optional array of part names to map to the route data

Knowing this, we can implement the above product-themed route as follows:

product: { type: 'seg', ptn: /^product\/\d+$/, parts: ['id'] }

Our pattern enforces that "product/" must be the first part of the URI (relative to the app root) and a numerical string (the product ID) the second.

We also provide a parts array, to give useful property names to our route data - in our case, the product ID. (The parts array is used to map segments after the first, since the first segment always denotes the route ID - it doesn't contribute to route data.)

If the parts map is omitted, route data will arrive as an array, rather than a map of key-value pairs.

When this route takes effect, the global route information will be as follows:

app.activeRoute; //"product" app.routeData; //{id: "12345"}

Or, if we hadn't provided a parts map to map the route data to property names:

app.routeData; //["12345"]

JSON routes 🔗

JSON routes are ideal for more complex situations where you need to pass around data that is either more complex  or not suited to the sequential nature of URL segments. Our product ID example above might look like this, as a JSON route:

  • https://example.com/#/{"route": "product", "id": 12345}

With JSON routes, the config object has the following members:

  • type (string) - a string denoting the route type - in the case of JSON routes, "json".That's it.

That's it. It doesn't need the other params that segment routes have, becuase all other information is denoted in the JSON passed in the URL. Therefore to adapt our product ID route to use JSON rather than URL segments, we'd declare it as follows:

product: {type: 'json'}

Important: The JSON passed in the URL must define a route property identifying the route (Lucid will warn you if it doesn't, or if a JSON route is activated that is not recognised.)

Using Push State 🔗

By default, Lucid routes utilise the URL hash. That is, if you activate a segment route, the URL might change to something like:

https://example.com/#category/dogs/boxer

It's possible to use Push States instead of the URL hash. To do so, set the usePushState instantiation param to true. This will mean the above route will instead take effect as:

https://example.com/category/dogs/boxer

Getting route data 🔗

Components can subscribe to route activations by registering a callback on the routeChanged event via the on() method of the component API.

This fires whenever a new route is activated, or the current one has its data changed (e.g. we go from one product to another - same route, different data passed).

this.on('routeChanged', () => { /* ... */ });

A component can retrieve the ID of and data relating to the active event via the following properties, which live on the app instance (which can always be retrieved via this.app from any component).

  • activeRoute (string) - the active route ID, e.g. "product"
  • routeData (object) - an object of route data relating to the active route, e.g. {id: 12345}

Important: note that when no route is active, activeRoute will have a value of undefined.

Route data can also be read or output in variables, via the special syntax

{{$rd:*}}

...where * is a segment (segment routes) or property (JSON routes) of the current active route's data. So if the current active route is a JSON route, or a segment route with named segments (declared in the route definition), we could reference the "mySegment" segment/property like so:

{{$rd:mySegmentName}}

For segmented routes where the segments are not named, segments can be referenced by index, i.e. to get the first segment:

{{$rd:0}}

Route data variables can be run through filter methods just like any other varaibles.

Routes and fetch data 🔗

It's possible to associate routes with AJAX requests that should fetch data from an API related to the current route.

This works for both segment and JSON routes. Simply specify a dataUri param on the route definition, which should be a function that returns a URI that will be fed to fetch().

routes: { myRoute: { ... dataUri: (rd, app) => 'uri' ... } }

The function is automatically forwarded the route data of the current active route, plus a reference to the app. This means you can use the route data (the segments, or properties from the decoded JSON if a JSON route) in building the request URI.

There are then two ways to consume the fetched data:

  • By subscribing to the routeChanged event, which fires when the route changes - the promise is passed as the first argument.
  • By subscribing to the routeFetched event, which fires when the route changes and the data has been fetched. The parsed data is passed as the first argument.

Lucid will parse the fetched based on the content-type response header, or else as plain text.

Lucid will emit a routeFetchError event should the server send a 4** or 5** code.

As with route data, you can retrieve fetch data at any time later by calling this.app.fetchedRouteData. You can also retrieve the last-used fetch URI for a given route by calling this.app.routes.myroute.lastFetchUri on a component.

Here's a full example, using the first approach. Suppose we defined the following route definition when instantiating Lucid:

routes: { products: { type: 'seg', ptn: /^products\/\d+$/, parts: ['catId'], dataUri: (rd, app) => 'https://some.api.com/products?catId='+rd.catId } }

And in the JavaScript of one of the app's components we listen for the route to become active:

this.on('routeChanged', fetch => { if (this.activeRoute != 'products') return; fetch.then(r => r.json()).then(obj => { //do something with the data e.g. pass it as props //or use it as repeater data }); });

Then, when the "products" route becomes active (i.e. the URL becomes something like "#products/12345"), the event callback fires and is passed the promise relating to the data retrieval request.

Activating routes 🔗

There's two ways to activate a route in Lucid.

Firstly, any a elements can cause a route change when they are clicked by having their href attributes in the format route:{route data}, where {route data} is either a segmented list or a JSON string, like so:

<a href='route:product/12345'>View product</a> <a href='route:{"route": "product", "id": 12345"}'>View product</a>

You can also cause a route change from a component's JS environment by calling the go() method of the component API. It should be passed either a segmented string (for segmented routes) or a JSON string or JavaScript object (for JSON routes).

//segment route this.go('product/12345'); //JSON route (both valid) this.go('{"route": "product", "id": 12345}'); this.go({route: 'product', id: 12345});

Note also that the route will change if the user navigates between routes via the browser's back and forward buttons.

Did I help you? Feel free to be amazing and buy me a coffee on Ko-fi!