V2 Router имеет много новых функций. Посмотрим на самые интересные.
Определить маршруты
Прежде всего, когда мы запускаем приложение 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', }, ], });
Что ж, это было довольно просто. Маршруты, определенные при инициализации приложения, являются маршрутами по умолчанию, они будут доступны для любого представления / маршрутизатора в приложении.
Если у вас есть приложение с несколькими представлениями / маршрутизатором, и вы хотите, чтобы некоторые представления / маршрутизаторы имели собственные строгие маршруты, и не хотите, чтобы маршруты по умолчанию были доступны в этом представлении, вы можете указать то же самое. routes
параметр в инициализации просмотра:
var view1 = app.views.create('.view-1', { el: '.view-1', routes: [ { path: '/users/', url: './pages/users.html', }, { path: '/user/', url: './pages/user.html', }, ], })
Если у вас есть приложение с несколькими представлениями / маршрутизатором, и вы хотите иметь некоторый вид / маршрутизатор для дополнительных маршрутов и не хотите, чтобы эти дополнительные маршруты были доступны в других представлениях, вы можете указать routesAdd
параметр в View init:
var view2 = app.views.create('.view-2', { el: '.view-2', // This routes are only available in this view routesAdd: [ { path: '/blog/', url: './pages/blog.html', }, { path: '/post/', url: './pages/post.html', }, ], })
Хорошо, теперь посмотрим, что означает каждое свойство маршрута:
Путь маршрута
Путь / URL-адрес маршрута, который будет отображаться в адресной строке окна браузера (если pushState
включен), когда следующий маршрут будет загружен либо с помощью 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', },
Содержание маршрута
В предыдущих примерах мы указали параметр url
для каждого маршрута. Этот параметр определяет, как и что загружать для этого маршрута. Он принимает разные свойства (не только URL):
url
- загружать содержимое страницы через Ajax,content
- создает динамическую страницу из указанной строки содержимого,pageName
- загрузить страницу из DOM с таким же атрибутомdata-name
(предыдущая встроенная страница),el
- загрузить страницу из DOM по переданному HTMLElement,template
- загрузить содержимое страницы из переданной строки или функции шаблона Template7,templateUrl
- загружать содержимое страницы с url-адреса через Ajax и компилировать его с помощью Template7,component
- загрузить страницу из переданного компонента маршрутизатора Framework7 (см. Ниже),componentUrl
- загружать страницы как компонент через Ajax,async
- выполнять необходимые асинхронные манипуляции и возвращать содержимое конкретного маршрута
Вот пример всех возможных вариантов:
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 $.getJSON('http://some-endpoint/', function (data) { resolve( // How and what to load: template { template: '<div class="page">{{users}}</div>' }, // Custom template context { context: { users: data, }, } ); }); } } ],
Варианты маршрута
Объект с дополнительными параметрами маршрута (необязательно):
animate
- должна ли страница быть анимированной или нет (перезаписывает настройки роутера по умолчанию),history
- должна ли страница сохраняться в истории роутера,pushState
- должна ли страница сохраняться в состоянии браузера (перезаписывает глобальный параметр маршрутизатораpushState
),reloadCurrent
- заменить текущую страницу на новую из маршрута,reloadPrevious
- заменить предыдущую страницу в истории на новую из маршрута,reloadAll
- загрузить новую страницу и удалить все предыдущие страницы из истории и DOM,context
- настраиваемый контекст для страницы Template7 / Component (с использованием параметровtemplate,
templateUrl
,component
илиcomponentUrl
)
Маршрут События
Можно добавить все события страницы внутри маршрута для этой страницы, используя свойство маршрута 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
в качестве второго аргумента с данными страницы.
Вложенные маршруты
Также возможно наличие вложенных маршрутов (маршрутов в маршрутах):
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', }, ];
Допустим, мы на /catalog/
странице и имеем следующие ссылки:
<a href=”computers/”>Computers</a>
- будет работать как положено. Ссылка будет объединена с текущим маршрутом (/catalog/
+computers/
), и у нас будет/catalog/computers/
, который есть в наших маршрутах.<a href=”./computers/”>Computers</a>
- будет работать так же, как случай 1, потому что./
в начале пути означает тот же подуровень.<a href=”/catalog/computers/”>Computers</a>
- также будет работать так же, как и в случае 1, потому что/
(косая черта) в начале означает root. И у нас такой корневой маршрут есть в объединенных маршрутах.<a href=”/computers/”>Computers</a>
- не сработает, поскольку/
(косая черта) в начале означает root. И такого/computers/
корневого маршрута в наших маршрутах нет и не будет.
Маршрутизируемые вкладки
Вкладки в версии 2 также могут быть маршрутизируемыми. Что означают маршрутизируемые вкладки и чем они хороши? Во-первых, это дает возможность переходить на вкладки по обычным ссылкам, а не по так называемым специальным вкладкам-ссылкам. Во-вторых, при переходе по такому маршруту вы можете загрузить страницу с открытой нужной вкладкой. В-третьих, при включенном Push State та же вкладка будет открываться при перемещении назад и вперед по истории. И последнее, но не менее важное: при использовании маршрутизируемых вкладок вы можете загружать содержимое вкладок так же, как и для страниц, то есть используя url
, content
, template
, templateUrl
, component
или componentUrl
:
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', }, ], } ]
На странице / about-me / у нас может быть, например, следующая структура:
<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>
Практически то же самое, что и с обычными вкладками, но с той разницей, что в ссылках вкладок и вкладках больше нет классов «tab-link-active» и «tab-active». Эти классы и вкладки будут переключаться маршрутизатором. И есть новый атрибут «data-route-tab-id», он необходим переключателю вкладок, чтобы понимать, какая ссылка относится к выбранному маршруту.
Маршрутизируемые модальные окна
Модальные окна также маршрутизируемы. Под модальными окнами здесь мы подразумеваем следующие компоненты: Popup, Popover, Actions Sheet, Login Screen, Sheet (ранее модальное окно Picker). Вероятно, здесь больше вариантов использования Popup и Login Screen.
И те же функции, что и у маршрутизируемых вкладок:
- дает возможность открывать модальные окна по обычным ссылкам вместо так называемых специальных ссылок или API,
- с включенным состоянием Push, то же модальное окно будет открываться, когда вы обновляете браузер, перемещаетесь назад и вперед по истории,
- с маршрутизируемыми модальными окнами вы можете загружать сам модальный файл и его содержимое так же, как и для страниц, то есть используя
url
,content
,template
,templateUrl
,component
или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"> <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' } , }); } }, } ]
Согласно приведенному выше примеру:
- при нажатии на ссылку с атрибутом href «/ popup-content /» открывается всплывающее окно с указанным строковым содержимым,
- когда вы нажимаете ссылку с атрибутом href «/ login-screen-ajax /», он выполняет запрос Ajax к файлу «login-screen.html» и открывает его как экран входа в систему,
- когда вы нажимаете ссылку с атрибутом href «/ popup-component /», он выполняет Ajax-запрос к файлу «popup-component.html», анализирует его как компонент маршрутизатора и открывает как всплывающее окно,
- когда вы нажимаете ссылку с атрибутом href «/ secured-content /», она загружает страницу из «secured-page.html», если пользователь вошел в систему, или открывает экран входа в систему из файла «login-screen.html», если пользователь не вошел в систему в.
Компонент маршрутизатора
Это что-то совершенно новое и, наверное, заслуживает отдельной статьи. Если вы помните, чтобы реализовать некоторую логику, зависящую от страницы, в версии 1 мы строго придерживались так называемых событий страницы и имен страниц с использованием атрибутов страницы данных. У нас было что-то подобное в нашем JS
$(document).on('page:init', function (e) { var page = e.detail.page; if (page === 'page1') { // page1 logic here } if (page === 'page2') { // page2 logic here } if (page === 'page3') { // page3 logic here } ...etc });
Framework7 v2 пытается решить эту проблему, изолировав логику, зависящую от страницы, в так называемые компоненты маршрутизатора. Если вы знаете, что такое компонент Vue, тогда его будет намного легче понять, поскольку он выглядит очень похоже.
Так что же такое компонент маршрутизатора? Компонент маршрутизатора в основном представляет собой объект со следующими свойствами (все свойства необязательны):
template
- строковый шаблон. Будет скомпилирован как шаблон Template7render
- функция рендеринга для рендеринга компонента. Должен возвращать компонентную html-строку или HTMLElementdata
- функция, должна возвращать данные контекста компонентаstyle
- строка с составляющими стилями CSS. Стили будут добавлены в документ после того, как компонент будет смонтирован (добавлен в DOM), и удалены после того, как компонент будет уничтожен (удален из DOM).methods
- объект с компонентными методамиon
- объект с обработчиками событий страницыbeforeCreate
,created
,beforeMount
,mounted
,beforeDestroy
,destroyed
- методы перехватчиков жизненного цикла компонентов.
Таким образом, пример маршрута с компонентом страницы может выглядеть так:
routes = [ ... { path: '/some-page/', component: { template: ` <div class="page"> <div class="navbar"> <div class="navbar-inner"> <div class="title">{{title}}</div> </div> </div> <div class="page-content"> <a @click="openAlert" class="red-link">Open Alert</a> <div class="list simple-list"> <ul> {{#each names}} <li>{{this}}</li> {{/each}} </ul> </div> </div> </div> `, style: ` .red-link { color: red; } `, data: function () { return { title: 'Component Page', names: ['John', 'Vladimir', 'Timo'], } }, methods: { openAlert: function () { var self = this.$app.dialog.alert('Hello world!'); }, }, on: { pageInit: function () { // do something on page init }, pageAfterOut: function () { // page has left the view }, } }, }, ... ]
Обратите внимание, что в компоненте в шаблоне поддерживается дополнительный атрибут @
. Это сокращенный метод назначения прослушивателя событий указанному элементу. Поиск указанного обработчика событий будет выполняться в компоненте methods
.
Все методы компонента и компилятор Template7 выполняются в контексте компонента.
Что такое контекст компонента. Контекст компонента - это объект, который вы вернули в data
методе, расширенный следующими полезными свойствами:
$
,$$
,$dom7
- одинаковые псевдонимы для Dom7,$app
- экземпляр приложения Framework7,$root
- корневые данные и методы, которые вы указали в тех же свойствахdata
иmethods
в инициализации приложения (new Framework7
),$route
- текущий маршрут. Содержит объект с маршрутомquery
,hash
,params
,path
иurl
,$router
- ссылка на связанный экземпляр роутера,$theme
- используется объект со свойствамиmd
иios
, которыеtrue
в соответствующей теме.
Но указывать все составляющие маршруты в одном массиве маршрутов не очень удобно, особенно если таких маршрутов много. Вот почему мы можем использовать вместо этого componentUrl
:
routes = [ ... { path: '/some-page/', componentUrl: './some-page.html', }, .. ];
И в some-page.html:
<template> <div class="page"> <div class="navbar"> <div class="navbar-inner"> <div class="title">{{title}}</div> </div> </div> <div class="page-content"> <a @click="openAlert">Open Alert</a> <div class="list simple-list"> <ul> {{#each names}} <li>{{this}}</li> {{/each}} </ul> </div> </div> </div> </template> <style> .red-link { color: red; } </style> <script> return { data: function () { return { title: 'Component Page', names: ['John', 'Vladimir', 'Timo'], } }, methods: { openAlert: function () { var self = this.$app.dialog.alert('Hello world!'); }, }, on: { pageInit: function () { // do something on page init }, pageAfterOut: function () { // page has left the view }, } } </script>
Что ж, теперь он намного чище. Теги <template>
и <style>
будут автоматически преобразованы в те же свойства экспортируемого компонента.
И хорошо, что вы можете загружать содержимое модалов и вкладок таким же образом, используя эту компонентную модель.
Такие компоненты маршрутизатора должны помочь лучше структурировать наши приложения, хранить вещи в нужном месте и делать многие вещи быстрее, понятнее и удобнее.
Переименованные события страницы
События страницы также переработаны. Теперь на каждой странице следующие события:
pageMounted
(page:mounted
) - срабатывает сразу после добавления HTML-элемента страницы в DOMpageInit
(page:init
) - срабатывает после инициализации всех компонентов страницыpageBeforeIn
(page:beforein
) - запускается прямо перед анимацией страницы для текущего представления (как для прямого, так и для обратного направлений навигации)pageAfterIn
(page:afterin
) - запускается сразу после анимации страницы для текущего представления (как для прямого, так и для обратного направлений навигации)pageBeforeOut
(page:beforeout
) - запускается прямо перед анимацией страницы вне текущего представления (как для прямого, так и для обратного направлений навигации)pageAfterOut
(page:afterout
) - запускается сразу после анимации страницы вне текущего представления (как для прямого, так и для обратного направлений навигации)pageBeforeRemove
(page:beforeremove
) - срабатывает прямо перед удалением страницы из DOM
Начальная страница
Начальную страницу также можно правильно загрузить с помощью routes
. В макете приложения мы должны оставить поле View пустым:
<body> <div id="app"> <div class="view"></div> </div> </body>
В маршрутах мы можем указать «домашний» маршрут, например:
routes: [ { path: '/', url: './home.html' }, ... ]
И когда мы запускаем просмотр, нам (рекомендуется) нужно указать, что это URL-адрес по умолчанию:
app.views.create('.view', { url: '/' })
Вот и все, теперь при загрузке приложения содержимое домашней страницы будет загружено из файла «home.html».
Перенаправление и псевдоним
Начиная с beta.16 в маршрутах также поддерживаются перенаправления и псевдонимы.
Перенаправление
routes = [ { path: '/foo/', url: 'somepage.html', }, { path: '/bar/', redirect: '/foo/', } ]
Это означает, что когда мы запрашиваем /bar/
URL-адрес, маршрутизатор будет перенаправлять на /foo/
URL-адрес, а затем искать маршрут, соответствующий этому новому URL-адресу. В этом случае он будет соответствовать первому маршруту с path/foo/
и загружать страницу из somepage.html.
Псевдоним
routes = [ { path: '/foo/', url: 'somepage.html', alias: '/bar/', }, { path: '/foo2/', url: 'anotherpage.html', alias: ['/bar2/', '/baz/', '/baz2/'], } ]
Псевдоним в этом случае в основном означает, что один и тот же маршрут может иметь несколько путей доступа. Итак, согласно приведенному выше примеру:
- если мы запрашиваем страницу по
/foo/
или/bar/
URL, тогда первый маршрут будет соответствовать, и мы получим страницу, загруженную из «somepage.html» - если мы запрашиваем страницу по
/foo2/
,/bar2/
,/baz/
,/baz2/
URL, тогда будет совпадать второй маршрут, и мы будем загружать страницу из «anotherpage.html»
Улучшенное состояние push
Состояние push в версии 2 сильно переработано и улучшено. Теперь он более корректно обрабатывает историю, поддерживает модальные окна, вкладки и значительно улучшает восстановление истории при перезагрузке приложения.
Новый API
Теперь у роутера всего 2 основных метода:
router.navigate(url[, options])
- перейти к указанному URL-адресу с указанными параметрами (необязательно).router.back([url, options])
- вернуться в историю. Оба параметра необязательны.
Резюме
Как видите, Router v2 - это почти что-то совершенно новое. Он был переработан, улучшен по всем параметрам. Да, потребуется время, чтобы принять и понять его новую философию, но оно того стоит. И в новом маршрутизаторе есть гораздо больше, в том числе переработанные переходы между страницами (теперь они основаны на JS, а не на CSS), поддержка панели навигации внутри страницы для темы iOS, восстановление положения прокрутки, новые события маршрутизатора и т. Д. Но более подробная информация о таких функциях будет рассмотрена в документации v2.