In this tutorial, you'll build a simple single-page application (SPA) with multiple views and learn how to manage navigation between them using Neo.mjs's built-in routing system.
- To understand how to structure a multi-view application.
- To learn how to define and handle client-side routes.
- To use navigation components to trigger route changes.
- View Controllers: Centralizing the logic for a view, including route handling.
- Card Layout: A layout that shows only one child item at a time, perfect for managing different views.
- Routes Config: A declarative way to map URL hash fragments to controller methods.
- Declarative Navigation: Using the
routeconfig on buttons to handle navigation automatically.
In this lab, you'll create the basic structure of our application: a main container that uses a card layout to hold
three simple views.
Create the Main View
The MainView will be a Container with a card layout. The items array will hold our three "pages": a home view,
an about view, and a contact view. For now, these are just simple components with some text.
import Component from '../../src/component/Base.mjs';
import Container from '../../src/container/Base.mjs';
import Toolbar from '../../src/toolbar/Base.mjs';
class MainView extends Container {
static config = {
className: 'Tutorial.Routing.MainView',
layout : {ntype: 'vbox', align: 'stretch'},
items : [{
module: Toolbar,
flex : 'none',
items : [
{ntype: 'button', text: 'Home'},
{ntype: 'button', text: 'About'},
{ntype: 'button', text: 'Contact'}
]
}, {
module : Container,
flex : 1,
itemDefaults: {module: Component, tag: 'h1'},
layout : {ntype: 'card', index: 0},
reference : 'main-container',
items: [
{text: 'Home View'},
{text: 'About View'},
{text: 'Contact View'}
]
}]
}
}
MainView = Neo.setupClass(MainView);At this point, you should see a toolbar and the "Home View". The other two views exist but are not visible because the
card layout's activeIndex is 0. The buttons don't do anything yet.
Now, let's add the logic to switch between the views. We'll create a ViewController and define our routes.
Create the View Controller
We will create a controller class and define a routes object. The keys of this object are the URL hash patterns we
want to match (e.g., #/home), and the values are the names of the methods in our controller that should be called.
The handler methods will get a reference to our card layout container (using the reference we set in Lab 1) and update
its activeIndex.
import Component from '../../src/component/Base.mjs';
import Container from '../../src/container/Base.mjs';
import Controller from '../../src/controller/Component.mjs';
import Toolbar from '../../src/toolbar/Base.mjs';
// 1. Define the ViewController
class ViewController extends Controller {
static config = {
className: 'Tutorial.Routing.ViewController',
// The routes config maps hash patterns to handler methods
routes: {
'/home' : 'onHomeRoute',
'/about' : 'onAboutRoute',
'/contact': 'onContactRoute'
}
}
onHomeRoute() {
this.getReference('main-container').layout.activeIndex = 0;
}
onAboutRoute() {
this.getReference('main-container').layout.activeIndex = 1;
}
onContactRoute() {
this.getReference('main-container').layout.activeIndex = 2;
}
}
ViewController = Neo.setupClass(ViewController);
// 2. Define the MainView
class MainView extends Container {
static config = {
className: 'Tutorial.Routing.MainView',
// 3. Attach the controller to the view
controller: ViewController,
layout : {ntype: 'vbox', align: 'stretch'},
items : [{
module: Toolbar,
flex : 'none',
items : [
{ntype: 'button', text: 'Home'},
{ntype: 'button', text: 'About'},
{ntype: 'button', text: 'Contact'}
]
}, {
module : Container,
flex : 1,
itemDefaults: {module: Component, tag: 'h1'},
layout : {ntype: 'card', index: 0},
reference : 'main-container',
items: [
{text: 'Home View'},
{text: 'About View'},
{text: 'Contact View'}
]
}]
}
}
MainView = Neo.setupClass(MainView);Now, if you manually change the URL hash in your browser's address bar (e.g., add #/about to the end of the URL),
you will see the view change! The controller is listening for hash changes and updating the UI accordingly.
Manually changing the hash is not a great user experience. Let's connect our toolbar buttons to the routing system.
Use the `route` Config
Neo.mjs buttons (and other components) have a special route config. When you provide a hash string to this config,
the component will automatically change the browser's URL hash when it is clicked.
Let's update the items in our Toolbar.
import Component from '../../src/component/Base.mjs';
import Container from '../../src/container/Base.mjs';
import Controller from '../../src/controller/Component.mjs';
import Toolbar from '../../src/toolbar/Base.mjs';
class ViewController extends Controller {
static config = {
className: 'Tutorial.Routing.ViewController',
routes: {
'/myhome' : 'onHomeRoute', // not using '/home' here, since the portal app itself uses it
'/about' : 'onAboutRoute',
'/contact': 'onContactRoute'
}
}
onHomeRoute() {
this.getReference('main-container').layout.activeIndex = 0;
}
onAboutRoute() {
this.getReference('main-container').layout.activeIndex = 1;
}
onContactRoute() {
this.getReference('main-container').layout.activeIndex = 2;
}
}
ViewController = Neo.setupClass(ViewController);
class MainView extends Container {
static config = {
className : 'Tutorial.Routing.MainView',
controller: ViewController,
layout : {ntype: 'vbox', align: 'stretch'},
items : [{
module: Toolbar,
flex : 'none',
items : [
// Add the 'route' config to each button
{ntype: 'button', text: 'Home', route: '#/myhome'},
{ntype: 'button', text: 'About', route: '#/about'},
{ntype: 'button', text: 'Contact', route: '#/contact'}
]
}, {
module : Container,
flex : 1,
itemDefaults: {module: Component, tag: 'h1'},
layout : {ntype: 'card', index: 0},
reference : 'main-container',
items: [
{text: 'Home View'},
{text: 'About View'},
{text: 'Contact View'}
]
}]
}
}
MainView = Neo.setupClass(MainView);Now, clicking the buttons in the toolbar will update the URL hash, which in turn triggers the correct handler in your
ViewController, which then switches the active card in your MainView. You have a fully functional multi-view application!
Often, you need to pass information in the URL, like an ID. The routing system supports this with parameters.
Define a Route with a Parameter
Let's add a "Users" view and a route that can accept a user ID. A parameter is defined in the route pattern using curly
braces: {paramName}.
The handler method will receive an object containing the parsed parameters as its first argument.
import Component from '../../src/component/Base.mjs';
import Container from '../../src/container/Base.mjs';
import Controller from '../../src/controller/Component.mjs';
import Toolbar from '../../src/toolbar/Base.mjs';
class ViewController extends Controller {
static config = {
className: 'Tutorial.Routing.ViewController',
routes: {
'/myhome' : 'onHomeRoute',
'/users/{name}': 'onUserRoute' // New route with a parameter
}
}
onHomeRoute() {
this.getReference('main-container').layout.activeIndex = 0;
}
// The 'params' object will contain the value from the URL
onUserRoute(params) {
const userView = this.getReference('user-view');
// Update the user view with the parameter
userView.text = `Displaying profile for: ${params.name}`;
// Switch to the user view
this.getReference('main-container').layout.activeIndex = 1;
}
}
ViewController = Neo.setupClass(ViewController);
class MainView extends Container {
static config = {
className : 'Tutorial.Routing.MainView',
controller: ViewController,
layout : {ntype: 'vbox', align: 'stretch'},
items : [{
module: Toolbar,
flex : 'none',
items : [
{ntype: 'button', text: 'Home', route: '#/myhome'},
{ntype: 'button', text: 'User: John', route: '#/users/John'},
{ntype: 'button', text: 'User: Jane', route: '#/users/Jane'}
]
}, {
module : Container,
flex : 1,
itemDefaults: {module: Component, tag: 'h1'},
layout : {ntype: 'card', index: 0},
reference : 'main-container',
items: [
{text: 'Home View'},
// Add a reference to the user view
{text: 'Please select a user', reference: 'user-view'}
]
}]
}
}
MainView = Neo.setupClass(MainView);Now, when you click the "User: John" or "User: Jane" buttons, the onUserRoute handler is called.
It receives the name from the URL, updates the content of the user view, and then makes that view active.
Congratulations! You've built a multi-view application and learned the fundamentals of routing in Neo.mjs.
You now know how to:
- Use a
cardlayout to manage different application views. - Create a
ViewControllerto handle application logic. - Define a
routesconfig to map URL hashes to controller methods. - Use the
routeconfig on buttons for easy, declarative navigation. - Create dynamic routes that accept parameters from the URL.