路由

定义路由

首先,当我们初始化 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/',
      templateUrl: './pages/users.html',
      options: {
        context: {
          users: ['John Doe', 'Vladimir Kharlampidi', 'Timo Ernst'],
        },
      },
      on: {
        pageAfterIn: function (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',
    },
  ],
});

在应用初始化时定义的路由为默认路由,他们将适用于应用内的所有视图(View)或路由(Router)。

如果你正在开发的是一款多视图(multi-view)或多路由应用,同时希望每个视图(View)有独立的路由机制 own strict routes ,并且不想让全局的默认路由机制应用到这个视图 View, 那么,你可以在视图(View)初始化时使用同样的数组参数 routes :

var view1 = app.views.create('.view-1', {
  routes: [
    {
      path: '/users/',
      url: './pages/users.html',
    },
    {
      path: '/user/',
      url: './pages/user.html',
    },
  ],
});

如果你正在开发的是一款多视图(multi-view)或多路由应用,同时希望某些视图有部分 额外路由 ,并且这些额外路由不同应用到别的视图, 那么你可以在视图初始化时使用参数 routesAdd:

// 这个视图支持全局路由 + 自有额外路由
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',
    },
  ],
})

路由参数

好,接下来我们来看看每个路由参数的具体含义:

参数名 类型 描述
name string 路由名称, e.g. home
path string 路由路径。表示我们点击路由匹配的链接或者通过 API 调用时,将会加载这个路由路径对应路径的文件
options object 路由额外的可选参数对象(可选)
routes array 路由嵌套数组
内容相关参数
以下路由参数将说明内容将如何(从哪里或从什么)被加载
el HTMLElement 从 DOM里通过 HTML 元素加载页面
pageName string 通过指定的 data-name 属性值加载页面
content string 使用指定的内容创建动态页面
url string 通过调用 Ajax 加载页面

同时支持通过 route path 使用 {{paramName}} 表达式来实现动态路由, 例如:

{
  path: '/users/:userId/posts/:postId',
  url: 'http://myapp.com/posts/{{userId}}/{{postId}}'
}
template string
function
通过 Template7 模板字符串或方法加载页面内容
templateUrl string 通过 Ajax 异步请求加载页面内容,同时使用 Template7 编译该页面

同时支持使用 {{paramName}} 表达式的动态路由 route path

component object 通过 Framework7 的路由组件 Router Component 加载页面内容
componentUrl string 通过 Ajax 异步请求将页面当做一个组件来加载

同时支持使用 {{paramName}} 表达式的动态路由 route path

async function(routeTo, routeFrom, resolve, reject) 异步处理路由从哪跳转到哪的操作,包含了处理通过的操作和拒绝处理的操作(有更好的翻译请联系译者)(Do required asynchronous manipulation and the return required route content and options)
支持路由的标签栏
tabs array 路由标签数组参数
支持路由的模态框
actions object 上拉操作列表路由
popup object 弹出框路由
loginScreen object 登录框路由
popover object 悬浮框路由
sheet object 操作列表路由
事件
on object 事件处理对象
别名 & 重定向
alias string
array
路由别名或路由数组别名。我们需要通过 path 指定别名
redirect string
function(route, resolve, reject)
路由重定向。我们需要通过 url 指定重定向 url (not path)

以下是一个比较完整的案例:

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 template
  {
    path: '/template/:name/',
    template: `
      <div class="page">
        <div class="page-content">
          <div class="block">
            <p>Hello {{$route.params.name}}</p>
          </div>
        </div>
      </div>
    `,
  },
  // By template URL
  {
    path: '/blog/',
    templateUrl: './pages/blog.html',
  },
  // By component
  {
    path: '/posts/',
    component: {
      // look below
    },
  },
  // By component url
  {
    path: '/post/:id/',
    componentUrl: './pages/component.html',
  },
  // Async
  {
    path: '/something/',
    async: function (routeTo, routeFrom, resolve, reject) {
      // Requested route
      console.log(routeTo);
      // Get external data and return template7 template
      app.request.json('http://some-endpoint/', function (data) {
        resolve(
          // How and what to load: template
          {
            template: '<div class="page">{{users}}</div>'
          },
          // Custom template context
          {
            context: {
              users: data,
            },
          }
        );
      });
    }
  }
],

路由路径

如上所述,路由的 path 参数表示当点击路由链接或通过 api 加载路由的时候,同时如果开启了 pushState ,那么浏览器的地址栏上将会显示同样的路由 path/url 。

这里同样支持动态路由。所以如果你设置了如 /blog/users/:userId/posts/:postId/ 的路由,并且点击了 /blog/users/12/posts/25 的链接,那么将会在页面的 route.params 读取包含参数 { userId: 12, postId: 25 } 的对象。

路由路径的匹配操作依赖于 Path To Regexp 库,所以只要 Path To Regexp 库支持的, Framework7 里同样支持。举个例子,如果你想要新增一个匹配所有路径的路由,可以使用以下的正则表达式来实现:

// Default route, match to all pages (e.g. 404 page)
{
  path: '(.*)',
  url: './pages/404.html',
},

路由可选参数

我们可以通过属性 options 来设置路由的可选属性:

参数 类型 描述
animate boolean 页面是否启用动画效果(会覆盖全局默认路由的设置)
history boolean 页面是否会保存到路由历史里
pushState boolean 页面是否会保存到浏览器的状态里。 将 pushState 设置为 false ,可以防止路由将当前页面保存到浏览器的浏览历史里。
reloadCurrent boolean 是否使用路由请求过来的新页面替换当前页面
reloadPrevious boolean 是否使用路由请求过来的新页面替换浏览器历史的上一个页面
reloadAll boolean 是否使用路由请求过来的新页面替换浏览器历史的其他所有页面
context object Template/Component 页面自定义或扩展的上下文 (当使用 template, templateUrl, component or componentUrl 加载路由时)

异步路由

async 是一个专门为返回动态路由参数而设计的强大工具。它的方法包含了如下的参数:

async(routeTo, routeFrom, resolve, reject)

  • routeTo - 即将请求的路由
  • routeFrom - 当前所在路由
  • resolve - 正常执行路由时的回调方法
  • reject - 中断执行路由时的回调方法

resolve 调用这个回调方法支持传入两个参数:

resolve(parameters, options)

  • parameters object - 包含即将请求的路由的内容。至少包含所列属性中的一个: url, content, template, templateUrl, component or componentUrl
  • options object - 可选路由属性 Route Options

reject 此回调方法不包含任何参数:

reject()

注意,除非你在 async 方法中执行了 resolvereject ,否则路由请求将会终端!

例如:

routes = [
  {
    path: '/foo/',
    async(routeTo, routeFrom, resole, reject) {
      if (userIsLoggedIn) {
        resolve({ url: 'secured.html' })
      } else {
        resolve({ url: 'login.html' })
      }
    }
  }
]

路由事件

可以在路由属性 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 data 的页面对象 page

Nested Routes

It is possible to have nested routes (routes in routes) as well:

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',
      },
      ...
    ],
  }
];

What does it mean? To get better understanding, actually (under the hood) such routes will be merged into the following ones:

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 let's say we are on a /catalog/ page and have the following links:

  1. <a href="computers/">Computers - 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.

  2. <a href="./computers/">Computers</a> - will work the same as case 1 because ./ in the beginning of path means same sub level.

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

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

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 Push State, 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, template, templateUrl, component or componentUrl

First of all we need to specify tabs routes in app routes. Let's 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-inner">
      <div class="title">About Me</div>
    </div>
  </div>
  <div class="toolbar tabbar">
    <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. 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 Push State, 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, template, templateUrl, component or componentUrl
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-page/',
    async(routeTo, routeFrom, resolve, reject) {
      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 to login-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 to popup-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 from secured-page.html if user is logged in or open Login Screen from login-screen.html file is user is not logged in.

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 from somepage.html
  • if we request page by /foo2/ , /bar2/, /baz/, /baz2/ URL then second route will match and we the page loaded from anotherpage.html

Redirect

We can pass route redirect using redirect property:

  • if we pass redirect as a string, we must pass here direct redirect url
  • if we pass redirect as a function, 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 (route, resolve, reject) {
      // if we have "user" query parameter
      if (route.query.user) {
        // redirect to such url
        resolve('/foo/?user=' + route.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 from somepage.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