路由
Define Routes
首先,当我们初始化Framework7应用程序时,我们应该使用routes
数组参数传递默认路由:
var app = new Framework7({
routes: [
{
name: 'about',
path: '/about/',
url: './pages/about.html',
},
{
name: 'news',
path: '/news/',
url: './pages/news.html',
options: {
animate: false,
},
},
{
name: 'users',
path: '/users/',
componentUrl: './pages/users.html',
options: {
props: {
users: ['John Doe', 'Vladimir Kharlampidi', 'Timo Ernst'],
},
},
on: {
pageAfterIn: function test (e, page) {
// do something after page gets into the view
},
pageInit: function (e, page) {
// do something when page initialized
},
}
},
// Default route, match to all pages (e.g. 404 page)
{
path: '(.*)',
url: './pages/404.html',
},
],
});
在应用程序初始化时定义的路由是默认路由,它们将对应用程序中的任何视图/路由可用.
如果您有一个多视图/路由应用程序,并且希望某些视图/路由具有自己的严格路由,并且不希望默认路由在此视图中可用,则可以在视图初始化时指定相同的routes
参数。
var view1 = app.views.create('.view-1', {
routes: [
{
path: '/users/',
url: './pages/users.html',
},
{
path: '/user/',
url: './pages/user.html',
},
],
});
如果您的多视图/路由器应用程序中,您想要某些视图/路由器具有附加路由,并且不希望这些附加路由在其他视图中可用,则可以在视图初始化时指定routesAdd
参数。
// This view will support all global routes + own additional routes
var view2 = app.views.create('.view-2', {
// These routes are only available in this view
routesAdd: [
{
path: '/blog/',
url: './pages/blog.html',
},
{
path: '/post/',
url: './pages/post.html',
},
],
})
Route Properties
好,我们看看路由属性的意思:
参数 | 类型 | 描述 | |
---|---|---|---|
name | string | 路由名, 比如 home | |
path | string | 路由路径. 这意味着当我们点击与此路径匹配的链接时,将加载该路由,或者可以通过此路径使用 API 加载该路由。 | |
options | object | 额外的路由参数对象 (可选) | |
routes | array | 指在路由中使用数组和嵌套路由的一种方式,子路由 | |
viewName | string | 路由加载的视图名字 | |
Master Detail | |||
master | boolean function(app, router) | Enables this route将此路由设置为主路由。它也可以是一个接收app 和路由实例的方法,在这个方法中,你应该返回true 或false 。
| |
detailRoutes | array | 详情路由的数组 | |
懒加载的模块 | |||
modules | array | 在路由加载之前加载包含lazy modules的数组 | |
以下路由属性定义了内容应该从哪里/什么地方加载 | |||
content | string | 从指定内容创建动态页面 | |
url | string | 通过ajax加载页面 还支持使用
| |
component | object | 从传递的Framework7 Router Component 加载页面 | |
componentUrl | string | 通过ajax加载一个页面转为一个组件 还支持从路由路径中使用 | |
async | function(context) | 执行所需的异步操作并返回所需的路由内容和选项。作为参数,它接收路由回调上下文对象。 | |
asyncComponent | function() | 方法应该返回一个Promise,该Promise已解析为包含Component或具有 它主要设计为
| |
可以路由的tabs | |||
tabs | array | tabs路由数组 | |
可以路由对话框 | |||
actions | object | 操作菜单路由 | |
popup | object | 弹出框路由 | |
loginScreen | object | 登录页酷游 | |
popover | object | 弹出提示路由 | |
sheet | object | Sheet route | |
可路由的 Panels | |||
panel | object | Panel 路由 | |
事件 | |||
on | object | 绑定事件处理函数 | |
Alias & Redirect | |||
alias | string array | 路由别名, 或者数组格式别名. 我们要指定别名的 路径path | |
redirect | string function(context) | 路由重定向。我们需要在这里指定重定向的URL(而不是路径)。如果是方法,则作为参数接收路由回调上下文对象。 | |
进入离开之前 触发 | |||
beforeEnter | function(context) array | 在路由加载/进入之前执行的功能(或功能数组)。要继续进行路由加载,必须调用resolve 。如果是array ,则必须解决数组中的每个函数才能继续。如果是方法,则作为参数接收route callback context 对象。 | |
beforeLeave | function(context) array | 在路由卸载/离开之前执行的函数(或函数数组)。为了进行导航,必须调用resolve 。如果是数组,则必须解决数组中的每个函数才能继续。如果是方法,则其作为参数接收到路由回调上下文对象。 | |
keepAlive | |||
keepAlive | boolean | 启用所谓的“keepAlive”路由。启用后,加载的页面及其组件(Vue、React或路由器组件)将永远不会被销毁。相反,它们将从DOM中分离出来,并在需要时再次重用。 |
下面是所有可能的选项示例:
routes: [
// Load via Ajax
{
path: '/about/',
url: './pages/about.html',
},
// Dynamic page from content
{
path: '/news/',
content: `
<div class="page">
<div class="page-content">
<div class="block">
<p>This page created dynamically</p>
</div>
</div>
</div>
`,
},
// By page name (data-name="services") presented in DOM
{
path: '/services/',
pageName: 'services',
},
// By page HTMLElement
{
path: '/contacts/',
el: document.querySelector('.page[data-name="contacts"]'),
},
// By component
{
path: '/posts/',
component: {
// look below
},
},
// By component url
{
path: '/post/:id/',
componentUrl: './pages/component.html',
},
// Async
{
path: '/something/',
async: function ({ app, to, resolve }) {
// Requested route
console.log(to);
// Get external data and return page content
fetch('http://some-endpoint/')
.then((res) => res.json())
.then(function (data) {
resolve(
// How and what to load
{
content: `<div class="page">${data.users}</div>`
},
);
});
}
}
],
Route Path
正如上面所述,路由的path
属性表示在浏览器窗口地址栏中显示的路径/URL(如果启用了browserHistory
),当通过API加载以下路由或点击具有相同路径的链接时。
还支持动态路径。所以如果你的路由中有以下路径/blog/users/:userId/posts/:postId/
,并且点击具有/blog/users/12/posts/25
href的链接,那么在加载的页面上,我们可以访问route.params
对象,其中包含{ userId: 12, postId: 25 }
路由路径匹配由Path To Regexp库处理,所以在Framework7中支持所有在该库中支持的内容。例如,如果你想添加一个默认路由来匹配所有路径,我们可以使用正则表达式,如:
// Default route, match to all pages (e.g. 404 page)
{
path: '(.*)',
url: './pages/404.html',
},
Route Options
让我们看一下可以在 options
属性中传递的其他路由选项:
参数 | 类型 | 说明 |
---|---|---|
animate | boolean | 页面是否应该进行动画显示(覆盖默认的路由设置) |
history | boolean | 页面是否应该保存在路由历史记录中 |
browserHistory | boolean | 页面是否应该保存在浏览器状态中。如果您使用browserHistory ,则可以在这里传递false 以防止路由出现在浏览器历史记录中 |
reloadCurrent | boolean | 用路由中的新页面替换当前页面,此时没有动画效果 |
reloadPrevious | boolean | 用路由中的新页面替换历史记录中的上一页 |
reloadAll | boolean | 加载新页面并从历史记录和DOM中删除所有先前页面 |
clearPreviousHistory | boolean | 在重新加载/导航到指定路由后,将清除先前的页面历史记录 |
ignoreCache | boolean | 如果设置为true ,则它将忽略缓存中是否存在该URL,并使用XHR重新加载它 |
force | boolean | 如果设置为true ,则它将忽略历史记录中的上一页并加载指定的页面 |
props | object | 将作为Vue/React页面组件props传递的props |
transition | string | 自定义页面过渡名称 |
openIn | string | 允许以模态框或面板的形式打开页面路由。所以它可以是以下之一:popup ,popover ,loginScreen ,sheet ,panel |
Route Callback Context
在async
,redirect
,beforeEnter
和beforeLeave
路由属性中使用的路由上下文回调的格式:
属性 | |
---|---|
app | 链接到全局应用程序实例 |
to | 请求的路由 |
from | 当前活动的路由 |
router | 当前路由器实例 |
resolve | 用于解析/继续路由的方法 |
reject | 用于阻止/拒绝路由的方法 |
direction | 导航方向,可以是“forward”或“backward” |
Async Route
async
路由属性是一个非常强大的工具,用于返回动态路由属性。它是一个带有以下参数的函数:
async(context)
- context - route callback context
resolve
method of route callback has the following format:
resolve(parameters, options)
- parameters object - object with resolved route content. Must contain one of
url
,content
,component
orcomponentUrl
properties - options object - object with Route Options
reject
callback function doesn't have arguments:
reject()
请注意,在异步方法中,直到您调用resolve
或reject
之前,路由将被阻塞!
For example:
routes = [
{
path: '/foo/',
async({ resolve, reject }) {
if (userIsLoggedIn) {
resolve({ url: 'secured.html' })
} else {
resolve({ url: 'login.html' })
}
}
}
]
Route Events
可以使用on
路由属性将所有页面事件添加到此页面的路由中。例如:
var app = new Framework7({
routes: [
// ...
{
path: '/users/',
url: './pages/users.html',
on: {
pageBeforeIn: function (event, page) {
// do something before page gets into the view
},
pageAfterIn: function (event, page) {
// do something after page gets into the view
},
pageInit: function (event, page) {
// do something when page initialized
},
pageBeforeRemove: function (event, page) {
// do something before page gets removed from DOM
},
}
},
// ...
],
});
请注意,这样的路由事件实际上是DOM事件,因此每个这样的处理程序将接受event
作为第一个参数,其中包含事件本身,以及page
作为第二个参数,其中包含页面数据。
此外,此类事件处理程序的上下文(this
)将指向相关的Router实例。
Nested Routes
也可以有嵌套路由(即路由中的路由)。
routes = [
{
path: '/faq/',
url: './pages/faq.html',
},
{
path: '/catalog/',
url: './pages/catalog.html',
routes: [
{
path: 'computers/',
url: './pages/computers.html',
},
{
path: 'monitors/',
url: './pages/monitors.html',
},
...
],
}
];
这是什么意思?为了更好地理解,实际上(在底层)这些路由将合并为以下路由:
routes = [
{
path: '/faq/',
url: './pages/faq.html',
},
{
path: '/catalog/',
url: './pages/catalog.html',
}
{
path: '/catalog/computers/',
url: './pages/computers.html',
},
{
path: '/catalog/monitors/',
url: './pages/monitors.html',
},
];
So lets say we are on a /catalog/
page and have the following links:
<a href="computers/">Computers</a>
- will work as expected. Link will be merged with the current route (/catalog/
+computers/
) and we will have/catalog/computers/
which we have in our routes.<a href="./computers/">Computers</a>
- will work the same as case 1 because./
in the beginning of path means same sub level.<a href="/catalog/computers/">Computers</a>
- will also work as expected the same as case 1 because/
(slash) in the beginning means root. And we have such root route in merged routes.<a href="/computers/">Computers</a>
- won't work as expected because/
(slash) in the beginning means root. And we don't have such/computers/
root route in our routes.
Detail Routes
For Master Detail view, it is also possible to specify detailRoutes
in addition to master: true
on master route.
When detailRoutes
are specified then navigating to detail route will also preload its master route.
But unlike nested routes (specified in routes
parameters), detail routes path
s don't merged with master route path
.
routes = [
{
path: '/blog/',
url: './news.html',
master: true,
detailRoutes: [
{
/* We need to specify detail route path from root */
path: '/blog/:postId/',
url: './post.html',
},
],
},
// ...
]
Routable Tabs
What routable tabs means and why is it good?
- First of all, it provides opportunity to navigate to tabs by usual links instead of so called special tab-links.
- Second, when navigating to such route you can load a page with required tab opened.
- Third, with enabled Browser History, the same tab will be opened when you navigate back and forward in history.
- And the last but not least, when using routable tabs you can load tabs content in the same ways as for pages, i.e. using
url
,content
,component
orcomponentUrl
First of all we need to specify tabs routes in app routes. Lets assume we have a page with routable tabs on /tabs/ route:
routes = [
{
path: '/about-me/',
url: './pages/about-me/index.html',
// Pass "tabs" property to route
tabs: [
// First (default) tab has the same url as the page itself
{
path: '/',
id: 'about',
// Fill this tab content from content string
content: `
<div class="block">
<h3>About Me</h3>
<p>...</p>
</div>
`
},
// Second tab
{
path: '/contacts/',
id: 'contacts',
// Fill this tab content via Ajax request
url: './pages/about-me/contacts.html',
},
// Third tab
{
path: '/cv/',
id: 'cv',
// Load this tab content as a component via Ajax request
componentUrl: './pages/about-me/cv.html',
},
],
}
]
On the /about-me/
page we may have the following structure for example:
<div class="page">
<div class="navbar">
<div class="navbar-bg"></div>
<div class="navbar-inner">
<div class="title">About Me</div>
</div>
</div>
<div class="toolbar tabbar toolbar-bottom">
<div class="toolbar-inner">
<a href="./" class="tab-link" data-route-tab-id="about">About</a>
<a href="./contacts/" class="tab-link" data-route-tab-id="contacts">>Contacts</a>
<a href="./cv/" class="tab-link" data-route-tab-id="cv">>CV</a>
</div>
</div>
<div class="tabs tabs-routable">
<div class="tab page-content" id="about"></div>
<div class="tab page-content" id="contacts"></div>
<div class="tab page-content" id="cv"></div>
</div>
</div>
Almost the same as with usual Tabs but with the difference that there is no more tab-link-active
and tab-active
classes on tab links and tabs. These classes and tabs will be switched by router. And there is a new data-route-tab-id
attribute, it is required for tabs switcher to understand which link related to the selected route.
You can learn more about Routable Tabs and their additional events in appropriate sections of Tabs component page.
Routable Modals
Modals can routable as well. By Modals here we mean the following components: Popup, Popover, Actions Sheet, Login Screen, Sheet Modal. Probably Popup and Login Screen have more use cases here.
And same features as for routable tabs and pages:
- it provides opportunity to open modals by usual links instead of so called special links or API,
- with enabled Browser History, the same modal will be opened when you refresh browser, navigate back and forward in history,
- with routable modals you can load modal itself and its content in the same ways as for pages, i.e. using
url
,content
,component
orcomponentUrl
routes = [
...
// Creates popup from passed HTML string
{
path: '/popup-content/',
popup: {
content: `
<div class="popup">
<div class="view">
<div class="page">
...
</div>
</div>
</div>
`
}
},
// Load Login Screen from file via Ajax
{
path: '/login-screen-ajax/',
loginScreen: {
url: './login-screen.html',
/* login-screen.html contains:
<div class="login-screen">
<div class="view">
<div class="page">
...
</div>
</div>
</div>
*/
},
},
// Load Popup from component file
{
path: '/popup-component/',
loginScreen: {
componentUrl: './popup-component.html',
/* popup-component.html contains:
<template>
<div class="popup-screen">
<div class="view">
<div class="page">
...
</div>
</div>
</div>
</template>
<style>...</style>
<script>...</script>
*/
},
},
// Use async route to check if the user is logged in:
{
path: '/secured-content/',
async({ resolve }) {
if (userIsLoggedIn) {
resolve({
url: 'secured-page.html',
});
} else {
resolve({
loginScreen: {
url: 'login-screen.html'
} ,
});
}
},
}
]
According to example above:
- when you click on link with
/popup-content/
href attribute it will open Popup from specified string content, - when you click on link with
/login-screen-ajax/
href attribute it will perform Ajax request tologin-screen.html
file and open it as a Login Screen, - when you click on link with
/popup-component/
href attribute it will perform Ajax request topopup-component.html
file, parse it as a Router Component and open it as a Popup, - when you click on link with
/secured-content/
href attribute it will load page fromsecured-page.html
if user is logged in or open Login Screen fromlogin-screen.html
file is user is not logged in.
Routable Panels
Routable Panels available from Framework7 version 3.2.0.
Panels (Side Panels) can also be routable with same features as for routable modals and pages:
- it provides opportunity to open Panel by usual links instead of so called special links or API,
- with enabled Browser History, the same Panel will be opened when you refresh browser, navigate back and forward in history,
- with routable Panels you can load Panel itself and its content in the same ways as for pages and modals, i.e. using
url
,content
,component
orcomponentUrl
routes = [
...
// Creates Panel from passed HTML string
{
path: '/left-panel/',
panel: {
content: `
<div class="panel panel-left panel-cover">
<div class="view">
<div class="page">
...
</div>
</div>
</div>
`
}
},
// Load Panel from file via Ajax
{
path: '/right-panel-ajax/',
panel: {
url: './right-panel.html',
/* right-panel.html contains:
<div class="panel panel-right panel-reveal">
<div class="view">
<div class="page">
...
</div>
</div>
</div>
*/
},
},
// Load Panel from component file
{
path: '/panel-component/',
panel: {
componentUrl: './panel-component.html',
/* panel-component.html contains:
<template>
<div class="panel panel-left panel-cover">
<div class="view">
<div class="page">
...
</div>
</div>
</div>
</template>
<style>...</style>
<script>...</script>
*/
},
},
]
According to example above:
- when you click on link with
/left-panel/
href attribute it will open Panel from specified string content, - when you click on link with
/right-panel-ajax/
href attribute it will perform Ajax request toright-panel.html
file and open it as a Right Panel, - when you click on link with
/panel-component/
href attribute it will perform Ajax request topanel-component.html
file, parse it as a Router Component and open it as a Panel,
Note that routable Panels can't be mixed with static Panels. So if you have static left panel in the app, then only right panel can be loaded as routable panel.
Route Before Enter/Leave
beforeEnter
and beforeLeave
route hooks can be very useful if you need to do additional checks, execute additional actions or load/send something before route load (enter) and unload (leave). It can be single method or array of methods to be executed. For example:
routes = [
{
path: 'profile',
url: 'profile.html',
beforeEnter: function ({ resolve, reject }) {
if (/* some condition to check user is logged in */) {
resolve();
} else {
// don't allow to visit this page for unauthenticated users
reject();
}
},
},
{
path: 'profile-edit',
url: 'profile-edit.html',
beforeLeave: function ({ resolve, reject }) {
if (/* user didn't save edited form */) {
app.dialog.confirm(
'Are you sure you want to leave this page without saving data?',
function () {
// proceed navigation
resolve();
},
function () {
// stay on page
reject();
}
)
} else {
resolve();
}
}
}
]
And of course there are multiple hooks are supported when passed as array of functions:
function checkAuth({ to, from, resolve, reject }) {
if (/* some condition to check user is logged in */) {
resolve();
} else {
reject();
}
}
function checkPermission({ to, from, resolve, reject }) {
if (/* some condition to check user edit permission */) {
resolve();
} else {
reject();
}
}
routes = [
{
path: '/profile/',
url: 'profile.html',
// check if the user is logged in
beforeEnter: checkAuth,
},
{
path: '/profile-edit/',
url: 'profile-edit.html',
// check if the user is logged in and has required permission
beforeEnter: [checkAuth, checkPermission],
}
]
Redirect & Alias
Alias
We can pass route alias using route alias
property. Alias in this case basically means that same route can have multiple paths to access:
routes = [
{
path: '/foo/',
url: 'somepage.html',
alias: '/bar/',
},
{
path: '/foo2/',
url: 'anotherpage.html',
alias: ['/bar2/', '/baz/', '/baz2/'],
}
]
According to the example above:
- if we request page by
/foo/
or/bar/
URL then first route will match and we get the page loaded fromsomepage.html
- if we request page by
/foo2/
,/bar2/
,/baz/
,/baz2/
URL then second route will match and we the page loaded fromanotherpage.html
Redirect
We can pass route redirect using redirect
property:
- if we pass
redirect
as astring
, we must pass here direct redirect url - if we pass
redirect
as afunction
, we need to call function's resolve parameter with redirect url
For example:
routes = [
{
path: '/foo/',
url: 'somepage.html',
},
{
path: '/bar/',
redirect: '/foo/',
},
{
path: '/baz/',
redirect: function ({to, resolve, reject}) {
// if we have "user" query parameter
if (to.query.user) {
// redirect to such url
resolve('/foo/?user=' + to.query.user);
}
// otherwise do nothing
else reject();
}
}
]
Above example means:
- when we request
/bar/
URL then router will redirect to/foo/
URL and then search for route that match to this new URL. In this case it will match to the first route with path/foo/
and load the page fromsomepage.html
- when we request
/baz/?user=john
we will redirect to URL/foo/?user=john
that will match to first route - when we request
/baz/
(without query) nothing will happen
Note in redirects we pass URL, not the route path like in alias
Keep Alive
keepAlive routes available from Framework7 version 3.6.0.
When keepAlive
enabled, then once router loads such page, the page and, if applicable, its component (Vue, React or Router component) will be never destroyed. Instead, it will be detached from DOM and reused again when required.
It can be useful to enable for "heavy" pages which are not too often updated. For example, page with map, or with heavy canvas or other computation. With usual logic, all those heavy computations will happen each time this page is visited. But with keepAlive it will be done once, and with all next visits, router will reuse already rendered page DOM element.
It also can be useful if you really need to preserve page state. With enabled keepAlive, the next time page will be loaded, all DOM modifications and form elements state will be preserved.
But there are things to pay attention:
- It is not supported for
async
routes - It is supported only for pages (not for panels and modals)
- If you have dynamic route path (like
/some-page/:foo/:bar
) or you rely on page route query (?foo=bar
), then query and route params won't be changed after its initial load. page:beforeremove
,pageBeforeRemove
page events will never be fired for such page- If you use it as Vue component, then
beforeDestroy
,destroyed
hooks will never be fired for such component.created
,mounted
hooks will fire only once. - If you use it as React component, then
componentWillUnmount
method will never be fired for such component.componentWillMount
,componentDidMount
methods will fire only once. - If you use it as F7's Router component, then
beforeDestroy
,destroyed
hooks will never be fired for such component.created
,mounted
hooks will fire only once.
To avoid pitfalls with component's and page lifecycle, it is recommend to rely on following page events:
page:mounted
- will be always called for keepAlive route page when its DOM element attached or reattached again.page:beforeunmount
- will be always called for keepAlive route page when its DOM element is going to be detached from DOM.
To make keepAlive route we just need to pass keepAlive: true
to its parameters:
import SomPageComponent from './some-page.js';
var routes = [
/* Usual route */
{
path: '/',
url: './pages/home.html',
},
/* Alive route. Will be loaded from file first time, and then will reuse rendered DOM element */
{
path: '/map/',
url: './pages/map.html',
keepAlive: true,
},
/* Alive route. Will be created as component, and then will reuse rendered DOM element */
{
path: '/some-page/',
component: SomPageComponent,
keepAlive: true,
},
];