Framework7 Views and Pages — Navigation, Lifecycle, and Dynamic Content
In this tutorial, you will learn about Framework7 Views and Pages. We cover key concepts, practical examples, and best practices to help you master this topic.
Framework7 views are containers that manage page navigation stacks, while pages are individual screens with lifecycle events, dynamic content loading, and support for nested views and tab-based navigation.
What You'll Learn
- Creating and managing multiple views
- Page lifecycle events (init, reinit, beforeout, afterout)
- Passing parameters between pages
- Dynamic page loading and templates
- Nested views and tab navigation
Why It Matters
Multi-screen mobile apps require navigation stacks — each view maintains its own history. Framework7 views handle page push/pop transitions, while page lifecycle events let you load data when a page appears and clean up when it disappears.
Real-World Use
A messaging app with a main view for the chat list and a secondary view for the active conversation. When the user taps a chat, it pushes a detail page into the main view while the sidebar view stays visible.
View and Page Architecture
flowchart TD
A[App] --> B[View 1]
A --> C[View 2]
B --> D[Page 1]
B --> E[Page 2]
B --> F[Page 3]
C --> G[Page A]
C --> H[Page B]
D -.->|push| E
E -.->|push| F
E -.->|pop| D
style A fill:#e6f3ff,stroke:#4a90d9,stroke-width:2px
Multiple Views
<div id="app">
<!-- Main view -->
<div class="view view-main view-init" data-url="/">
<!-- Pages load dynamically via router -->
</div>
<!-- Secondary view (sidebar) -->
<div class="view view-init view-left" data-url="/sidebar/">
<!-- Sidebar content -->
</div>
</div>
var app = new Framework7({
root: '#app',
views: [
{
el: '.view-main',
routes: [
{ path: '/', url: 'pages/home.html' },
{ path: '/details/', url: 'pages/details.html' }
]
},
{
el: '.view-left',
routes: [
{ path: '/sidebar/', url: 'pages/sidebar.html' }
]
}
]
});
// Access views
var mainView = app.views.get('.view-main');
var sidebarView = app.views.get('.view-left');
// Navigate in a specific view
mainView.router.navigate('/details/');
// Navigate in sidebar
sidebarView.router.navigate('/sidebar/settings/');
Expected output: Two views exist side by side. The main view handles primary navigation. The sidebar view has its own separate navigation stack. Each view maintains independent history.
Page Lifecycle Events
// Page events fire on page transitions
var app = new Framework7({
on: {
pageInit: function(page) {
console.log('Page initialized:', page.name);
// Load initial data
},
pageReinit: function(page) {
console.log('Page reinitialized:', page.name);
// Page shown again (from history)
},
pageBeforeIn: function(page) {
console.log('Page about to enter view:', page.name);
},
pageAfterIn: function(page) {
console.log('Page entered view:', page.name);
// Start animations, load dynamic content
},
pageBeforeOut: function(page) {
console.log('Page about to leave view:', page.name);
// Save state before navigating away
},
pageAfterOut: function(page) {
console.log('Page left view:', page.name);
// Clean up resources
}
}
});
// Or listen per page using data- attributes
// <div class="page" data-name="home"
// @page:init="onPageInit"
// @page:reinit="onPageReinit">
Expected output: Page lifecycle events fire in order during navigation: init/beforeIn/afterIn when entering, beforeOut/afterOut when leaving. Reinit fires when returning to a cached page.
Page Parameters
// Method 1: Route parameters
// Route definition
{
path: '/product/:id/',
component: 'pages/product.html'
}
// Component template (product.html)
$$(document).on('page:init', '.page[data-name="product"]', function(e) {
var page = e.detail.page;
var route = page.route;
var productId = route.params.id;
console.log('Loading product:', productId);
// Fetch product data
});
// Method 2: Query parameters
// Navigate: mainView.router.navigate('/product/?id=42')
$$(document).on('page:init', '.page[data-name="product"]', function(e) {
var page = e.detail.page;
var query = page.route.query;
console.log('Product ID from query:', query.id);
});
// Method 3: Custom page data
mainView.router.navigate('/details/', {
props: {
userId: 123,
fromList: true
}
});
$$(document).on('page:init', '.page[data-name="details"]', function(e) {
var page = e.detail.page;
var props = page.route.props;
console.log('User ID:', props.userId);
console.log('From list:', props.fromList);
});
Expected output: Route parameters, query strings, and custom props all reach the page on init. The page can use these to load data and configure its content.
Dynamic Page Loading
// Method 1: Component via template string
{
path: '/users/',
component: {
template: `
<div class="page" data-name="users">
<div class="navbar">
<div class="navbar-inner">
<div class="title">Users</div>
</div>
</div>
<div class="page-content">
<div class="list media-list" id="user-list">
<!-- Populated dynamically -->
</div>
</div>
</div>
`
}
}
// Method 2: Component with async data
{
path: '/users/',
async: function(routeTo, routeFrom, resolve, reject) {
// Fetch data
fetch('/api/users')
.then(function(res) { return res.json(); })
.then(function(users) {
resolve(
{
template: `
<div class="page" data-name="users">
<div class="navbar">
<div class="navbar-inner">
<div class="title">Users (${users.length})</div>
</div>
</div>
<div class="page-content">
<div class="list media-list">
${users.map(function(u) {
return '<li><a href="/user/' + u.id + '/" class="item-link item-content">' +
'<div class="item-inner"><div class="item-title-row">' +
'<div class="item-title">' + u.name + '</div></div>' +
'<div class="item-subtitle">' + u.email + '</div></div></a></li>';
}).join('')}
</div>
</div>
</div>
`
}
);
});
}
}
Expected output: The users page loads its content from an API and renders the template with the fetched data. Each user is a clickable list item that navigates to a detail page.
Tab Navigation
<div class="page" data-name="tabs">
<div class="navbar">
<div class="navbar-inner">
<div class="title">Tabs Example</div>
</div>
</div>
<div class="toolbar tabbar">
<div class="toolbar-inner">
<a href="#tab-1" class="tab-link tab-link-active">Tab 1</a>
<a href="#tab-2" class="tab-link">Tab 2</a>
<a href="#tab-3" class="tab-link">Tab 3</a>
</div>
</div>
<div class="page-content">
<div class="tabs">
<div class="tab tab-active" id="tab-1">
<div class="block">
<p>Content for Tab 1</p>
</div>
</div>
<div class="tab" id="tab-2">
<div class="block">
<p>Content for Tab 2</p>
</div>
</div>
<div class="tab" id="tab-3">
<div class="block">
<p>Content for Tab 3</p>
</div>
</div>
</div>
</div>
</div>
// Tab events
$$(document).on('tab:show', '.tab', function(e) {
var tab = e.detail.tab;
console.log('Tab shown:', tab.id);
});
// Programmatic tab switching
app.tab.show('#tab-2');
Expected output: Three tabs with a toolbar switching between them. Each tab contains independent content. The tab:show event fires when switching.
Nested Views
<div class="page" data-name="split-view">
<div class="row">
<div class="col-40">
<!-- Left view -->
<div class="view view-init view-left" data-name="left">
<div class="page">
<div class="navbar"><div class="navbar-inner"><div class="title">List</div></div></div>
<div class="page-content">
<div class="list">
<ul>
<li><a href="/item/1/" class="item-link item-content" data-view=".view-right">
<div class="item-inner"><div class="item-title">Item 1</div></div>
</a></li>
<li><a href="/item/2/" class="item-link item-content" data-view=".view-right">
<div class="item-inner"><div class="item-title">Item 2</div></div>
</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="col-60">
<!-- Right view (detail) -->
<div class="view view-init view-right" data-name="right">
<div class="page">
<div class="page-content">
<div class="block"><p>Select an item</p></div>
</div>
</div>
</div>
</div>
</div>
</div>
Expected output: A split view layout where clicking an item in the left column navigates in the right view. The data-view attribute tells the router which view to use for navigation.
Common Mistakes
Not defining a default route - Without a route for '/', the view shows an empty page. Always define a root route that loads the initial page.
Confusing pageInit with pageReinit - pageInit fires once when a page is first created. pageReinit fires each time the page becomes visible from the history stack.
Accessing DOM before page is rendered - In page:init, the page DOM is available. In route configuration (before resolve), the DOM does not exist yet.
Forgetting to use data-name on pages - The data-name attribute identifies pages for event targeting and routing. Without it, page events do not fire correctly.
Using the router before views are initialized - Call app.views.get() and router.navigate() only after the app is fully initialized. Use the app:init event or the ready callback.
Practice Questions
- What is the difference between a view and a page?
- List the four main page lifecycle events in order.
- How do you pass parameters to a page via the router?
- How do you create a tab-based layout inside a page?
- What is a nested view and when would you use one?
Challenge: Build a split-view master-detail app: left view with a list of items (loaded from a JS array), right view showing item details, parameter passing from list to detail, back navigation in the detail view, and a tab bar in the detail view showing Info and Comments tabs.
FAQ
{{< faq "How do I go back to a previous page?" "Call mainView.router.back(). You can also use the back button: Back." >}}
Mini Project
Build a multi-view contacts app: main view with a list of contacts (name, phone, email), a detail view that opens when tapping a contact, detail view shows all info in a form layout, a tab bar in detail view with Info and Notes tabs, and back navigation from detail to list.
What's Next
Views and pages form the screen structure. Learn how Framework7 Navigation and Router handles URL routing, history, transitions, and route guards.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro