I can hear readers murmuring and scratching their heads when they read this title. Why would you want routing without a url? What’s the purpose? How would it even work? What we are talking about here is how routing works when using navigation in an app build with ChocolateChip-UI. This is a framework for creating hybrid mobile apps for Android and iOS. As such, the users are never exposed to the chrome of a Webview. They only interact with the structure and content of the app. The browser and its url bar are invisible to the user.
To understand why we came up with routing without urls, you first have to understand how a ChocolateChip-UI mobile app is structured. A ChocolateChip-UI app is based on the concept of screens. When you look at any device, you’ll find that apps live in the larger area of the screen from under the status bar to the left, right and bottom of the screen. ChocolateChip-UI uses a special tag to represent this viewing area: ui-screen
. The ui-screen
with automatically expanding to fill all available space because it is responsive. In the Codepen example below, we have a ui-screen
with a blue background so you can see how it expands to fill all available space:
The ui-screen
is just a container for other structures. The two most common are nav
and section
. You could also add a footer
as a toolbar along the bottom of the screen. The nav
is where you would put the screen’s title, using an h1
. You could also put some type of button for enabling user interaction. The section
tag is where the content for that screen goes. Below is a Codepen example with a nav
and section
:
Because a ui-screen
fills the entire screen, other screens need to be placed off-screen. ChocolateChip-UI handles this by using a set of three classes:
previous
current
next
When a ui-screen
has a class of previous
, it will be transformed with a transition-x value of -100%, which will position it off screen to the left. A ui-screen
with a class of current
will be transform with a transition value of 0, which positions it into view. A ui-screen
with a class of next
will be transformed with a transition value of 100%, which positions it off screen to the right. If you need to support a right to left language, such as Arabic, Farsi, Urdu or Hebrew, you can put dir='rtl'
on the html
tag. Doing so will cause the positioning to flip to the opposite. This makes navigation animations feel proper for those languages.
In the Codepen example below, we’ve created two ui-screens
, one with a class of current
and the other with a class of next
. By tapping the buttons, we change the classes to trigger a navigation experience. Notice how the ui-screens
slide in and out automatically.
Using this toggling of classes gives us the experience of navigating from one screen to the other. We don’t need to know about a link or url. We just toggle the classes and we get the effect of navigating physically to some destination. Although cool, this is tedious. You really don’t want to have to keep writing code to toggle classes every time you want to have navigation occur. To avoid this, ChocolateChip-UI provides a navigation system.
There are several ways in which mobile operating systems enable users to navigate to different screens of an app. The most common way is a navigation list. As we’ve already seen, ChocolateChip-UI expects your app to consist of ui-screens
that animate in and out of view. To make this easy to pull off, ChocolateChip-UI has a simple mechanism to show which ui-screen
you want to transition to. First you’ll need to create a list. Then to indicate which ui-screen
each list item leads to, you use a special attribute: data-goto
. Its value will be the id of the ui-screen
you want to go to. To indicate that the list item is navigable, add a disclosure indicator inside of an aside
tag:
<li data-goto='detail-page'><h3>Item</h3><aside><disclosure></disclosure></aside></li>
This will give you a list like this
For this navigation list to work, you will need to add the navigation widget to your project. You do this by importing it into your project’s app.js
file. By the way, if you don’t know how to start a new ChocolateChip-UI project, please read the documentation for how to install and create projects.
To import ui-navigation
add the following to the top of your app.js
file:
import {UINavigation} from './src/widgets/ui-navigation
With this imported, when you build and load your app in the browser, you will find that tapping on the list item with the property data-goto='detail-page'
will transition you to the ui-screen
with an id of detail-page
. Of course, you need to make sure your app has such a ui-screen
or you will transition over to a blank screen.
If you navigate to a screen, you might want to get back to the previous screen. Hitting the browser’s back button won’t work because this was done with an animation triggered by classes. ChocolateChip-UI provides an easy way to enable back navigation. In the nav
tag of the destination screen, put a button with the class back
. When the user navigates to that screen, tapping the back button will automatically navigate the user back to the previous screen. A back button is just a button with a class of back
. It shoud be the first item in the nav
tag, before the h1
:
<nav><button class='back'>Back</button><h1>Some Details</h1></nav>
The following Codepen example shows all of this working:
If you look at this pen closely, you’ll notice there is no JavaScript to make the navigation happen. The convention of markup to enable navigation, means you don’t have to write any JavaScript for this to work. Instead, at load time, ChocolateChip-UI listeners for list items with the attribute data-goto
and buttons in the ui-screen
nav
tags with a class of back
. This enables the forward and backward navigation to just work.
From the above example, it’s clear that forward navigation is very easy to manage. The user taps a list item. ChocolateChip-UI grabs the data-goto
value and know which screen to go to. It navigates the current screen out of view by changing its class, and it navigates the destination into view by changing its class. But how does it know where to go back to when the user taps the back button? ChocolateChip-UI uses a special array: $.ChuiRoutes
. When the app loads the first time, ChocolateChip-UI pushes the current ui-screen
's id to this array. Then when the user taps a list item with adata-goto
attribute, ChocolateChip-UI pushes that id to $.ChuiRoutes
. You can check your routes at any time by opening the browser’s console and checking the value of $.ChuiRoutes
. So, what about the back button? When you tap that, ChocolateChip-UI pops the $.ChuiRoutes
array and gets the last array item. It then toggles the class of the popped item and the class of the last item in the array. This gives you your back navigation. The back button never needs to know anything other than the current value of $.ChuiRoutes
.
The following Codepen example illustrates this forward and backward navigation:
Using this simple technique of data-goto
attributes on list items and back buttons in the navigation destination, you can create navigation that drills down several levels. The following Codepen examples illustrates this:
Normally, when you tap a list item of a navigation list, you want to see more details on that item. Using the simple, declarative technique winds up requiring a lot of ui-screens
. The following Codepen example illustrates this problem. The more items you have, the more ui-screens
you need:
This is not efficient. It would be better to have just one destination for all the the list’s items and render the content dynamically. To enable this type of approach, you need to have routing with route parameters.
ChocolateChip-UI has a router. To use it, you’ll need to import it into your app.js
file:
import {UINavigation} from './src/widgets/ui-navigation'import {Router} form './src/widgets/ui-router'
Router is a class, so to use it, you need to create and instance. The initialization expects two values: a route and a callback to execute when the route happens. But there are no urls being used for navigation, so how does this work? When the user taps on a list item with data-goto
, it publishes that ui-screen
id. Using the Router, you can create a route that listens for that route. When the route gets published by a user tap, the callback will execute. Here’s how you set up routing:
import {UINavigation} from './src/widgets/ui-navigation'import {Router} form './src/widgets/ui-router'
app(() => {const router = new Router()router.addRoute({route: 'detail',callback: (param) => {// Handle the route}})})
The above route assumes the user will tap a list item with a data-goto
value of detail
, which will be the id of a ui-screen
. The route callback is expecting to receive a parameter (param), which we can use to know which item was tapped. So, how do we get the parameter to our route? That requires an addition piece of information being passed to the list item data-goto
attribute. Normally, the value for the data-goto
attribute is just the id of the ui-screen
to go to. But we can tell ChocolateChip-UI to pass some more information with that id. To do so, we add a :
to the id, followed by the value we want to pass.
This requires us to sidetrack for a moment. Let’s say we have a list of people:
const people = [{firstName:'Joe',lastName:'Bodoni'},{firstName:'Ellen',lastName:'Vanderbilt'},{firstName:'Sam',lastName:'Anderson'}]
If we want to print this data out as a list, its fine. However, if we want to be able to identify each person from the others, they need some unique identifier. This could be an id, a uuid, a guid, or a key. Choose one and give each object one:
const people = [{id: 101,firstName:'Joe',lastName:'Bodoni'},{id: 102,firstName:'Ellen',lastName:'Vanderbilt'},{id: 103,firstName:'Sam',lastName:'Anderson'}]
Using the above data, we can define our list component in such a way that each list item’s data-goto
attribute gets each object’s unique id:
const list = new Component({element: '#peopleList',// Add route parameter using object id:render: (person) => html`<li data-goto='detail:${person.id}'><h3>${person.firstName} ${person.lastName}</h3><aside><disclosure></disclosure></aside></li>`})
The above component will create list items with the following navigation attributes:
data-goto='detail:101'
data-goto='detail:102'
data-goto='detail:103'
When the user taps one of these list items, the value after the :
will be sent as the route parameter. By examining the route parameter, we can determine which list item the user tapped and filter the data to show the appropriate content. Below is a hypothetical use case:
import {UINavigation} from './src/widgets/ui-navigation'import {Router} form './src/widgets/ui-router'
app(() => {const router = new Router()router.addRoute({route: 'detail',callback: (param) => {// Handle the routeconst person = people.filter(person => person.id == id)// Render the destination screen list with this data:chosenPersonList.render(person)}})})
Here is a Codepen example illustrating how dynamic list creation along with routing works:
To learn more about navigation and routing, check out the documentation and tutorial.
Navigation is handled by using the data-goto
attribute, and this activates ChocolateChip-UI’s internal pubsub system to publish which ui-screen
is the destination and pass a route parameter. This means there is no need to mess with browser urls. Now you might object that there is no way to capture a url to send to someone. But I can’t think of a time when I was using a native app and was able to find a url in it. Well, unless it is directly exposing a url bar. Only a few native apps actually expose the browser url when displaying content like memes or actual webpages. But then why bother with all the trouble of building a native app for that? It makes more sense to just build a PWA (progressive web app) for those types of use case.