Опубликован: 02.04.2013 | Уровень: для всех | Доступ: свободно
Лекция 4:

Анатомия приложения и навигация по страницам

Элементы управления страниц и навигация

Сейчас мы подошли к той стороне приложений для Магазина 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).

Примечание. Дополнительные материалы, демонстрирующие основы управления страницами, вместе с обработкой состояния сеанса работы приложения, можно найти в следующих SDK-примерах: "Активация и приостановка приложений с использованием WinJS" (http://code.msdn.microsoft.com/windowsapps/App-activation-events-and-d39c53d5) (использование состояние сеанса работы приложения и элемента управления страницей), "Активация, возобновление, приостановка работы приложения" (http://code.msdn.microsoft.com/windowsapps/App-activating-and-ec15b168) (описано ранее, показывает использование задержанной приостановки и запуск приложения после завершения работы), и "Навигация и история навигации" (http://code.msdn.microsoft.com/windowsapps/Navigation-sample-cf242faa).

Шаблон Приложение навигации, структура 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>) и затем вызвав этот конструктор с родительским элементом и объектом, содержащим его параметры.

Юрий Макушин
Юрий Макушин
Россия, Москва, РЭА им. Плеханова, 2004