Анатомия приложения и навигация по страницам
Элементы управления страниц и навигация
Сейчас мы подошли к той стороне приложений для Магазина Windows, которая весьма сильно отличает их от обычных веб-приложений. В веб-приложениях, навигация между страницами реализуется посредством использования гиперссылок <a href> или установки параметра document.location в JavaScript. Всё это замечательно. Часто здесь либо нет данных для передачи между страницами, либо их немного. И даже когда есть что передавать между страницами, существует хорошо налаженный механизм HTML5для этого, такой, как sessionStorage и localStorage (который хорошо работает и с приложениями для Магазина).
Однако, подобные способы навигации приводят к некоторым проблемам в случае с приложениями для Магазина Windows. Первая, перемещение на совершенно новую страницу подразумевает совершенно новый контекст скрипта - все JavaScript-переменные с предыдущей страницы будут потеряны. Конечно, вы можете передать данные между этими страницами, но управление подобным по всему приложению серьезно ухудшает производительность и может очень скоро превратиться в вашу самую нелюбимое дело в работе программиста. Лучше и проще, другими словами, чтобы клиентское приложение поддерживало в памяти постоянное состояние, не зависящее от перемещений по страницам.
Кроме того, природа подсистемы рендеринга HTML/CSS такова, что при переходах между страницами с помощью гиперссылки появляется пустой экран. Пользователи веб-приложений привыкли ждать какое-то время, пока браузер загрузит новую страницу (я много что успеваю сделать за дополнительные 15 секунд!), но это не соответствует тому, что ожидают пользователи от быстрых и динамичных приложений для Магазина Windows. Более того, подобные переходы не позволяют анимацию различных элементов на экране, что помогает создать ощущение неразрывной связи страниц, если это соответствует дизайну приложения.
В итоге, хотя вы можете использовать прямые ссылки, приложения для Магазина обычно реализуют понятие "страница" динамически заменяя секции в DOM внутри контекста одной страницы наподобие default.html, что родственно тому, как работают приложения, основанные на AJAX. Подобный подход позволяет всегда сохранять контекст скрипта и обеспечить переходы между элементами и группами элементов так, как вам нужно. В некоторых случаях имеет значение простой показ и скрытие страниц, в итоге вы можете быстро переходить вперед и назад. Посмотрим на стратегии и инструменты, с помощью которых можно достичь эти цели.
Инструменты WinJS для страниц и навигации по страницам
Сама по себе Windows и хост-процесс приложения не предоставляют средств для работы со страницами - с точки зрения системы это лишь детали реализации приложения, о которых не стоит беспокоиться. К счастью, инженеры, создавшие WinJS и шаблоны в Visual Studio и Blend как следует об этом подумали! В результате они предоставили изумительные инструменты для управления частями, состоящими из HTML+CSS+JS в контексте единственной страницы-контейнера:
- WinJS.UI.Fragments содержит низкоуровневые API для "загрузки фрагментов", использование которых необходимо только тогда, когда вы хотите плотно контролировать этот процесс (как тогда, когда части HTML-фрагмента получают вместе с родительскими элементами). Мы не будем освещать это в данном учебном курсе, посмотрите документацию (http://msdn.microsoft.com/library/windows/apps/br229781.aspx) и пример "Загрузка HTML-фрагментов" (http://code.msdn.microsoft.com/windowsapps/Fragments-91f66b07).
- WinJS.UI.Pages (http://msdn.microsoft.com/library/windows/apps/hh770584.aspx) предоставляет API высокого уровня, предназначенное для обычного использования и применяющееся в шаблонах. Воспринимайте это как оболочку вокруг низкоуровневых инструментов загрузки фрагментов, которая позволяет вам легко определить "элемент управления страницей" - произвольный фрагмент HTML, CSS и JS - который вы можете просто поместить в контекст другой страницы, как вы поступаете с другими элементами управления8Если вы хорошо знакомы с пользовательскими элементами управления в XAML, здесь используется та же идея.. Их реализация, на самом деле, похожа на реализацию других элементов управления в WinJS (как мы увидим в "Элементы управления, их стилизация и привязка данных" ), поэтому вы можете объявлять их в разметке, создавать их экземпляры с помощью WinJS.UI.process[All], использовать столько этих элементов на хост-странице, сколько нужно и даже создавать из них вложенных структуры. Смотрите Сценарий 1 в примере "Элементы управления HTML-страницей" (http://code.msdn.microsoft.com/windowsapps/Page-Controls-sample-568b10b4).
Эти API предоставляют только средства для загрузки и выгрузки отдельных страниц - они берут HTML из других файлов (вместе с соответствующим CSS и JS-кодом) и присоединяют эти данные к элементу в DOM. Вот и всё. Для того, чтобы по-настоящему реализовать структуру навигации по страницам, нам нужно еще два механизма: что-то, что управляло бы стеком навигации и что-то, что перехватывало бы события навигации в механизме загрузки страниц WinJS.UI.Pages.
Первую задачу можно выполнить с помощью WinJS.Navigation, который с помощью примерно 150 строк CS101-кода поддерживает базовый навигационный стек. Это всё, что он делает. Сам по себе стек - это просто список URI, основываясь на котором WinJS.Navigation формирует свойства state, location, history, canGoBack, и canGoForward. Манипуляция стеком производится посредством методов forward, back и navigate, объект WinJS.Navigation вызывает несколько событий - beforenavigation, navigating и navigated - для любых прослушивателей (через addEventListener).9Событие beforenavigation можно использовать для отмены навигации, если нужно. Либо вызов args.preventDefault (args будет объектом события), возвратит true, либо вызов args.setPromise где promise-объект вернет true.
Для выполнения второй задачи вы можете создать собственные связи между WinJS.Navigation и WinJS.UI.Pages. На самом деле, на ранних стадиях разработки приложений для Windows 8, вплоть до первых публичных предварительных релизов для разработчиков, программисты писали один и тот же код снова и снова. В ответ на это, команда разработки в Microsoft, ответственная за шаблоны, великодушно решали создать стандартную реализацию этого механизма, что добавило несколько команд для работы с клавиатурой (для перемещения вперед и назад) и некоторые удобные оболочки для использования в шаблонах. Ура!
Этот инструмент называется PageControlNavigator. Так как это - всего лишь часть кода из шаблона, а не часть WinJS, он находится полностью под вашим управлением, в итоге, вы можете делать с ним всё, что хотите10Материал "Краткое руководство: использование одностраничной навигации" (http://msdn.microsoft.com/ru-ru/library/windows/apps/hh452768.aspx) показывает удобный способ перехвата HTML-гиперссылок с помощью WinJS.Navigation.Navigate. Этот инструмент может быть весьма полезен, особенно, если вы импортируете код из веб-приложений.. В любом случае, так как весьма вероятно то, что вы часто будете использовать PageControlNavigator в собственных программах, посмотрим, как это всё работает в контексте шаблона Приложение навигации (Navigation App).
Шаблон Приложение навигации, структура PageControl и PageControlNavigator
Если вспомнить шаблон Пустое приложение, шаблон Приложение навигации демонстрирует основы использования элементов управления страницами. (Более сложные шаблоны строят систему навигации, идущую дальше). Если вы создаёте новый проект с использованием данного шаблона в Visual Studio или Blend, вот что вы получите:
- default.html Содержит единственный div-контейнер с элементом управления PageControlNavigator, указывающим на страницу pages/home/home.html как на домашнюю страницу приложения.
- js/default.js Содержит базовый код активации и обработки изменения состояний приложения.
- css/default.css Содержит глобальные стили.
- pages/home Содержит элемент управления страницей для содержимого "домашней страницы", состоящей из home.html, home.js и home.css. Каждый из элементов управления страницы обычно имеет собственную файлы разметки, сценариев, и стилей.
- js/navigator.js Содержит реализацию класса PageControlNavigator.
Для разработки на базе этой структуры, добавьте дополнительные страницы, используя шаблон Элемент управления страницей. Рекомендую сначала создать новую папку для страницы в папке pages, наподобие папки home в стандартной структуре проекта. Затем щёлкните правой кнопкой мыши по этой папке, выберите команду Добавить > Создать элемент (Add > New Item) и выберите Элемент управления страницей (Page Control). Эта команда создаст подходящим образом названные .html, .js и .css-файлы в данной папке.
Давайте посмотрим на тело страницы default.html (опустив стандартный заголовок закомментированный элемент управления AppBar):
<body> <div id="contenthost" data-win-control="Application.PageControlNavigator" data-win-options="{home: '/pages/home/home.html'}"></div> </body>
Всё, что здесь есть - это один контейнер div, названный contenthost (название может быть любым), в котором мы объявляем элемент управления Application.PageControlNavigator. В нём мы задаём единственный параметр, определяющий первый элемент управления страницей, который ему следует загрузить (/pages/home/home.html). Экземпляр элемента управления PageControlNavigator будет создан в обработчике события activated при вызове WinJS.UI.processAll.
Внутри home.html имеется базовая, для элемента управления страницы, разметка. Это то, что шаблон Приложение навигации предоставляет в качестве домашней страницы по умолчанию, и этого в значительной степени то, что вы получаете, добавляя новый PageControl из шаблона элемента:
<!DOCTYPE html> <html> <head> <!--... обычный HTML-заголовок и ссылки на WinJS опущены --> <link href="/css/default.css" rel="stylesheet"> <link href="/pages/home/home.css" rel="stylesheet"> <script src="/pages/home/home.js"></script> </head> <body> <!-Содержимое, которое будет загружено и отображено. --> <div class="fragment homepage"> <header aria-label="Header content" role="banner"> <button class="win-backbutton" aria-label="Back" disabled></button> <h1 class="titlearea win-type-ellipsis"> <span class="pagetitle">Welcome to NavApp!</span> </h1> </header> <section aria-label="Main content" role="main"> <p>Content goes here.</p> </section> </div> </body> </html>
Элемент с CSS-классами fragment и homepage, вместе с header, создают страницу со стандартным визуальным профилем и кнопкой Назад, которую PageControlNavigator автоматически присоединяет к событиям клавиатуры, мыши и сенсорного экрана. (Это ли не внимание!). Всё, что вам нужно сделать - это изменить текст внутри элемента h1 и содержимое внутри section, или просто заменить это всё на ту разметку, которая вам нужна. (Кстати, хотя ссылки на WinJS присутствуют в каждом элементе управления страницы, они, на самом деле, не перезагружаются; они существуют здесь лишь для того, чтобы помочь вам править элементы управления страниц в Blend.)
Определение реального элемента управления страницы находится в файле pages/home/home.js. По умолчанию шаблон предоставляет лишь абсолютный минимум:
(function () { "use strict"; WinJS.UI.Pages.define("/pages/home/home.html", { // Эта функция вызывается каждый раз, когда пользователь переходит на данную страницу. Она // заполняет элементы страницы данными приложения . ready: function (element, options) { // TODO: Инициализируйте страницу здесь. } }); })();
Самая важная часть здесь - это WinJS.UI.Pages.define, которая связывает относительный URI (идентификатор элемента управления страницы), с объектом, содержащим методы элемента управления страницы. Обратите внимание на то, что сущность define позволяет вам определять различные члены страницы из различных расположений. Множественные вызовы WinJS.UI.Pages.define с тем же самым URI просто добавит члены к существующему определению, заменив те, что уже существуют. Знайте, что если вы допустите ошибку в URI, включая несовпадение между URI здесь и реальным путём к странице, страница не загрузится. Эту ошибку может быть непросто отследить.
В случае со страницей, создаваемой из шаблона Элемент управления страницей, вы получите пару дополнительных методов в её структуре (некоторые комментарии опущены):
(function () { "use strict"; WinJS.UI.Pages.define("/page2.html", { ready: function (element, options) { }, updateLayout: function (element, viewState, lastViewState) { // TODO: Ответ на изменения в состоянии viewState. }, unload: function () { // TODO: Ответ на уход с этой страницы. } }); })();
Хорошо будет отметить, что как только вы определили элемент управления страницы таким способом, вы можете создавать его экземпляры из JavaScript с использованием ключевого слова new, сначала получив его конструктор из WinJS.UI.Pages.get(<page_uri>) и затем вызвав этот конструктор с родительским элементом и объектом, содержащим его параметры.