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

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

Хотя, базовая структура для метода ready предоставляется шаблоном, WinJS.UI.Pages и PageControlNavigator будут использовать следующие методы, если они доступны:

Таблица 3.5.
Метод PageControl Когда вызывается
init Вызывается перед тем, как созданы элементы из элемента управления страницы.
processed Вызывается после того, как WinJS.UI.processAll завершен (то есть, были созданы экземпляры элементов управления на странице, что выполняется автоматически), но перед тем, как содержимое страницы добавлено в DOM.
ready Вызывается после того, как страница добавлена в DOM.
error Вызывается, если возникла ошибка при загрузке или рендеринге страницы.
unload Вызывается при уходе со страницы.
updateLayout Вызывается в ответ на событие window.onresize, которое сигнализирует о смене между альбомным, заполняющим, прикрепленным, портретным режимами просмотра.

Обратите внимание на то, что WinJS.UI.Pages вызывает первые четыре метода. Методы unload и updateLayout, с другой стороны, используются только PageControlNavigator. Среди всех них метод ready реализуют чаще всего. Это то место, где вы можете провести дальнейшую инициализацию элемента управления (например, заполняете списки), подключаете другие обработчики событий, специфичных для страницы и так далее. Метод unload это так же место, где вы можете удалить прослушиватели событий для объектов WinRT, как описано в разделе "События WinRT и removeEventListener" ниже. Метод updateLayout важен, когда вы хотите адаптировать макет страницы к новым условиям, как, например изменения макета элемента управления ListView (как мы увидим в "Коллекции и элементы управления для вывода коллекций" , "Коллекции и элементы управления для вывода коллекций").

Что касается самого PageControlNavigator, код в js/navigator.js показывает, как он определен и как он подключает несколько событий в своём конструкторе:

(function () { "use strict";

// [некоторые части опущены]
var nav = WinJS.Navigation;

WinJS.Namespace.define("Application", {	
PageControlNavigator: WinJS.Class.define(	
// Определение функции конструктора для объекта PageControlNavigator.
function PageControlNavigator (element, options) {	
this.element = element || document.createElement("div");	
this.element.appendChild(this._createPageElement());	

this.home = options.home;
nav.onnavigated = this._navigated.bind(this);
window.onresize = this._resized.bind(this);

document.body.onkeyup = this._keyupHandler.bind(this);	
document.body.onkeypress = this._keypressHandler.bind(this);	
document.body.onmspointerup = this._mspointerupHandler.bind(this);
}, {	
//...	

В первую очередь мы видим определение пространства имен Application в качестве контейнера класса PageControlNavigation. Его конструктор принимает element, который содержит объект (div contenthost в default.html), или он создаёт новый экземпляр, если ничего не задано. Конструктор, кроме того, принимает параметр options, который задаётся в атрибуте этого элемента data-win-options. Элемент управления страницы затем присоединяет своё содержимое к этому корневому элементу, добавляет прослушиватель для события WinJS.Navigation.onnavigated и устанавливает прослушиватели для клавиатуры, мыши и событий изменения размера. Затем он ждёт, пока кто-нибудь вызовет WinJS.Navigation.Navigate, что происходит в обработчике события activated в js/default.js, для того, чтоб перейти либо на домашнюю страницу, либо на последнюю просмотренную страницу, если было перезагружено предыдущее состояние сеанса работы с программой:

if (app.sessionState.history) {
nav.history = app.sessionState.history;
}
args.setPromise(WinJS.UI.processAll().then(function () {
if (nav.location) { nav.history.current.initialPlaceholder = true; return nav.navigate(nav.location, nav.state);
} else {
return nav.navigate(Application.navigator.home);
}
}));

Когда это случается, активируется обработчик PageControlNavigator _navigated, что, в свою очередь, приводит к вызову WinJS.UI.Pages.render для выполнения загрузки, содержимое затем будет присоединено в виде элементов-потомков к элементу управления средства навигации:

_navigated: function (args) {	
var that = this;	
var newElement = that._createPageElement();	
var parentedComplete;	
var parented = new WinJS.Promise(function (c) { parentedComplete = c; });

args.detail.setPromise( WinJS.Promise.timeout().then(function () {
if (that.pageElement.winControl && that.pageElement.winControl.unload) {
that.pageElement.winControl.unload();
}
return WinJS.UI.Pages.render(args.detail.location, newElement, args.detail.state, parented);
}).then(function parentElement(control) { that.element.appendChild(newElement); that.element.removeChild(that.pageElement); that.navigated();
parentedComplete();
})
);
},

Здесь вы можете видеть, как PageControlNavigator вызывает событие unload предыдущей страницы. После этого содержимое новой страницы добавляется в DOM и затем содержимое старой страницы убирается. Вызов that.navigated затем сбрасывает состояние this.element.

Совет. В JavaScript-коде элемента управления страницы вы можете использовать this.element.querySelector вместо document.querySelector если вы лишь хотите просмотреть содержимое элемента управления страницы и не нуждаетесь в обходе всего DOM. Так как this.element - это лишь узел, он не имеет других методов обхода наподобие getElementById.

Вот как, друзья мои, это работает! В дополнение к примеру "Элементы управления HTML-страниц" (http://code.msdn.microsoft.com/windowsapps/Page-Controls-sample-568b10b4), и для показа конкретного примера работы этих механизмов в реальном приложении, код в примере HereMyAm3d конвертирован для использования данной модели его единственной страницей. Для того чтобы выполнить эту конверсию, я начал с нового проекта, использующего шаблон Приложение навигации для того, чтобы получить настроенную структуру страничной навигации. Затем я скопировал или импортировал необходимый код и ресурсы из HereMyAm3c, в основном, в pages/home/home.html, home.js и home.css. И вспомните, как я говорил, что вы можете открыть элемент управления страницы напрямую в Blend (и почему страницы имеют ссылки на WinJS)? В качестве упражнения, откройте этот проект в Blend. Во-первых, вы увидите, что всё отображается в default.html, но вы так же можете открыть саму home.html и редактировать лишь эту страницу.

Вам следует заметить, что WinJS вызывает WinJS.UI.processAll в процессе загрузки элемента управления страницы, таким образом нам не нужно беспокоиться об этих деталях. С другой стороны, перезагрузка состояния приложения при previousExecutionState==terminated нуждается в некотором внимании. Так как это происходит в событии WinJS.Application.onactivated прежде чем любые элементы управления страницами загружены, и до того, как PageControlNavigator хотя бы инициализирован, нам нужно помнить об этом условии, о том, что метод ready домашней страницы может позже создать ее экземпляр в соответствии с данными из app.sessionState. Для этого мы просто записываем еще один флаг, названный initFromState, в app.sessionState (он содержит значение true, если previousExecutionState равняется terminated и false в иных случаях).

Врезка: WinJS.Namespace.define and WinJS.Class.define

WinJS.Namespace.define (http://msdn.microsoft.com/library/windows/apps/br212667.aspx) предоставляет короткое имя для шаблона пространства имен JavaScript. Это помогает уменьшить загрязнение глобального пространства имен, так как каждое пространство имен, определенное приложением, это лишь отдельных объект в глобальном пространстве имен, который может предоставить доступ к любому количеству других объектов, функций и так далее. Это широко используется в WinJS и, так же, рекомендовано для приложений, где вы определяете всё, что вам нужно в модуле - внутри блока (function() { ... })() и затем выборочно экспортируете переменные или функции посредством пространства имен. Коротко говоря, используя пространство имен в любое время вы, фактически, добавляете любые глобальные объекты или функции!

Здесь используется следующий синтаксис: var ns = WinJS.Namespace.define(<name>, <members>), где <name> - это строка (точки допустимы), и <members> - это любой объект, заключенный в {}. Кроме того, команда WinJS.Namespace.defineWithParent(<parent>, <name>, <members>) определяет то же самое в пространстве имен <parent>.

Если вы вызываете WinJS.Namespace.define для того же самого <name> несколько раз, элементы <members> комбинируются. При возникновении коллизии, наиболее поздно добавленный элемент побеждает. Например:

WinJS.Namespace.define("MyNamespace", { x: 10, y: 10 }); WinJS.Namespace.define("MyNamespace", { x: 20, z: 10 });
//MyNamespace == { x: 20, y: 10, z: 10}

WinJS.Class.define (http://msdn.microsoft.com/library/windows/apps/br229813.aspx) это, в свою очередь, сокращение для шаблона объекта, определяющее конструктор, в итоге экземпляр такого объекта может быть создан с использованием new.

Синтаксис: var className = WinJS.Class.define(<constructor>, <instanceMembers>, <staticMembers>) где <constructor> это функция, <instanceMembers> это объект со свойствами и методами класса, и <staticMembers> это объект, к свойствам и методам которого можно получить прямой доступ посредством конструкции <className>.<member> (без использования ключевого слова new).

Варианты: WinJS.Class.derive(<baseClass>, ...) создаёт подкласс (... это тот же самый список аргументов, как и в случае с define) используя прототипное наследование, и WinJS.Class.mix(<constructor>, [<classes>]) описывает класс, который комбинирует инициализированный экземпляр (и статическое представление) класса в один или большее количество других <classes> и инициализирует объект с помощью <constructor>.

Наконец, заметьте, что, так как описание класса просто генерирует объект, WinJS.Class.define обычно используется внутри модуля, а результирующий объект экспортируется в приложение в качестве члена пространства имен. После этого вы можете использовать команду <namespace>.<class> в любом месте приложения.

Врезка: помощь IntelliSense

В приложения для Магазина Windows могут быть включены определенные структуры разметки, внутри комментариев, часто начинающиеся с тройного слэша, ///. Их используют Visual Studio и Blend для того, чтобы обеспечить поддержку IntelliSense в редакторах кода. Вы увидите, например, комментарий вида /// <reference path…/>, который создаёт взаимоотношения между вашим текущим файлом скрипта и другим скриптом для разрешения внешних функций и переменных. Этот механизм разъяснен на странице "IntelliSense для JavaScript" (http://msdn.microsoft.com/library/bb385682.aspx) в документации. Для вашего собственного кода, в особенности, для пространств имен и классов, которые вы используете из других частей приложения, используйте эти структуры комментариев при описании собственных интерфейсов для IntelliSence. Подробности вы можете найти в материале "Расширение IntelliSence для JavaScript" (http://msdn.microsoft.com/library/hh874692.aspx) и просмотреть JavaScript-файлы WinJS, в которых есть множество примеров.

Процесс и стили навигации

Понимая взаимосвязь элемента управления страницы, WinJS.UI.Pages, WinJS.Navigation, и PageControlNavigator можно ясно увидеть, как организовать навигацию между несколькими страницами внутри контекста одной HTML-страницы (например, default.html). При созданном экземпляре PageControlNavigator и заданном посредством WinJS.UI.Pages элементе управления страницы, нужно лишь вызвать WinJS.Navigation.Navigate с соответствующим URI данного элемента управления страницы (его идентификатором). Эта команда загрузит данную страницу и добавит её в DOM внутри того элемента, к которому прикреплен PageControlNavigator, выгрузив любую предыдущую страницу. В результате данная страница будет видима, таким образом произойдёт "перемещение" к странице в ожидаемой пользователем форме. Кроме того, вы можете использовать другие методы WinJS.Navigating для того, чтобы перемещаться вперед и назад по стеку навигации с помощью его свойств canGoBack и canGoForward, которые позволяют вам активировать и деактивировать элементы управления навигацией. Просто помните, что всё время вы находитесь в том же контексте вашей хост-страницы, где вы создали элемент управления PageControlNavigator.

В качестве примера, создайте новый проект, используя шаблон Приложение таблицы (Grid app) и обратите внимание на следующее:

  • pages/groupedItems/groupedItems - это домашний раздел для центральной страницы (хаба, корневого узла, hub) приложения. Она содержит элемент управления ListView (смотрите "Коллекции и элементы управления для вывода коллекций" ) с набором элементов по умолчанию.
  • Щелчок по заголовку группы в списке осуществляет перемещение к странице раздела (pages/groupDetail). Она реализована в pages/groupedItems/groupedItems.html, где встроенный обработчик события onclick позволяет перемещаться к pages/groupDetail/groupDetail.html с аргументом, идентифицирующим конкретную группу для отображения. Этот аргумент попадает в функцию ready страницы pages/groupDetail/groupDetail.js.
  • Щелчок по элементу на центральной странице осуществляет переход к странице сведений (pages/itemDetail). Обработчик itemInvoked для отдельных элементов, функция _itemsInvoked в файле pages/groupedItems/groupedItem.js, вызывает WinJS.Navigation.navigate("/pages/itemDetail/itemDetail.html") с аргументом, идентифицирующим конкретный элемент для отображения подробных сведений о нём. Как и в случае с группой, этот аргумент попадает в функцию ready, которая определена в pages/itemDetail/itemDetail.js.
  • Щелчок по элементу на странице раздела так же осуществляет переход на страницу сведений, посредством того же самого механизма - смотрите функцию _itemInvoked, определенную в pages/groupDetail/groupDetail.js.
  • Кнопка Назад на всех страницах привязана к WinJS.Navigation.Back благодаря коду в PageControlNavigator.

На всякий случай, шаблон Приложение с разделением (Split App) работает похожим образом, когда каждый элемент списка на pages/items привязан, при активизации, к перемещению на pages/split .

В любом случае, шаблон Приложение таблицы, кроме того, служит примером того, что мы называем стилем навигации Хаб-Раздел-Сведения (Hub-Section-Details). Здесь домашняя страница приложения представляет собой центральную страницу, где пользователь может в полной мере изучить приложение. Щелчок по заголовку группы осуществляет навигацию к странице раздела, второму уровню организации, где отображены лишь элементы этой группы. Щелчок по элементу (на центральной странице или на странице разделов) переносит нас на страницу сведений для данного элемента. Вы можете, конечно, реализовать данный стиль навигации любым желаемым способом. Шаблон Приложение таблицы использует элементы управления страниц, WinJS.Navigation и PageControlNavigator. (Контекстное масштабирование (семантический зум, semantic zoom), как мы увидим в "Коллекции и элементы управления для вывода коллекций" , так же поддерживается в качестве инструмента навигации для переключения между центральными страницами и страницами разделов.)

Альтернативная модель навигации - это плоский (flat) стиль, который просто имеет один уровень иерархии. Здесь навигация происходит на любую из страниц в любое время посредством панели навигации (navigation bar) (она появляется вместе с панелью приложения, смотрите "Жизненный путь приложений для Магазина Windows: Характеристики платформы Windows 8" курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript"). При использовании элементов управления страницы и PageControlNavigator, элементы управления навигацией могут просто запускать WinJS.Navigation.Navigate для этой цели. Обратите внимание, что при таком стиле навигации кнопка Назад обычно не используется.

Информацию об этих стилях, вместе со многими другими аспектами пользовательского интерфейса, касающимися навигации, можно обнаружить в материале "Проектирование навигации для приложений Магазина Windows" (http://msdn.microsoft.com/library/windows/apps/hh761500.aspx). Это - важный материал для дизайнеров.

Врезка: Страницы первоначальной идентификации пользователя и лицензионного соглашения (EULA) в приложении

Некоторые приложения могут требовать либо первоначальную идентификацию пользователя, либо принятие лицензионного соглашения для продолжения работы. Таким образом, целесообразно показывать подобные страницы сразу после экрана-заставки. В подобных случаях, если пользователь не примет условия лицензии или не введет идентификационные данные, приложению следует отобразить сообщение, говорящее о необходимости этих действий, но всегда следует позволять пользователю закрыть приложение, если он этого хочет. Не закрывайте приложение автоматически.

Обычно подобные страницы появляются лишь при первом запуске приложения. Если пользователь ввёл подходящие идентификационные данные, их можно сохранить для последующего использования, воспользовавшись API Windows.Security.Credentials.PasswordVault (http://msdn.microsoft.com/library/windows/apps/windows.security.credentials.passwordvault.aspx). Если пользователь принял EULA, сведения об этом должны быть сохранены среди данных приложения и повторно загружены всякий раз, когда приложению нужно это проверить. Эти параметры (идентификационные данные и факт соглашения с лицензией) следует сделать доступными посредством чудо-кнопки Параметры. Правовая информация, кстати, так же как и лицензионное соглашение, всегда следует делать доступными посредством Параметров. Смотрите материал "Руководство и контрольный список для элементов управления входом" (http://msdn.microsoft.com/library/windows/apps/hh965453.aspx).

Оптимизация переключения страниц: показать и скрыть

Даже с применением элементов управления страниц, всё еще многое происходит при навигации между ними: одни наборы элементов удаляются из DOM, другие добавляются. В зависимости от того, о какой странице идёт речь, это могут быть довольно ресурсоёмкие операции. Например, если у вас есть страница, которая отображает список из сотен или тысяч элементов, когда щелчок по каждому из них вызывает переход на страницу сведений об элементе (как в шаблоне Приложение таблицы), нажатие на кнопку Назад на странице сведений потребует восстановления списка.

Отображение индикатора прогресса может помочь смягчить беспокойство пользователя, и рекомендуется показывать подобный индикатор после двух секунд после начала операции и предоставлять средство для отмены операции через десять секунд. Несмотря на это, пользователи, как известно, нетерпеливы и, скорее всего, захотят быстро переключаться между списком элементов и сведениями о них. В данном случае элемент управления страницы может быть не лучшим решением задачи.

Вы можете использовать разделенный (основные данные - подробные данные) вид, конечно, но это подразумевает разделение доступного рабочего пространства экрана. Альтернатива заключается в том, чтобы постоянно, всё время, держать страницу со списком полностью загруженной. Вместо того чтобы переходить к сведениям об элементе тем способом, который мы рассматривали, просто выведите детальную страницу (смотрите WinJS.UI.Pages.render) в другой div, который занимает весь экран и перекрывает список, а затем сделайте этот div видимым. Когда вы закрываете страницу сведений, просто скройте элемент div и установите innerHTML в значение "". Используя этот подход вы получите тот же эффект, что и при навигации между страницами, но всё будет происходить гораздо быстрее. Кроме того, вы можете применить анимации WinJS, такие, как enterContent (http://msdn.microsoft.com/library/windows/apps/Hh701582.aspx) и exitContent (http://msdn.microsoft.com/library/windows/apps/hh701585.aspx) для того, чтобы сделать переходы более динамичными.

Обратите внимание на то, что так как PageControlNavigator предоставляется шаблоном как часть вашего приложения, вы можете модифицировать его как вам будет угодно для того, чтобы реализовать подобную возможность более последовательным способом.

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