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.

The Basics

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 :

Handing Screen State

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.

Navigation

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

Importing UI-Navigation into Your App

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.

How to Get Back

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.

Keeping Track of Navigation

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 a data-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:

Navigation with Multiple Destinations

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.

Meet the Router

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 route

const 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.

Look Ma, No Urls!