Файлы к данной лекции Вы можете скачать здесь.
На ранних стадиях написания курса, я, кроме того, плотно работал с подрядчиком на постройке дома для моей семьи. Даже когда я не был на месте строительства, прилагая все усилия, чтобы оно продвигалось, я был, конечно, вовлечен в процесс принятия решений о различных фазах строительства и иногда участвовал в самом строительстве.
В предгорьях Сьерра-Невады в Калифорнии, где я живу, каркас дома построен из местного дерева, и вся сантехника и проводка должны быть в стенах до того, как будет установлена изоляция и древесные плиты (гипсокартон). Меня поразило, сколько времени потребовалось для того, чтобы завершить монтаж этой инфраструктуры. Строители потратили много времени на добавление маленьких брусков дерева, то тут, то там, чтобы упростить себе отделочную работу позже (вроде развешивания шкафов), и много времени заняла правильная сборка сантехники и проводки. Всё это стало совершенно невидимым для глаз, когда панели были закреплены и была готова отделка.
Представьте себе, на что будет похож дом, построенный без такого внимания к его деталям. Представьте, что некоторые выключателя просто не работают или управляют не теми светильниками. Представьте себе, что трубы в стенах протекают. Представьте, как шкафы и отделка падают со стен через пару недель после того, как они попали в дом. Даже если дому удалось пройти окончательную проверку, подобные недостатки сделают его почти непригодным для жизни, и неважно, каким красивым он может показаться на первый взгляд. Это походило бы на несколько работ знаменитого архитектора Фрэнка Ллойда Райта: очень интересно с архитектурной точки зрения, но совершенно неудобно для того, чтобы жить там.
С приложениями - та же история. На самом деле, я удивляюсь, как много похожего между этими двумя начинаниями! Это значит, что приложение может замечательно выглядеть, даже ошеломляюще, но как только вы начнете по-настоящему пользоваться им, день за днем, недостатки внимания к фундаментальным основам станет болезненно очевидным. В результате, ваши клиенты, вероятнее всего, начнут искать себе новый дом, то есть - приложение какого-нибудь другого разработчика.
Эта лекция посвящена тем самым основам: ключевым глубинным конструкциям приложения, на основе которых можно построить нечто, выглядящее красиво и работающее по-настоящему хорошо. Мы сначала доведем до полного понимания окружение хост-процесса приложения и посмотрим на активацию (то, как приложение запускается) и на переходы между стадиями жизненного цикла приложения. Затем мы посмотрим на навигацию по страницам внутри приложения и разберем еще некоторые важные попутные вопросы, такие, как работа с несколькими асинхронными операциями.
Позвольте мне предупредить вас о том, что эта лекция гораздо больше и сложнее, чем многие, следующие за ней, так как она имеет дело с программными эквивалентами создания каркаса дома, построения сантехнических коммуникаций и электропроводки. В случае с нашим домом, я могу с полной уверенностью говорить о том, что установка прекрасных светильников, которые выбрала моя жена, принесло больше удовольствия, чем сам процесс строительства, которым я занимался месяцами ранее. Но сейчас, по-настоящему живя в доме, я чувствую глубокую признательность за всю ту менее привлекательную работу, благодаря которой дом был построен. Это место, где я хочу быть, место, в котором я и моя семья будем счастливы провести большую часть нашей жизни. Вы ведь хотите, чтобы клиенты чувствовали то же самое по отношению к вашим приложениям? Абсолютно точно! Зная о том удовольствии, которое хорошо спроектированные приложения способны принести вашим клиентам, займёмся нашим делом и ощутим удовольствие от исследования деталей!
Как было описано в лекции 1, приложения, написанные на HTML, CSS и JavaScript не являются исполняемыми, как их скомпилированные аналоги, написанные на C#, Visual Basic или C++. В пакетах приложений нет EXE-файлов, там есть лишь .html, .css и .js-файлы (и ресурсы тоже, конечно), которые не содержат ничего, кроме обычного текста. В итоге, что-то должно превратить эти тексты, определяющие приложение, во что-то, что запускается и работает в памяти. Это "кое-что" - хост-процесс приложения (app host), wwahost.exe, который создаёт то, что мы называем управляемой средой (hosted environment) приложений для Магазина Windows.
Рассмотрим то, что мы уже узнали в лекции 1 и лекции 2 о характеристиках управляемой среды.
ms-appx:/// или ms-appx-web:///), использованной для обращения к контенту (третий / означает "пакет приложения"). Удалённый контент (обращение к нему ведется с помощью http[s]://) всегда исполняется в веб-контексте.
postMessage может быть использована для организации межконтекстного взаимодействия между iframe и средой, которая её содержит. Это может быть полезным для исполнения удаленного скрипта в веб-контексте и передачи результатов в локальный контекст; скрипт из веб-контекста не нужно перемещать в локальный контекст и исполнять там. (Правила Магазина Windows запрещают это и приложения, отправленные в Магазин, анализируются на предмет применения подобных методов работы).
В этой лекции мы больше будем заниматься не самими этими характеристиками, а тем, как они воздействуют на структуру приложения. (Для того, чтобы посмотреть характеристики, обратитесь к примеру "Интеграция содержимого и элементов управления с веб-сервисов", (http://code.msdn.microsoft.com/windowsapps/Mashup-Sample-10689f5b).)
В первую очередь, и прежде всего, отметим, что домашняя страница приложения, та, которую вы указываете в манифесте, в поле Начальная страница (Start page), на закладке Интерфейс приложения (Application UI)<href> или команды document.location) так же должна принадлежать локальному контексту.
Далее, страница локального контекста может содержать элемент iframe, имеющий локальный или веб-контекст, предоставляя ему атрибут src, ссылающийся на контент в пакете приложения (и, кстати, программный доступ только для чтения к содержимому вашего пакета, который можно получить посредством Windows.ApplicationMode.Package.Current.InstalledLocation). Ссылки на любые другие местоположения (http[s]:// или другие протоколы) всегда помещаются в iframe, который исполняется в веб-контексте.
<!-- iframe в локальном контексте, с источником из пакета приложения --> <!-- подобное разрешено лишь из локального контекста --> <iframe src="/frame-local.html"></iframe> <iframe src="ms-appx:///frame-local.html"></iframe> <!-- iframe в веб-контексте с источником в пакете приложения --> <iframe src="ms-appx-web:///frame-web.html"></iframe> <!-- iframe с внешним источником автоматически присваивается веб-контекст --> <iframe src="http://www.bing.com"></iframe>
Кроме того, если вы используете тег <a href="..." target="..."> с target, указывающем на iframe, схема в href определяет контекст.
Страница в веб-контексте, в свою очередь, может содержать только iframe, который так же имеет веб-контекст. Например, выше можно использовать последние два элемента iframe, в то время как два первых элемента - нет. Кроме того, вы можете использовать ms-appx-web:/// в веб-контексте для того, чтобы ссылаться на другое содержимое в пакете приложения, например, на изображения.
Хотя это не вполне справедливо внутри приложений для Магазина Windows, по причинам, которые мы рассмотрим ниже в этой лекции, похожие правила применимы к навигации между страницами с использованием <href> или document.location. Так как всё то, о чём мы здесь говорили, похоже сейчас на кашу из разных сведений, точное поведение для этих вариаций и iframe приведено в следующей таблице:
| Цель | Результат на странице в локальном контексте | Результат на странице в веб-контексте |
|---|---|---|
<iframe src="ms-appx:///"> | iframe в локальном контексте | Не разрешено |
<iframe src="ms-appx-web:///"> | iframe в веб-контексте | iframe в веб-контексте |
<iframe src="http[s]:// "> или другая схема | iframe в веб-контексте | iframe в веб-контексте |
<a href="[uri]" target="myFrame"> <iframe name="myFrame"> | iframe в локальном или веб-контексте, в зависимости от [uri] | iframe в веб-контексте; [uri] не может начинаться с ms-appx. |
<a href="ms-appx:///"> | Ссылка на страницу в локальном контексте | Не разрешено, если только не задано явно (смотрите ниже) |
<a href="ms-appx-web:///"> | Не разрешено | Ссылка на страницу в веб-контексте |
<a href="[uri]"> с любым другим протоколом, включая http[s] | Открывает браузер по умолчанию с [uri] | Открывает браузер по умолчанию с [uri] |
Когда iframe работает в веб-контексте, обратите внимание на то, что страница может иметь ms-appx-web-ссылки на ресурсы пакета приложения, даже если страница загружена с удалённого источника (http[s]). Подобная страница, конечно, не будет работать в браузере.
Последние два элемента в таблице, на самом деле, имеют в виду то, что приложение не может осуществить переход со своей страницы верхнего уровня (в локальном контексте) прямо к странице любого вида в веб-контексте (удалённой или локальной) и при этом отображать её в приложении. Вместо приложения будет запущен браузер. Такова жизнь приложения в хост-процессе! Подобный контент нужно размещать в iframe.
Аналогично, навигация со страницы веб-контекста к страницам локального контекста по умолчанию не разрешена, но вы можете включить это, вызвав сверхсекретную функцию MSApp.addPublicLocalApplicationUri (http://msdn.microsoft.com/library/windows/apps/hh465759.aspx) из кода на локальной странице (на самом деле, функция хорошо документирована) для каждого конкретного URI, который вам нужен:
//Это должно быть вызвано из локального контекста
MSApp.addPublicLocalApplicationUri("ms-appx:///frame-local.html");
Упражнение для этой лекции, "Direct Navigation", содержит демонстрацию этой возможности (как в Сценарии 6, в примере "Интеграция содержимого и элементов управления с веб-сервисов", (http://code.msdn.microsoft.com/windowsapps/Mashup-Sample-10689f5b). Будьте осторожны, когда URI содержит параметры запроса. Например, вы можете не захотеть позволять вебсайту переходить на что-то вроде: ms-appx:///delete.html?file=superimportant.doc , выполняя операцию по удалению очень важных данных!
Здесь возникает еще один вопрос, о возможности предоставить странице в веб-контексте доступ к специфическим функциям, наподобие геолокации, записи данных в буфер обмена, доступ к кэшу приложения, к IndexedDB - к тому, чем обычно пользуются веб-страницы. По умолчанию веб-контекст в приложениях для Магазина Windows не имеют доступа к подобным возможностям уровня операционной системы. Например, создайте новое приложение по шаблону Пустое приложение в Visual Studio с этой единственной HTML-строкой в теле страницы default.html:
<iframe src="http://maps.bing.com" style="width:1366px; height: 768px"></iframe>
Затем, включите возможность Расположение (Location) в манифесте (я об этом забыл, когда проводил данный эксперимент!) и запустите приложение. Вы, как и ожидалось, увидите страницу Bing
(рис 3.1) Использование возможности, доступ к которой контролируется брокером, наподобие геолокации, внутри веб-контекста, приводит к появлению ошибки
Подобные возможности заблокированы, так как веб-контент, загруженный в iframe, может легко осуществить переход на любые другие страницы. Со страницы карт Bing, изображенной выше, пользователь может перейти на домашнюю страницу поисковой системы Bing, выполнить поиск, и затем перейти на любое количество непроверенных, потенциально опасных, страниц. В любом случае, эти страницы могут запрашивать доступ к критически важным ресурсам, и если это приведет к показу обычного вопроса для пользователя, который выводит приложение, пользователи могут быть обмануты при предоставлении подобного доступа.
К счастью, если вы хорошо попросите, Windows позволит вам включить эти возможности для веб-страниц, о которых знает приложение. Всё, что для этого нужно - письменные показания, под присягой, подписанные вами и шестнадцатью свидетелями… Ладно, я шучу! Вам просто нужно добавить то, что называется правила URI содержимого приложения (application content URI rules) в ваш манифест. Каждое правило заявляет о том, что контент, доступный по некоторому URI, известен приложению, и оно доверяет ему. Таким образом, этот контент может действовать от имени приложения. Вы так же можете исключать URI, что обычно используется для исключения определенных страниц, которые могли бы быть включены в другое правило.
Подобные правила создаются на закладке URI содержимого (Content URI) редактора манифеста в Visual Studio, как показано на Рис. 3.2. Каждое правило должно содержать точный URI, по которому может быть сделан запрос, такой, как http://www.bing.com/maps/ . Как только мы добавим это правило (как в полном упражнении для этой лекции, "ContentURI"), картам Bing разрешено будет использовать геолокацию. Когда они попытаются это сделать, отобразится диалоговое окно (Рис. 3.3), как тогда, когда приложение само пытается выполнить подобное действие. (Примечание. При выполнении в отладчике, пример "ContentURI" может показать исключение Отсутствие прав доступа (Permission Denied) при запуске. Если это произошло, нажмите Продолжить (Continue) в Visual Studio, так как это не влияет на запуск приложения вне отладчика).
(рис 3.2) Добавление URI содержимого в манифест приложения; содержимое текстовых полей сохраняется при сохранении манифеста. Кнопка Добавить новый URI (Add New URI) создаёт набор элементов управления, с помощью которых можно добавлять дополнительные правила
(рис 3.3) Когда правило URI содержимого должным образом настроено, веб-контент в iframe действует так же, как приложение, что говорит о том, почему правила URI содержимого необходимы для защиты пользователя от страниц, неизвестных приложению, которые, в противном случае могли бы обмануть пользователя, получая доступ к критически важным ресурсам
Так как мы говорим здесь об iframe, вот несколько дополнительных советов, которые вы можете счесть полезными при работе с этим элементом. Во-первых, чтобы предотвратить выделение, настройте стиль элемента с помощью -ms-user-select: none (http://msdn.microsoft.com/library/windows/apps/hh779846.aspx) или установите его свойство style.msUserSelect в значение none с помощью JavaScript. Во-вторых, некоторые веб-страницы содержат код, который препятствует их загрузке в iframe, в таком случае, страница будет открыта в стандартном браузере, а не в iframe. Если эта страница важна для вашего приложения, вам нужно поработать с владельцем страницы для создания альтернативной страницы, которая предназначена специально для вас. В-третьих, так как плагины в приложениях для Магазина Windows не поддерживаются, они так же не будут загружены для страницы, которая загружена в iframe. В итоге, загружать в приложение веб-содержимое, которое не принадлежит приложению - дело рисковое.
Далее, поддержка iframe не предназначена для того, чтобы позволить вам строить приложение исключительно из удалённых веб-страниц. Раздел 2.4. "Сертификационных требований к приложениям для Windows 8" (http://msdn.microsoft.com/library/windows/apps/hh694083.aspx), фактически, прямо запрещает приложения, которые являются веб-сайтами - основная функциональность приложения должна содержаться внутри приложения, то есть, подразумевается, что она не должна реализовываться средствами веб-сайта, загруженного в элемент iframe. Несколько ключевых причин для этого - то, что веб-сайты обычно не очень хорошо настроены для сенсорного взаимодействия (что нарушает требование пункта 3.5.) и часто не работают нормально в прикрепленном режиме (нарушение требования 3.6.). В итоге, чрезмерное использование веб-контента означает, что приложение не пройдёт сертификацию в Магазине Windows.
Как мы уже видели, схема ms-appx[-web]:/// позволяет приложению ссылаться в элементах iframe на страницы, которые существуют внутри пакета приложения или в веб. Встаёт вопрос: может ли приложение сослаться на содержимое в локальной файловой системе, которое существует за пределами пакета, такое, как динамически создаваемый файл в папке данных приложения? Быть может, приложение использует протокол file:// для адресации содержимого или доступа к нему?
Как бы не хотелось мне сказать вам, что это просто работает, ответ несколько неоднозначен. Во-первых, протокол file:// полностью заблокирован при проектировании системы по различным причинам, связанным с безопасностью, даже для папок с данными приложения, к которым вы обладаете полным доступом. (Другие обычные протоколы так же не поддерживаются в URI iframe src.) К счастью, имеется заменитель, ms-appdata:///, который удовлетворяет часть потребностей. Внутри локального контекста приложения, ms-appdata:/// это - короткое имя для папки с данными приложения, где существуют папки локальных, перемещаемых, временных данных. Итак, если вы создали изображение, названное image65.png в папке локальных данных приложения, вы можете сослаться на него, используя команду ms-appdata:///local/image65.png, и похожим образом - для roaming и temp, где URI может быть включено в CSS, как, например, background.
К несчастью, есть оговорка, как это обычно бывает с контейнером приложения. Она заключается в том, что ms-appdata может быть использована только для ресурсов, а именно, это может быть атрибут src для элементов img (изображение), video (видео) и audio (аудио). Данный подход нельзя использовать для загрузки HTML-страниц, таблиц стилей CSS или JavaScript, не подходит он и для целей навигации (iframe, гиперссылки и так далее). Это так, потому что не представляется возможным создания суб-изолированной среды для подобных страниц, а без этого страница, загруженная с помощью ms-appdata://, получит полный доступ к приложению.
Можете ли вы выполнять динамическую генерацию страниц? Да: вам нужно загрузить содержимое файла и обработать его вручную, вставив в DOM посредством свойства innerHTML или чего-то подобного. Вы можете получить доступ к папкам с данными приложения, воспользовавшись API Windows.Storage.ApplicationData. Для загрузки и рендеринга полной HTML-страницы, нужно, чтобы вы обработали все внешние ссылки, разобрались бы со скриптами, но сделать это можно - если вы действительно этого хотите.
Похожий вопрос касается возможности динамической генерации и исполнения скриптов. Ответ снова подразумевает ограничения. Да, вы можете взять строку JavaScript и передать её в функцию eval или exeScript. Однако, учтите, что требования сертификации Магазина Windows прямо запрещают выполнять это со скриптами, полученными из удалённых ресурсов в локальном контексте (раздел 3.9. требований). Другое предостережение заключается в том, что для подобного кода применяется автоматическая фильтрация, которая предотвращает внедрение скриптов (и другого опасного кода) в DOM через свойства вроде innerHTML и outerHTML, и методы, такие, как document.write и DOMParser.parseFromString. Но есть ситуации, когда вы, как разработчик точно знаете, что делаете, наслаждаясь жонглированием бензопилами и горящими кинжалами, и, таким образом, хотите обойти подобные ограничения, особенно - используя библиотеки сторонних разработчиков (смотрите врезку ниже). Понимая это, Microsoft предоставляет механизм для того, чтобы сознательно всё это обойти: MSApp.execUnsafeLocalFunction. Подробности об этом вы можете найти в материале "Разработка безопасных приложений", (http://msdn.microsoft.com/library/windows/apps/hh849625.aspx), который, помимо этой темы, содержит информацию еще по некоторым вещам, которые я сюда не включал. Один из подразделов этого документа - касается различных вариаций атрибута sandbox для iframe. Так же, для того, чтобы лучше разобраться в этом, можете посмотреть пример "JavaScript iframe sandbox attribute sample" (http://code.msdn.microsoft.com/windowsapps/JavaScript-iframe-sandbox-0f077ece)
Как ни странно, WinJS, на самом деле, упрощает жонглирование опасными предметами! WinJS.Utilities.setInnerHTMLUnsafe, setOuterHTMLUnsafe, и insertAdjacentHTMLUnsafe- это "обёртки" для вызова методов DOM, которые могут отфильтровать опасное содержимое.
Рассказав всё это (вы ведь любите углубляться в детали?), давайте рассмотрим пример использования ms-appdata. Скорее всего, этот механизм вы будете широко использовать в своих приложениях.
В целом, приложения для Магазина Windows могут использовать библиотеки наподобие jQuery, Prototype, Dojo и так далее, как сказано в лекции 1. Тем не менее, существуют некоторые ограничения и оговорки.
Во-первых, так как страницы локального контекста не могут загружать скрипты из удалённых источников, приложение обычно нуждается во включении подобных библиотек в свой пакет, если не планируется использовать эти возможности только в веб-контексте. WinJS, заметьте, не нужно встраивать в приложение, так как он предоставляется Магазином Windows, но подобные "пакеты фреймворков" не поддерживаются для других библиотек в Windows 8.
Во-вторых, изменения в DOM API и ограничения пакета приложения могут повлиять на библиотеку. Например, библиотечные функции, использующие window.alert, работать не будут. Библиотека, кроме того, не может загружать другую библиотеку из удалённого источника в локальный контекст. Важно отметить, что всё в библиотеке, что подразумевает более высокий уровень доверия, чем обеспечивает контейнер приложения (например, открытый доступ к файловой системе) будет сопряжено с проблемами.
Самая распространённая проблема возникает, когда библиотека пытается добавить элементы или скрипты в DOM (как с помощью innerHTML), это - широко распространённый подход для веб-приложений, который, как правило, не разрешен внутри контейнера приложения. Например, попытка создать виджет для выбора даты jQuery ($("myCalendar").datepicker()) выдаст такого рода ошибку. Вы можете обойти эту проблему на уровне приложения, заключив вышеприведенный код в MSApp.execUnsafeLocalFunction, но это не решит проблем с внедрением кода, которое происходит из более глубокого уровня библиотеки. В случае с примером с jQuery, который здесь показан, элемент управления может быть создан, но щелчок по нему выдаст другую ошибку.
В итоге, вы можете использовать библиотеки сторонних разработчиков, понимая, что обычно они написаны, исходя из предположений, не всегда применимых к контейнеру приложения. Со временем, конечно, появятся версии библиотек, полностью совместимые с Windows 8.
Отлично! Пережив семь страниц эзотерики, давайте поиграем с настоящим кодом и вернемся к приложению "Here My Am!", которое мы написали в лекции 2. Эта программа использует удобный метод URL.createObjectURL для показа изображения, полученного с камеры в элементе img:
captureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo)
.done(function (capturedFile) {
if (capturedFile) {
that.src = URL.createObjectURL(capturedFile);
}
});
Всё это замечательно: мы просто берем адрес, полагая, что изображение хранится где-то в то время когда мы сформировали URI. На самом деле, фотографии (и видео), захваченные с помощью соответствующего API камеры, просто хранятся во временном файле. Если вы установите точку останова в отладчике и посмотрите на capturedFile, вы увидите, что там находится уродливый путь к файлу, наподобие C:\Users\kraigb\AppData\Local\Packages\ ProgrammingWin8-JS-CH3- HereMyAm3a_5xchamk3agtd6\TempState\picture001.png. Ничего себе! Не выглядит дружественно, да и типичный пользователь вряд ли захочет видеть подобное.
В случае с приложением, подобным данному, скопируем данный временный файл в более подходящее место, для того, чтобы разрешить пользователю, например, выбирать одну из ранее сделанных фотографий (так, как мы сделаем в лекции 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript". Мы создадим копию файла в папке локальных данных приложения и используем ms-appdata для установки img src на данное расположение файла. Начнём с вызова captureUI.captureFileAsync, как и ранее.
//Для использования в promise-вызовах,
// объединенных в цепочку
var capturedFile = null;
captureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo)
.then(function (capturedFileTemp) {
//Убедитесь в том, что проверили правильность полученных данных;
//это может быть null, если пользователь отменит операцию.
if (!capturedFileTemp) { throw ("no file captured"); }
Обратите внимание на то, что вместо вызова done для получения promise-результатов, мы используем then. Это происходит потому, что нам нужно построить цепочку из асинхронных операций и then позволяет информации об ошибках распространяться по цепочке, как мы увидим в следующем разделе. В любом случае, как только мы получим результат в captureFileTemp (файл расположен в подобной, странно выглядящей, папке), затем мы откроем или создадим папку "HereMyAm" внутри папки с локальными данными приложения. Это может быть сделано с помощью Windows.Storage.ApplicationData.current.localFolder, что позволяет нам получить объект Windows.Storage.StorageFolder, который даёт нам метод createFolderAsync:
//Для демонстрации использования ms-appdata usage, скопируем StorageFile в папку HereMyAm
//распложенную в папке appdata/local, и используем ms-appdata для того, чтобы
//обратиться к ней.
var local = Windows.Storage.ApplicationData.current.localFolder; capturedFile = capturedFileTemp;
return local.createFolderAsync("HereMyAm", Windows.Storage.CreationCollisionOption.openIfExists);
})
.then(function (myFolder) {
//Снова, проверяем правильность результата операции
if (!myFolder) { throw ("could not create local appdata folder"); }
Предполагая, что папка создана успешно, myFolder будет содержать другой объект StorageFolder. Затем мы используем его в качестве целевого параметра для файлового метода copyAsync, который, в качестве второго параметра, принимает имя файла. Для этого имени мы просто используем исходное имя, добавив к нему дату и время (заменив двоеточия на дефисы, чтобы имя файла было корректным):
//Присоединяем время создания файла (следует избегать коллизий,
//но нужно заменить двоеточия)
var newName = capturedFile.displayName + " - "
+ capturedFile.dateCreated.toString().replace(/:/g, "-") + capturedFile.fileType;
return capturedFile.copyAsync(myFolder, newName);
})
.done(function (newFile) {
if (!newFile) { throw ("could not copy file"); }
Так как это - последняя асинхронная операция в цепочке, мы используем метод promise-объекта done по причинам, о которых скоро поговорим. В любом случае, если копирование удалось, переменная newFile содержит объект StorageFile для копии, и мы можем сослаться на данный файл, используя URI ms-appdata:
lastCapture = newFile;
//Сохраним для целей общего доступа
that.src = "ms-appdata:///local/HereMyAm/" + newFile.name;
},
function (error) {
console.log(error.message);
});
Полный код вы можете найти в упражнении HereMyAm3a.
Конечно, мы можем использовать URL.createObjectURL с newFile, как ранее (убедившись в установке параметра { oneTimeOnly=true } для того, чтобы избежать утечек памяти). Несмотря на то, что это расходится с целями данного упражнения, работает это отлично (и нагрузка на память, в общем, та же самая, при использовании этих двух методов). На самом деле, нам нужно использовать этот подход, если мы скопируем изображения в библиотеку изображений пользователя. Для того, чтобы это сделать, просто замените Windows.Storage.ApplicationData.current.localFolder на Windows.Storage.KnownFolders.picturesLibrary и объявите возможность Библиотека изображений (Pictures Library) в манифесте. Оба API возвращают StorageFolder, поэтому оставшийся код выглядит так же, за исключением того, что мы используем URL.createObjectURL, так как ни ms-appdata://, ни file:// мы не можем использовать для доступа к библиотеке изображений. Упражнение HereMyAm3a содержит данный код в комментариях.
В предыдущем примере кода вы могли заметить, как мы передаем информацию об исключениях, когда мы не получаем ожидаемый результат в любой из асинхронных операций. Кроме того, у нас есть только один обработчик ошибок в конце конструкции, у нас имеется эта странная конструкция, возвращающая результат (promise-объект, отложенный результат) от каждой последующей асинхронной операции вместо того, чтобы обрабатывать promise-объекты то тут, то там.
Хотя, на первый взгляд это может выглядеть странным, подобный подход, на самом деле, является наиболее распространённым шаблоном для работы с последовательными асинхронными операциями, так как он работает лучше, чем более очевидный подход с использованием вложенных конструкций. Вложенность подразумевает вызов следующего асинхронного API внутри обработчика завершения предыдущего, каждый из них завершается оператором done. Вот как код из предыдущего примера может быть переписан с использованием такого подхода (посторонний код удалён для упрощения примера):
captureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo)
.done(function (capturedFileTemp) {
//...
local.createFolderAsync("HereMyAm", ...)
.done(function (myFolder) {
//...
capturedFile.copyAsync(myFolder, newName)
.done(function (newFile) {
})
})
});
Единственное преимущество такого подхода заключается в том, что каждый обработчик завершения будет иметь доступ ко всем переменным, объявленным ранее. А вот недостатков у него довольно много. С одной стороны, здесь, между асинхронными вызовами, обычно достаточно много промежуточного кода, что делает структуру выглядящей неопрятно. Важнее то, что обработка ошибок значительно усложняется. Когда promise-вызовы вложены друг в друга, обработку ошибок нужно проводить на каждом из уровней. Если ошибка возникнет на одном из внутренних уровней, обработчик на внешнем уровне не сможет её обработать. Таким образом, каждый promise-вызов нуждается в собственном обработчике ошибок, что превращает базовую структуру подобного кода в нечто, напоминающее спагетти:
captureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo)
.done(function (capturedFileTemp) {
//...
local.createFolderAsync("HereMyAm", ...)
.done(function (myFolder) {
//...
capturedFile.copyAsync(myFolder, newName)
.done(function (newFile) {
},
function (error) {
})
},
function (error) {
});
},
function (error) {
});
Не знаю, как вы, а я теряюсь во всех этих } и ) (хотя очень старался вспомнить занятия по LISP в колледже), здесь непросто увидеть, какая функция обработки ошибок применяется к конкретному асинхронному вызову.
Объединение promise-вызовов в цепочку решает все эти проблемы, в обмен на небольшую плату в виде необходимости объявления нескольких дополнительных переменных за пределами цепочки. При объединение в цепочку, вы выполняете команду return для следующего promise-объекта в каждом из обработчиков завершения, вместо того, чтобы дополнять его вызовом done. Это позволяет вам выравнивать все асинхронные вызовы лишь однажды, и даёт эффект распространения ошибок по цепочке. Когда в promise-вызове произошла ошибка, вы видите, что то, что возвращается, является promise-объектом, и если вы вызовете метод then (но не done - смотрите следующий раздел), снова будет возвращён другой promise-объект, содержащий ошибку. В результате, любые ошибки быстро доходят по цепочке до первого доступного обработчика ошибок, что позволяет вам иметь лишь один обработчик ошибок в самом конце:
captureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo)
.then(function (capturedFileTemp) {
//...
return local.createFolderAsync("HereMyAm", ...);
})
.then(function (myFolder) {
//...
return capturedFile.copyAsync(myFolder, newName);
})
.done(function (newFile) {
},
function (error) {
})
Для моих глаз (и моего ума) эта структура кода кажется более чистой - и такой код легче отлаживать и поддерживать. Если хотите, вы даже можете завершить цепочку вызовом done(null, errorHandler), заменив предыдущий done на then:
captureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo)
//...
.then(function (newFile) {
})
.done(null, function (error) {
})
})
И, наконец, немного об отладке promise-вызовов, объединенных в цепочку (или вложенных, если уж на то пошло). Каждый шаг включает асинхронную операцию, поэтому вы не можете просто применить пошаговое исполнение, как это делается с синхронным кодом (в противном случае, вы окажетесь глубоко в WinJS). Вместо этого, установите точку останова на первой строке внутри каждого обработчика завершения и на первой строке функции обработки ошибок, которая находится в конце. Когда каждая из точек останова сработает, вы можете пошагово исполнить обработчик завершения. Когда вы дойдёте до следующего асинхронного вызова, нажмите на кнопку Далее (Continue) в Visual Studio, в итоге сможет выполниться асинхронная операция, после которой сработает точка останова в следующем обработчике завершения (или точка останова в обработчике ошибок).
Хотя обрабатывать ошибки в конце цепочки promise-вызовов - это обычная практика, как показано в коде выше, вы можете использовать обработчик ошибок в любом месте цепочки - и then, и done принимают одинаковые аргументы. Если на данном уровне возникает исключение, оно будет обработано ближайшим внутренним обработчиком ошибок.
Это приводит нас к различию между then и done. Во-первых, then возвращает еще один promise-объект, тем самым позволяя объединять команды в цепочки, в то время как done возвращает undefined, поэтому он должен быть в конце цепочки. Во-вторых, если исключение возникает внутри асинхронной операции с методом then и на данном уровне нет обработчика ошибок, информация об ошибке сохраняется в promise-объекте, который возвращает then. В противоположность этому, если done видит исключение и не имеет обработчика ошибок, он направляет исключение в цикл событий (event loop) приложения. Оно обходит любые локальные (синхронные) блоки try/catch, хотя вы можете перехватить их с помощью обработчиков WinJS.Application.onerror и window.onerror. (Последний получит ошибку, если первый её не обработал). Если вы этого не сделаете, приложение будет остановлено и информация об ошибке будет отправлена в Магазин Windows и появится на информационной панели. По этой причине мы рекомендуем, чтобы вы реализовывали обработчик WinJS.Application.onerror.
На практике, это означает, что если вы завершите цепочку promise-вызовов
С promise-объектами вы можете делать и многое другое, кстати, вроде комбинирования их, отмены и так далее. Мы вернемся к этому в конце данной лекции.
Когда идёт разговор об исключениях и обработке ошибок, разработчиков обычно огорчает то, что команды startLog (http://msdn.microsoft.com/en-us/library/windows/apps/hh701617.aspx), stopLog (http://msdn.microsoft.com/en-us/library/windows/apps/hh701626.aspx) и formatLog (http://msdn.microsoft.com/en-us/library/windows/apps/hh701587.aspx) из пространства имен WinJS.Utilities (http://msdn.microsoft.com/en-us/library/windows/apps/br229783.aspx), которые обеспечивают дополнительную функциональность на базе console.log. Думаю, вы сможете узнать об этом из документации, однако, считаю важным довести информацию о них до вашего сведения.
Другая функция DOM API, которой вам может захотеться воспользоваться - это window.close. Вы можете пользоваться ей во время разработки, но в опубликованном приложении Windows воспринимает её как аварийное завершение программы и генерирует в ответ отчёт об ошибке. Этот отчёт появится в информационной панели Магазина Windows для вашего приложения, с сообщением о том, что вам не следует пользоваться данной функцией! В конце концов, приложения для Магазина Windows не должны предоставлять собственных средств для закрытия приложения, как описано в разделе 3.6. сертификационных требований.
Однако, возможна ситуация, когда опубликованное приложение должно завершить работу в ответ на неисправимое происшествие. Хотя вы можете использовать для этого команду window.close, лучше воспользоваться командой MSApp.terminateApp, так как она позволяет вам, кроме того, включить информацию о произошедшей ошибке. Эти подробности будут показаны в информационной панели Магазина Windows, упрощая диагностику проблемы.
В дополнение к информационной панели Магазина Windows, вам следует ознакомиться со средством просмотра событий Windows (Windows Event Viewer)
Для того, чтобы активировать эту возможность, для начала вам нужно перейти к разделу Журналы приложений и служб, развернуть ветку Microsoft > Windows > AppHost, щёлкнуть левой кнопкой мыши по элементу Администратор (Admin) для того, чтобы выделить его (это важно), после чего щёлкнуть по элементу Администратор правой кнопкой мыши и выбрать Вид > Отобразить аналитический и отладочный журналы (View > Show Analytic and Debug logs) для включения вывода полной информации, как показано на Рис. 3.4. Это включит отслеживание ошибок и исключений. Затем щёлкните правой кнопкой пункт AppTracing (так же в разделе AppHost) и выберите пункт Включить журнал (Enable Log). Это позволит отслеживать ваши вызовы console.log, так же, как и другую диагностическую информацию, поступающую от хост-процесса приложения.
(рис 3.4) События хост-процесса приложения, такие, как необработанные исключения и ошибки загрузки, можно найти в средстве просмотра событий
Мы уже говорили о диалоговом окне Исключения в Visual Studio в лекции 2. Вернитесь к Рис. 2.16. Для каждого типа JavaScript-исключений это диалоговое окно предоставляет два флага, названные Вызванное (Thrown) и Не обработанное пользовательским кодом (User-unhandled). Установка флага Вызванное приведет к отображению диалогового окна в отладчике (Рис. 3.5), когда будет вызвано исключение, независимо от того, было ли оно обработано и до того, как сработают любые из ваших обработчиков событий. Если у вас есть обработчики событий, вы можете без проблем нажать на кнопку Продолжить (Continue) в диалоговом окне и ошибка будет передана вашим обработчикам ошибок. (В противном случае работа приложения завершится). Если вы, вместо этого нажмёте на кнопку Остановить отладку (Break), вы можете увидеть подробности об исключении в окне Локальные (Locals), как показано на Рис. 3.6.
(рис 3.5) Диалоговое окно исключения в Visual Studio. Как показывает диалоговое окно, можно безопасно нажать на кнопку Продолжить (Continue), если в вашем приложении есть обработчик ошибок; в противном случае работа приложения завершится. Обратите внимание на флаг в этом окне, который позволяет быстро включить опцию Вызванное (Thrown) для данного типа исключений в диалоговом окне Исключения
(рис 3.6) Информация в окне Visual Studio Локальные (Locals), когда вы останавливаете работу приложения при исключении
Опция Не обработанное пользовательским кодом (User-unhandled) (включенная для всех типов исключений по умолчанию) отобразит похожее диалоговое окно всякий раз, когда исключение попадёт в цикл событий, показывая то, что оно не было обработано в функции обработки ошибок приложения (в "пользовательском" коде - с точки зрения системы).
Обычно вы включаете параметр Вызванное только для тех исключений, которые вы собираетесь обрабатывать. Включение их всех может привести к сложностям при работе с приложением. Вы можете сделать это в качестве эксперимента и затем оставить данный параметр установленным лишь для тех исключений, которые вы ожидаете перехватить. Оставьте параметр Не обработанное пользовательским кодом для всех остальных исключений. На самом деле, если у вас нет особых причин не делать этого, убедитесь в том, что параметр Не обработанное пользовательским кодом включен у группы Ошибки времени выполнения JavaScript (JavaScript Runtime Exceptions), так как эта установка включает в себя все исключения, даже не перечисленные. При таком подходе вы можете перехватить (и исправить) любое исключение, которое может внезапно завершить работу приложения, а это кое-что из того, с чем вашим пользователям никогда не следует сталкиваться.
Для начала, позвольте мне поздравить вас с тем, что вы продвинулись так далеко в этой лекции, полной подробностей. В качестве награды, давайте поговорим кое о чем гораздо более осязаемом и привлекательном: о том, как активируется приложение, о последовательности действий, выполняемых при его запуске. Это может случиться множеством способов, с плитки на Начальном экране, с помощью контракта, при ассоциации с типом файлов или схемой URI. Во всех этих случаях активации вы будете писать много кода для инициализации структур данных, перезагрузки ранее сохранённого состояния и делать всё для того, чтобы предоставить пользователям хороший опыт взаимодействия с приложением.
Говоря об активации приложения, мы, на самом деле, должны сделать шаг назад, во время до того момента, когда будет загружен хост-процесс приложения, к тому моменту, когда пользователь нажал на плитку приложения на Начальном экране или когда ваше приложение было запущено каким-то другим способом. Первое, что происходит, еще до того, как загружается или исполняется код приложения, это - показ Windows экрана-заставки приложения, состоящего из изображения и фонового цвета, которые заданы в вашем манифесте.
Экран-заставка, который показывается, как минимум, на 0.75 секунды, это не просто картинка , которая показывает пользователям что-то интересное, пока приложение запускается (что лучше, чем песочные часы). Она так же занимает то пространство, где будет запущено приложение, в итоге, прямо привлекая внимание пользователя к этому месту. Это может быть область заполняющего просмотра, наложенная на другое приложение область, выводимая чудо-кнопкой Общий доступ или Поиск, или область режима прикрепленного просмотра, если приложение запускается сразу в прикрепленном режиме просмотра. В течение этого времени, запускается экземпляр хост-процесса приложения, обрабатываются и выводятся ваши HTML- страницы с CSS и загружается, обрабатывается и исполняется ваш JavaScript-код, по пути вызывая события, как мы увидим в следующем разделе. Когда готова первая страница приложения, система убирает экран-заставку.
Экран-заставка, вместе с плиткой вашего приложения, это один из весьма важных путей для брендирования вашего приложения, поэтому убедитесь в том, что ваш художник (или художники) уделили этим вопросам максимум внимания. Есть и другие графические элементы и установки в манифесте, которые так же играют роль в брендинге и в общем виде приложения в системе, как показано ниже, в таблице. Особо отметьте то, что шаблоны в Visual Studio и Blend предоставляют некоторые материалы-заполнители по умолчанию, которые совершенно непривлекательны. Поэтому дайте прямо сейчас торжественную клятву, что вы никогда не будете загружать приложения в Магазин, когда в проекте всё еще присутствуют эти материалы! (За дополнительными сведениями обратитесь к документу: "Руководство и контрольный список для экранов-заставов", (http://msdn.microsoft.com/library/windows/apps/hh465338.aspx).
Вы можете видеть, что в таблице перечислено по несколько размеров для различных изображений, заданных в манифесте для поддержки различной плотности пикселей: для масштабирования в 100%, 140%, 180%, и даже несколько для 80% (не пренебрегайте последним, он, как правило, используется для большинства настольных мониторов). И, в то время, как вы можете просто предоставить одно изображение в масштабе 100% для каждого из перечисленных элементов, практически гарантированно, что версия с увеличенным масштабом графических элементов будет выглядеть не очень хорошо. Поэтому, почему бы не сделать ваше приложение выглядящим наилучшим образом? Уделите время на то, чтобы осознанно создать каждый отдельный графический элемент.
| Закладка манифеста | Раздел | Элемент | Использование | Размеры изображений | ||
|---|---|---|---|---|---|---|
| 100% | 140% | 180% | ||||
| Упаковка | n/a | Значок | Изображение плитки/значка, используемое для приложения на странице описания в Магазине Windows. | 50x50 | 70x70 | 90x90 |
| Интерфейс приложения | n/a | Отображаемое имя пакета | Появляется при просмотре Начального экрана в режиме "все приложения" , в результатах поиска, при использовании чудо-кнопки Параметры, в Магазине Windows. | n/a | n/a | n/a |
| Плитка | Значок | Квадратное изображение плитки | 150x150 (+80% масштаб 120x120) | 210x210 | 270x270 | |
| Широкий значок | Необязательное изображение для широкой плитки. Если присутствует, отображается по умолчанию, но пользователь, если захочет, может использовать вместо него квадратное изображение. | 310x150 (+80% масштаб 248x120) | 434x210 | 558x270 | ||
| Мелкий значок | Плитка, которая используется при уменьшенном просмотре Начального экрана, при просмотре в режиме "все приложения", в панелях чудо-кнопок Поиск и Общий доступ, если приложение поддерживает соответствующие контракты в качестве целевого. Кроме того, используется на плитке приложения, если вы выбрали опцию показа логотипа приложения в левом нижнем углу плитки вместо его названия. | 30x30 (+80% масштаб 24x24) | 42x42 | 54x54 | ||
| Показывать имя | Задаёт, следует ли показывать имя приложения на плитке приложения (на всех, не показывать, только на стандартном или на широком значке). Установите этот параметр в значение "Нет значков", если плитка вашего приложения включает имя приложения. | n/a | n/a | n/a | ||
| Краткое имя | Необязательное. Если задано, используется в качестве имени приложения на плитке, заменяя Отображаемое имя пакета, так как это имя может быть слишком длинным для квадратной плитки. | n/a | n/a | n/a | ||
| Текст переднего плана | Цвет текста, которым выведено имя приложения на плитке, если применимо (смотрите Показывать имя). Возможны варианты Светлый (Light) и Тёмный (Dark). Соотношение контраста между этим текстом и цветом фона должен равняться 1.5. | n/a | n/a | n/a | ||
| Цвет фона | Цвет, используемый для прозрачных областей любых изображений на плитке, кроме того, задаёт фон по умолчанию для дополнительных плиток, фон для окон уведомлений, цвет кнопок в диалоговых окнах приложения, цвет границ для тех случаев, когда приложение обеспечивает функциональность контракта средства выбора файлов или контактов, цвет заголовков в панели параметров, цвет страницы приложения в Магазине Windows. Кроме того, задаёт фоновый цвет для экрана-заставки, если он не задан отдельно. | n/a | n/a | n/a | ||
| Уведомления | Эмблема | Отображается около уведомления индикатора событий для идентификации приложения на экране блокировки (редко, так как это требует объявления дополнительных возможностей) | 24x24 | 33x33 | 43x43 | |
| Заставка | Заставка | Когда приложение запускается, это изображение выводится в центре экрана на фоне, цвет которого задаёт Цвет фона. Если нужно, изображение может использовать прозрачность. | 620x300 | 868x420 | 1116x540 | |
| Цвет фона | Цвет, который заполняет основную область экрана-заставки. Если не задан, вместо него будет использован Цвет фона из Интерфейса приложения | n/a | n/a | n/a | ||
Обратите внимание на то, что в таблице 80% масштаб указан для графических элементов, используемых в особых случаях, таких, как режим низкого DPI (обычно, когда DPI меньше 130 и разрешение ниже, чем 2560х1440) и изображения в таком масштабе следует предоставлять вместе с другими. Кроме того, обратите внимание на дополнительные графические элементы, помимо Значка (Packaging Logo) (первый элемент в таблице), которые вам понадобятся перед отправкой приложения в Магазин Windows. Подробности вы найдете в материале "Выбор изображений для вашего приложения" (http://msdn.microsoft.com/library/windows/apps/hh846296.aspx), в разделе "Рекламные изображения".
Когда вы сохраняете эти файлы, добавьте .scale-80, .scale-100, .scale-140, и .scale-180 к именам файлов перед расширениями, как, например, в имени: splashscreen.scale-140.png. Это позволит вам и в манифесте, и в других местах приложения ссылаться на изображение, используя лишь базовое имя, такое, как splashscreen.png, и Windows автоматически загрузит подходящий вариант для текущего масштаба. В противном случае она будет искать изображение без суффикса. И не нужно дополнительного кода! Это показано в упражнении HereMyAm3b, где я добавил всю необходимую для брендинга графику (с некоторым дополнительным текстом на каждом рисунке для того, чтобы показать масштаб). Для того, чтобы протестировать эти различные графические элементы, используя кнопку Изменить разрешение (Set resolution/scaling) в симуляторе - обратитесь к Рис. 2.5 во второй лекции. Вы можете выбирать различные плотности пикселей на экране размером 10,6" (1366 x 768 =100%, 1920 x 1080 =140%, и 2560 x 1440 = 180%). Кроме того, вы увидите, что 80%-й масштаб используется при других установках экрана, в том числе - на 23" и 27". Во всех случаях, установки влияют на то, какое изображение будет использовано на Начальном экране и на экране-заставке, но обратите внимание на то, что вам может понадобиться выйти и перезапустить симулятор для того, чтобы изменение масштаба возымело действие.
Кроме того, вы должны знать о том, что полноцветные изображения фотографического качества не очень хорошо поддаются качественному уменьшению (Значок (Store Logo, Эмблема магазина), Мелкий значок (Small Logo, Мелкая эмблема)). Это одна из причин того, что данные значки обычно имеют простой дизайн в стиле Магазина Windows, который, кроме того, способствует уменьшению их размеров при сжатии. Это - отличное соображение, следуя которому можно сделать размер пакета вашего приложения меньше, когда вы создаёте больше версий изображений для различных контрастов и языков. Больше об этом будет в лекции 6 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой".
Так как хост-процесс приложения построен на тех же самых механизмах обработки и рендеринга, что и Internet Explorer, общая последовательность событий при активации более или менее похожа на последовательность, характерную для веб-приложений и наблюдаемую в браузере. На самом деле, скорее "более", чем "менее"! Когда вы запускаете приложение с помощью его плитки, вот что происходит с точки зрения Windows:
document.DOMContentLoaded. Вы можете использовать его для того, чтобы произвести дальнейшую инициализацию, в особенности, связанную с DOM (используется редко).
Windows.UI.WebUI.WebUIApplication.onactivated. Это обычно происходит, когда вы выполните все стартовые задачи, создадите экземпляр WinJS и элементов управления, инициализируете состояние и так далее.
activated завершится (если приложение не запросило отложенную операцию, как рассказано далее, в разделе "Задержанная активация").
body.onload. Обычно оно не используется в приложениях для Магазина Windows, хотя оно может использоваться кодом из библиотек сторонних разработчиков.Важной особенностью является то, что приложение снова может быть активировано по множеству различных причин, таких, как контракт или ассоциация, даже тогда, когда оно уже запущено. Как мы увидим в дальнейших лекциях, загружаемая страница (шаг 3), может варьироваться при реализации контракта, и если определенная страница уже загружена, она получит только событие Windows.UI.WebUI.WebUIApplication.onactivated, не получив другие.
Сейчас, однако, давайте сконцентрируемся на работе с этим базовым процессом загрузки и, так как вы обычно выполняете работу по инициализации внутри события activated, рассмотрим его структуру поближе.
Для того, чтобы оптимизировать генерирование байт-кода при обработке HTML, CSS и JavaScript-файлов, Магазин Windows требует, чтобы все .html, .css и .js-файлы были сохранены в кодировке UTF-8. Это установлено по умолчанию для всех файлов, создаваемых в Visual Studio или в Blend. Если вы импортируете активы из других источников, проверьте их кодировку. В диалоге Сохранить как (Save As) в Visual Studio (в Blend сейчас нет этой возможности), выберите Сохранить с кодировкой (Save with Encoding) и установите кодировку в Юникод (UTF-8, с сигнатурой), кодовая страница 65001 (Unicode (UTF-8 with signature) - Codepage 65001). Комплект сертификации приложений для Windows (Windows App Certification Kit) выдаст предупреждение, если обнаружит файлы не в этой кодировке.
В том же духе, минимизация JavaScript-кода не особенно важна приложениям для Магазина Windows. Так как пакет приложения загружается из Магазина Windows целиком и часто содержит другие активы, которые гораздо больше, чем ваши файлы с программным кодом, минимизация здесь не играет большой роли. Когда пакет установлен, генерация байт-кода подразумевает, что JavaScript-файлы в пакете уже обработаны и оптимизированы, в итоге, минимизация не даёт дополнительного выигрыша в производительности.
Как мы видели в лекции 2, новый проект, создаваемый в Visual Studio или в Blend даёт вам следующий код в файле js/default.jd (некоторые комментарии удалены):
(function () { "use strict";
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
if (args.detail.previousExecutionState !==
activation.ApplicationExecutionState.terminated) {
// TODO: Это приложение было вновь запущено. Инициализируйте
// приложение здесь.
} else {
// TODO: Это приложение было вновь активировано после приостановки.
// Восстановите состояние приложения здесь.
}
args.setPromise(WinJS.UI.processAll());
}
};
app.oncheckpoint = function (args) {
};
app.start();
})();
Пройдёмся по этому коду для того, чтобы пересмотреть то, что мы уже знаем и завершить понимание структуры этого базового кода:
(function () { … })(); содержащая всё, это, основа, шаблон модуля JavaScript.
"use strict" предписывает интерпретатору JavaScript применить Строгий режим (Strict Mode) (http://msdn.microsoft.com/library/br230269.aspx), функцию ECMAScript 5. Это проверка для неоптимальных методов программирования, таких, как использование неявно объявленных переменных, в итоге, его стоит оставить там, где он есть.
var app = WinJS.Application; и var activation = Windows.ApplicationMode.Activation; оба создают существенно сокращенные псевдонимы для часто используемых пространств имен. Это обычный подход для упрощения множественных ссылок на одни и те же части WinJS или WinRT.
app.onactivated = function (args) {…} назначает обработчик событию WinJS.UI.onactivated, которое является оболочкой для события Windows.UI.WebUI.WebUIApplication.onactivated. В этом обработчике:args.detail.kind идентифицирует тип активации.
args.detail.previousExecutionState идентифицирует состояние приложения до активации, что определяет, нужно ли перезагружать состояние сеанса работы.
WinJS.UI.processAll создаст экземпляры элементов управления WinJS - то есть, элементы, которые содержат атрибут data-win-control , как мы рассмотрим в лекции 4, "Элементы управления, их стилизация и привязка данных".
args.setPromise указывает Windows ждать до тех пор, пока завершится WinJS.UI.processAll, прежде чем убирать экран-заставку (смотрите раздел "Задержанная активация" дальше в этой лекции).app.oncheckpoint получает пустой обработчик в шаблоне: мы остановимся на этом в разделе "Переход между событиями жизненного цикла приложений".
app.start() (WinJS.Application.start()) инициирует обработку событий, которые WinJS поставил в очередь при запуске.Обратите внимание на то, как мы не обрабатываем напрямую любые события, запускаемые Windows, наподобие DOMContentLoaded или Windows.UI.WebUI.WebUIApplication.onactivated. Может быть, мы просто игнорируем эти события? Вовсе нет: один из удобных сервисов, которые WinJS предоставляет посредством WinJS.UI.Application - это упрощённая структура активации и других событий времени жизни приложения. Совершенно необязательно, но очень полезно.
В случае со start, например, происходит пара вещей. Во-первых, объект WinJS.Application прослушивает различные события, которые исходят из различных источников (DOM, WinRT и других) и собирает их в один объект, с которым вы регистрируете ваши собственные обработчики. Во-вторых, когда WinJS.Application принимает события активации, он не просто передает их в обработчики приложения, так как таких обработчиков, на самом деле, может и не быть. Поэтому он устанавливает эти события в очередь до тех пор, пока приложение не сообщит о том, что оно действительно готово, вызывая start. В этот момент WinJS проходит по очереди и запускает эти события. Вот и всё, что нужно сделать.
Как показывает код из шаблона, приложения обычно выполняют большую часть работы по инициализации в событии activated, где существует несколько потенциальных ветвей кода, прохождение по которым зависит от args.details (объект IActivatedEventArgs (http://msdn.microsoft.com/library/windows/apps/windows.applicationmodel.activation.iactivatedeventargs.aspx)). Если вы посмотрите документацию по WinJS.Application.onactivated (http://msdn.microsoft.com/library/windows/apps/br212679.aspx), вы увидите, что реальное содержимое args.details зависит от конкретного вида активации. Все виды активации, однако, имеют три общих свойства:
| args.details Свойства | Тип (в Windows.Application-Model.Activation) | Описание |
|---|---|---|
Kind | ActivationKind (http://msdn.microsoft.com/library/windows/apps/windows.applicationmodel.activation.activationkind.aspx) | Причина активации. Возможные значения launch (наиболее типично); search, shareTarget, file, protocol, fileOpenPicker, fileSavePicker, contactPicker, и cachedFileUpdater (для обслуживания контрактов); и device, printTask, Settings, cameraSettings (обычно используется приложениями, работающими с устройствами). Для каждого поддерживаемого типа активации, приложение будет иметь соответствующий путь инициализации. |
previousExecutionState | ApplicationExecutionState (http://msdn.microsoft.com/library/windows/apps/windows.applicationmodel.activation.applicationexecutionstate.aspx) | Состояние приложения до данной активации. Возможные значения notRunning, running, suspended, terminated, и closedByUser. Обработка случая terminated наиболее распространена, так как в данном случае вам потребуется восстановить ранее сохраненное состояние сеанса работы с приложением (смотрите "Переход между событиями жизненного цикла приложений"). |
splashScreen | splashScreen (http://msdn.microsoft.com/library/windows/apps/windows.applicationmodel.activation.splashscreen.aspx) | Содержит событие ondismissed для тех случаев, когда экран-заставка не отображается. Так же содержит свойство imageLocation (Windows.Foundation.Rect) с координатами отображения экрана-заставки, как отмечено в разделе "Расширенные экраны-заставки" |
Дополнительные свойства предоставляют соответствующие данные по активации. Например, свойство launch предоставляет titleId и arguments от дополнительных плиток (смотрите лекцию 2 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой"). Тип активации search (следующий наиболее часто используемый) предоставляет queryText и language, protocol предоставляет uri и так далее. Мы увидим, как использовать многие из этих свойств в соответствующем им контексте, и иногда они применимы не к default.html, а к другим страницам. То, что включено в шаблон (и то, что мы уже используем в приложении вроде "Here My Am!"), в основном, касается обработки обычного процесса запуска приложения с плитки (или внутри отладчика Visual Studio).
WinJS.Application не связан только с активацией - его цель - централизовать события от нескольких различных источников и превратить их в собственные события. Опять же, это позволяет приложению прослушивать события из одного источника (назначая обработчик с помощью addEventListener(<event>) или on<event>, поддерживается и то, и другое). Вот полный перечень этих событий и их источников (если поставлено в очередь, событие вызывается в WinJS.Application.start):
activated Ставится в очередь в локальном контексте для Windows.UI.WebUI.WebUIApplication.-onactivated. В веб-контексте, где WinRT неприменима, вместо этого ставится в очередь для DOMContentLoader (где тип запуска - launch, а previousExecutionState установлено в notRunning ).
Utilities.ready (http://msdn.microsoft.com/en-us/library/windows/apps/br211903.aspx), посредством которой вы можете задать обратный вызов для DOMContentLoaded. Это используется внутри WinJS, на самом деле, для того, чтобы гарантировать, что любое обращение к WinJS.UI.processAll будет обработано после DOMContentLoaded.activated.
ready Ставится в очередь после loaded и activated. Это событие - последнее из событий в цепочке событий активации.
error Вызывается, если возникает исключение при диспетчеризации другого события. (Если ошибка не обработана, она передаётся в window.onerror).
checkpoint Это событие сообщает приложению, когда следует сохранить состояние сеанса работы, которое понадобится при повторном запуске приложения из предыдущего состояния terminated. Это событие вызывается в ответ на событие документа beforeunload и на событие Windows.UI.WebUI.WebUIApplication.onsuspending.
unload Так же вызывается для beforeunload после того, как будет вызвано событие checkpoint.
settings Вызывается в ответ на Windows.UI.ApplicationSettings.SettingsPane.oncommandsrequested (Смотрите лекцию 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript").С большинством из этих событий (за исключением error и settings), args, которые вы получите, содержат метод, который называется setPromise. Если вам нужно произвести любую асинхронную операцию (вроде XmlHttpRequest), вы можете получить promise-объект для этой работы и передать его в setPromise вместо того, чтобы самостоятельно вызывать его then или done. WinJS, таким образом, не обработает следующее событие в очереди до тех пор, пока получение данного отложенного результата не будет выполнено. Отметим, чтобы быть честными, что нет разницы между этим подходом и обычным вызовом done у promise-объекта самостоятельно внутри событий loaded, ready и unload. А вот в случае с activated и checkpoint (в особенности в состоянии suspending) разница есть, так как Windows, в противном случае, полагает, что вы сделали всё, что нужно, как только вы возвратились из обработчика; подробнее об этом - в разделе "Задержанная активация". В итоге, если у вы выполняете асинхронные задачи в обработчиках этих событий, лучше всего использовать setPromise. Так как WinJS.UI.processAll - это сама по себе асинхронная операция, шаблон заключает её в оболочку из setPromise, в итоге, экран-заставка не исчезнет до тех пор, пока не буду созданы экземпляры элементов управления WinJS.
Я думаю, что вы сочтёте WinJS.Application удобным инструментом для ваших приложений, и это средство, кроме того, предоставляет еще некоторые возможности, как описано здесь: "Пространство имен WinJS.Application" (лекции 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript".
Еще нужно сказать о методах queueEvent и stop. Метод queueEvent помещает событие в очередь, оно попадёт на диспетчеризацию, когда существующие элементы выйдут из очереди, любым прослушивателем события, который вы установили с помощью WinJS.Application. События просто идентифицируются по строке, в итоге, вы можете ставить в очередь события с любым именем, которое вам нравится, и вызывать WinJS.Application.addEventListener с тем же самым именем в любом месте приложения. Это можно использовать для централизации пользовательских событий, вызываемых и при старте и в другое время в процессе выполнения приложения без создания отдельной глобальной функции для подобных целей. Кроме того, это мощное средство, с помощью которого раздельно объявленные, независимые компоненты, могут вызывать события, которые агрегирует один обработчик. (В качестве примера использования queueEvent смотрите Сценарий 2 в примере "Модель приложения" (http://code.msdn.microsoft.com/windowsapps/ApplicationModel-Sample-4be6575d)).
Что касается stop, это событие предоставляется для помощи во время модульного тестирования, во время которого вы можете симулировать различные последовательности активации, не перезапуская приложение и так или иначе симулировать нужные условия при перезапуске. Когда вы вызываете stop, WinJS удаляет прослушиватели событий, очищает очередь событий и очищает объект sessionState, но приложение продолжает работать. Затем вы можете вызвать queueEvent для того, чтобы заполнить очередь событий теми событиями, которыми хотите, и затем снова вызвать start для того, чтобы обработать эту очередь. Этот процесс может повторяться столько раз, сколько нужно.
Сейчас, хотя экран-заставка, отображаемый по умолчанию, позволяет привлечь внимание пользователя, пользователь не может наблюдать за ним, если тот же самый экран отображается действительно долго. На самом деле, "действительно долго" для типичного пользователя - это около 15 секунд, после чего он начинает полагать, что приложение зависло и возвращается на Начальный экран для того, чтобы запустить другое приложение, которое не будет бесполезно тратить его время.
Честно говоря, до тех пор, пока пользователь держит ваше приложение на переднем плане и не переключается, Windows даёт приложению всё необходимое ему время. Но когда пользователь переключается на Начальный экран или на другое приложение, вы ограничены 15-ю секундами. Если ваше приложение не на переднем плане, Windows даёт приложению 15 секунд на то, что приложение выполнит то, что есть в app.start и вызовет событие activated, в этот момент домашняя страница должна быть отрисована. В противном случае, бабах! Windows автоматически завершает ваше приложение.
Первое условие, конечно, заключается в том, чтобы оптимизировать процесс загрузки для того, чтобы он был как можно более быстрым. Но, всё таки, иногда приложениям, на самом деле, нужно больше, чем 15 секунд для того, чтобы запуститься, в особенности при первом запуске после установки, в итоге, ему следует дать пользователю знать о том, что что-то происходит. Например, пакет приложения может содержать изрядное количество сжатых данных, когда оно загружено из Магазина Windows, и которые должны быть распакованы в локальную файловую систему при первой загрузке, в итоге, последующие запуски будут происходить гораздо быстрее. Многие игры поступают так с графикой и другими ресурсами, оптимизируя локальное хранение данных под характеристики устройства. Другие приложения могут заполнять локальную IndexedDB из данных, хранящихся в JSON-файле или загружать и кэшировать данные из онлайнового сервиса.
Возможно, что пользователь попытается загрузить ваше приложение вскоре после перезагрузки системы, в таком случае система может быть загружена множеством дисковых операций. Если вы загружаете данные с диска при активации приложения, этот процесс может занять больше времени, чем обычно.
Во всех эти случаях, всегда, когда приложение рискует превысить лимит в 15 секунд, вам понадобится реализовать расширенный экран-заставку (extended splash screen). Это подразумевает скрытие вашей реальной домашней страницы за другим элементов div, который выглядит точно так же, как системный экран-заставка, однако, находится под контролем приложения, и, в итоге, способен отображать индикаторы прогресса или другие элементы интерфейса, пока происходит инициализация приложения.
В целом, Microsoft рекомендует, чтобы расширенный экран-заставка совпадал с системным для того, чтобы избежать мерцания и резкого изменения изображения. (Смотрите материал "Руководство и контрольный список для экранов-заставок" (http://msdn.microsoft.com/library/windows/apps/hh465338.aspx)). В этот момент многие приложения просто добавляют индикатор прогресса и сообщение вроде: "Пожалуйста, возьмите напиток, выполните несколько физических упражнений или насладитесь несколькими минутами медитации, пока мы всё загрузим". Соответствие системному экрану-заставке, однако, не означает, что расширенный экран-заставка должен действовать подобным образом. Многие приложения, стартуют с копией системного экрана-заставки и затем используют анимированную графику на одной из его сторон, чтобы освободить место для других элементов. Другие приложения плавно скрывают существующие изображения и запускают видео.
Создание плавного перехода - это цель объекта args.detail.splashScreen, включенного в событие activated. Этот объект (смотрите: "Windows.ApplicationModel.Activation.SplashScreen" (http://msdn.microsoft.com/library/windows/apps/windows.applicationmodel.activation.splashscreen.aspx)) содержит свойство imageLocation (типа Windows.Foundation.Rect), которое содержит данные о расположении и размере изображения для экрана-заставки. Так как ваше приложение может быть запущено на устройствах с экранами различных размеров, это подсказывает вам где расположить то же самое изображение на вашей странице, где начать анимацию, и/или где поместить объекты вроде сообщений и индикаторов прогресса, связанных с данным изображением.
Объект splashScreen, кроме того, предоставляет событие ondismissed, в итоге вы можете произвести специфические действия, когда системный экран-заставка исчезает и появляется первая страница вашего приложения. Обычно это удобно для того, чтобы начать анимацию на странице, начать проигрывание видео и так далее.
Для того, чтобы увидеть пример расширенного экрана-заставки, обратитесь к примеру "Экран-заставка" (http://code.msdn.microsoft.com/windowsapps/Splash-screen-sample-89c1dc78). Еще одна деталь, о которой важно упомянуть, заключается в том, что так как экран-заставка - это обычная страница в вашем приложении, она может быть помещена в различные режимы просмотра - в такие, как режим прикрепленного просмотра. В итоге, как и со всеми остальными страницами вашего приложения, убедитесь в том, что расширенный экран-заставка поддерживает эти состояния!
Как упоминалось ранее, как только вы вернетесь из события activated, Windows считает, что вы сделали всё, что нужно, при старте приложения. По умолчанию, таким образом, Windows удалит свой экран-заставку и сделает домашнюю страницу приложения видимой. Но что, если вам нужно завершить одну или более асинхронных операций до того, как домашняя страница будет действительно готова, например, завершить WinJS.UI.processAll?
Это, снова, то, для чего существует метод args.setPromise внутри события activated. Если вы установили для асинхронной операции отложенный результат, использовав setPromise, Windows будет ждать до тех пор, пока отложенный результат не будет получен, и только тогда скроет экран-заставку. Шаблоны используют этот метод для того, чтобы экран-заставка отображался до тех пор, пока не будет завершен WinJS.UI.processAll.
Так как setPromise просто ожидает окончание выполнения отдельной отложенной операции, как обработать несколько асинхронных операций? Вы можете сделать это парой способов. Во-первых, если вам нужно управлять последовательностью подобных операций, вы можете связать их в цепочку, так, как мы уже это умеем - просто убедитесь в том, что в конце цепочки promise-объектов находится тот, который служит аргументом для setPromise - не вызывайте его метод done (вместо этого, если нужно, используйте then)! Если последовательность исполнения неважна, но вам нужно, чтобы все операции завершились, вы можете скомбинировать эти отложенные результаты, используя команду WinJS.Promise.join, передавая результат в setPromise. Если вам нужно, чтобы завершилась лишь одна операция, вы, вместо вышеупомянутой, можете использовать WinJS.Promise.any. Команды join и any будут разъяснены в последнем разделе этой лекции.
Другой подход заключается в том, чтобы зарегистрировать более чем один обработчик с WinJS.Application.onactivated; каждый обработчик получит собственные аргументы события и собственную функцию setPromise, и WinJS объединит эти возвращенные promise-результаты с помощью WinJS.Promise.join.
Метод setPromise из WinJS, на самом деле, реализован с использованием более общего механизма отложенных операций из WinRT. Объект args, передаваемый в Windows.UI.WebUI.WebUIApplication.onactivated (событие WinRT), содержит небольшой метод, который называется getDeferral (технически - Windows.UI.WebUI.ActivatedOperation.getDeferral (http://msdn.microsoft.com/library/windows/apps/windows.ui.webui.activateddeferral.aspx)). Эта функция возвращает отложенный объект, который содержит метод complete, и Windows не скроет системный экран-заставку до тех пор, пока вы не вызовете этот метод (хотя, это не меняет тот факт, что пользователи нетерпеливы, и ваше приложение всё еще ограничено 15-секундным лимитом). Код может быть похож на этот:
//В обработчике события activated
var activatedDeferral = Windows.UI.WebUI.ActivatedOperation.getDeferral();
someOperationAsync().done(function () {
//После завершения инициализации activatedDeferral.complete();
}
Конечно, setPromise, в конечном счете, делает то же самое, и если вы прямо добавите обработчик события WinRT activated, вы сможете реализовать ту же задержку активации своими силами.
Для приложений и для тех, кто их публикует, идеальным миром был бы мир, в котором пользователи запускают приложения и остаются в них навсегда (и, без сомнения, совершают множество покупок в приложении!). Но в жестокой реальности это невозможно. Независимо от того, как вам хотелось бы, чтобы это было иначе, ваше приложение - не единственное, которое захочется запустить пользователю. В конце концов, зачем были бы нужны функции вроде общего доступа или закрепления контента, если бы несколько приложений не могли исполняться вместе? К лучшему, или к худшему, пользователи будут переключаться между приложениями, менять состояния просмотра, и, возможно, закрывать ваше приложения. Но то, что вы можете сделать - это направить силы в "лучшую" сторону уравнения, убедившись в том, что ваше приложение хорошо ведет себя в любом из этих обстоятельств.
Первое соображение касается фокуса (focus), который применим к элементам управления в вашем приложении и к приложению в целом. Здесь вы можете просто использовать стандартные события HTML blur и focus. Например, в игре в стиле "экшн", или в чём-то подобном, с таймером, постановка на паузу обычно происходит по событию blur, а запуск - по событию focus.
Похожее, но иное условие касается видимости (visibility). Приложение может быть видимым, но не иметь фокуса, как тогда, когда оно работает в прикрепленном режиме. В подобных случаях приложение может продолжать действия, наподобие анимации или обновления ленты новостей, но оно приостановит подобную активность, когда потеряет видимость (то есть, когда оно окажется в фоновом режиме). Для этого используйте событие visibilityChange (http://msdn.microsoft.com/library/windows/apps/hh441213.aspx) из DOM API и затем проверьте свойство visibilityState (http://msdn.microsoft.com/library/windows/apps/hh453385.aspx) объекта window или document, так же, как и свойство document.hidden. (Событие применимо и к видимости отдельных элементов). Изменение видимости, кроме того, отличное время для того, чтобы сохранить пользовательские данные, наподобие документов или состояния прохождения игры.
Приложение может узнать об изменении состояния просмотра (view state change) несколькими способами. Как показано в примере "Here My Am!", приложение обычно использует медиа-запросы (объявленные в CSS или посредством прослушивателей медиа-запросов в коде) для того, чтобы перенастроить макет и видимость элементов, что, на самом деле, единственное, на что должно влиять изменение состояния просмотра. (Опять же, изменение состояния просмотра никогда не изменяет режим работы приложения, только макет и видимость объектов). В любое время приложение может получить сведения о текущем режиме просмотра посредством Windows.UI.ViewManagement.ApplicationView.value. Эта команда возвращает одно из значений типа Windows.UI.ViewManagement.ApplicationViewState, а именно: snapped, filled, fullScreenLandscape и fullScreenPortrait; подробности вы можете найти в лекции 6.
Когда приложение завершает работу (пользователь провёл сверху вниз по экрану или нажал Alt+F4), важно отметить, что сначала приложение попадает во внеэкранный (скрытый) режим, приостанавливается, а потом закрывается, в итоге обычные DOM-события, наподобие unload, не применимы. Пользователь, кроме того, может остановить процесс вашего приложения через Диспетчер задач, однако, при таком стечении обстоятельств, никакие события в коде приложения не генерируются. Помните так же о том, что приложению не следует самому себя закрывать, как говорилось ранее, но оно может воспользоваться командой MSApp.terminateApp для того, чтобы закрыться в ответ на происшествия, последствия которых невозможно исправить.
Помимо фокуса, видимости и состояний просмотра есть три других критических момента в жизненном цикле приложения:
Windows.UI.WebUI.WebUIApplication.onsuspending, которое так же видимо через WinJS.Application.oncheckpoint. Приложение должно завершить обработку этого события за пять секунд, или Windows решит, что приложение зависло и завершит его (время!). В течение этого времени, приложения сохраняют переходящие во времени состояния сеанса работы и освобождают любые захваченные ранее монопольные (exclusive) ресурсы, такие, как файловые потоки или доступ к устройствам (смотрите материал "Приостановка работы приложения" (http://msdn.microsoft.com/library/windows/apps/hh465138.aspx)).
Windows.UI.WebUI.WebUIApplication.onresuming. (Оно не отображается посредством WinJS.Application, так как оно обычно не используется и у WinJS нет значения для его добавления). Мы скоро поговорим об этом подробнее в разделе "Данные от сервисов и WinJS.xhr", так как нужда в этом событии часто возникает при использовании сервисов. В дополнение к этому, если вы отслеживаете данные от сенсоров любого рода (вроде компаса, GPS-приёмника или сенсора ориентации в пространстве), возобновление приложения - это подходящее время для того, чтобы обновить эти данные. Кроме того, здесь вы можете проверить статус лицензии приложения и покупок внутри приложения, если вы используете пробную версию приложения или модель с ограничением по времени (смотрите лекцию 6 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой"). Кроме того, это время, когда вы можете захотеть обновить макет приложения (как мы увидим в лекции 6) к иному состоянию просмотра, чем то, в котором оно было приостановлено, так как возможно возобновление работы приложения напрямую в иное состояние просмотра или в другое разрешение экрана, как тогда, когда устройство было подключено к внешнему монитору. То же самое касается активации/деактивации команд работы с буфером обмена.
previousExecutionState, когда приложение перезапускается.Полезно знать, что вы можете симулировать эти состояния в отладчике Visual Studio, используя выпадающее меню панели инструментов, как показано на Рис. 3.7. Эти команды вызывают соответствующие события и установку значения previousExecutionState для следующего запуска приложения. (Отнеситесь с благодарностью к этим элементам управления. Было время, когда у нас их не было, и было очень неудобно отлаживать эти состояния приложения!).
(рис 3.7) Выпадающее меню в панели инструментов Visual Studio для симуляции приостановки, возобновления и завершения работы приложения
Мы уже вкратце обсудили эти состояния, но давайте посмотрим, как они связаны с запускаемыми событиями и со значением previousExecutionState, которое используется при следующем старте приложения. Это может выглядеть несколько сложным, поэтому переходы между состояниями показаны на Рис. 3.8, а в таблице ниже описано, как определяется значение previousExecutionState.
Значение previousExecutionState | Сценарии |
|---|---|
notRunning | Первый запуск после установки из Магазина Windows. Первый запуск после перезагрузки или выхода. Приложение запущено через примерно 10 секунд после того, как было закрыто пользователем (приблизительное время, необходимое для скрытия, приостановки, и полного завершения работы приложения; если пользователь перезапустит приложение быстрее, Windows предварительно немедленно завершит его работу, не завершая операции, выполняемые при приостановке). Приложение было остановлено с помощью Диспетчера задач во время выполнения или самостоятельно закрылось с помощью MSApp.terminateApp. |
running | Приложение уже исполняется и вызвано способом, отличающимся от запуска с помощью плитки, такого, как чудо-кнопки Поиск или Общий доступ, дополнительные плитки, всплывающее уведомление и реализация других контрактов. Когда приложение запущено и пользователь щёлкает по плитке, Windows просто переключается на уже работающее приложение без вызова событий активации (хотя, и focus, и visibilitychange вызываются). |
suspended | Приложение приостановлено и затем вызвано способом, отличающимся от запуска с помощью плитки (как показано выше, для running). В дополнение к событиям focus/visibility, приложение принимает событие resuming. |
terminated | Ранее приложение было приостановлено, после чего завершено Windows по причине нехватки ресурсов. Обратите внимание на то, что это не применимо к MSApp.terminateApp,так как приложение должно выполняться для того, чтобы вызвать данную функцию. |
closedByUser | Приложение было закрыто с помощью не прерванного жеста закрытия (проведение сверху вниз или Alt+F4). "Прерванное" закрытие происходит, когда пользователь возвращается к приложению в течение 10 секунд, в таком случае предыдущее состояние будет установлено в notRunning. |
(рис 3.8) Обработка событий жизненного цикла и значения previousExecutionState
Активировано, загружено и так далее (activated, load, etc.)
Выполняется (в памяти) (running (in memory))
Приостановка/контрольная точка (suspending/checkpoint)
Приостановлено (в памяти) (suspended (in memory))
Возобновление (resuming)
Нет событий, предыдущее состояние равно значению завершено только если Windows закрыла приложение ((no event) previous state == terminated only if Windows closed the app)
Не исполняется, закрыто пользователем или завершено (notRunning, closedByUser, or terminated)
Основной вопрос для приложения, конечно, не в том, чтобы определить значение previousExecutionState, а в том, что, на самом деле, делать с этим значением при активации. К счастью, эта история немного проще и кое-что мы видим в коде шаблона:
launch и предыдущее состояние - это notRunning или closedByUser, приложению следует запуститься с установками интерфейса по умолчанию и применить любые постоянные настройки и сохраненные состояния. В случае с closedByUser возможны сценарии, когда приложению следует выполнить дополнительные действия (такие, как обновление кэшированных данных) после того, как пользователь явным образом завершит работу приложения и оставит его на какое-то время закрытым.
launch и предыдущее состояние - terminated, приложению следует запуститься в том же самом состоянии, в котором оно было перед последней приостановкой.
launch и при других видах активации, которые включают дополнительные аргументы или параметры (как в случае с дополнительными плитками, всплывающими уведомлениями, контрактами), ему следует инициализироваться для обслуживания целей такого запуска, используя дополнительные параметры. Приложение может быть уже запущено, в итоге ему не обязательно инициализировать своё состояние по умолчанию.Именно по причине наличия второго из вышеперечисленных требований, приложения предоставляют структуру кода для этого случая вместе с обработчиком checkpoint. Мы рассмотрим в подробностях сохранение и перезагрузку состояний в лекции 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript". Основная идея заключается в том, что приложению следует, когда оно приостанавливается, если не раньше, сохранит любые передаваемые состояния сеанса работы, которые ему нужны для самовосстановления после остановки. Эти состояния включают в себя непереданные данные форм, позицию прокрутки контента, стек навигации по приложению и другие переменные. Это так потому, что хотя Windows может приостановить приложение и выгрузить его из памяти, пользователь всё еще воспринимает приложение работающим. Таким образом, когда пользователи активируют приложение снова для обычного использования (это будет скорее вид активации launch, чем активация посредством контракта), они ожидают, что приложение будет в том же состоянии, в котором было до этого. В течение времени, когда приложение приостанавливается, таким образом, ему нужно сохранить любые состояния, которые могут сделать вышеупомянутое возможным. Приложение затем восстанавливает это состояние, в том случае, если previousExecutionState равно terminated.
Для того, чтобы узнать подробности о том, где это важно при проектировании приложений, обратитесь к материалу "Руководство по приостановке и возобновлению работы приложений" (http://msdn.microsoft.com/library/windows/apps/hh465088.aspx). Ясно то, что если пользователь непосредственно закроет приложение с помощью Alt+F4 или жеста закрытия, будут вызваны события suspending и checkpoint, в итоге приложение сохранит состояния сеанса работы Однако, приложение автоматически завершает работу после приостановки и перезагрузка состояния сеанса работы не будет запрошена при повторном запуске, так как значение previousExecutionState будет notRunning или closedByUser.
Лучше всего, на самом деле, сохранять состояние сеанса работы при его изменении в течение времени выполнения приложения, таким образом, минимизируя объём работы, необходимый для обработки события suspending (где у вас есть всего пять секунд). Помните, что состояние сеанса работы (сессии) не включает данные, которые постоянно присутствуют от сессии к сессии, такие, как файлы пользователя, информация о наивысших достижениях в играх и параметры приложения, так как приложение всегда перезагружает или применяет подобные постоянные данные при каждом способе активации. Единственное, о чём вам стоит беспокоиться - это о поддержании иллюзии того, что приложение всегда запущено.
Вы всегда сохраняете состояние сеанса работы в папку с данными приложения или контейнеры параметров, которые предоставляет API Windows.Storage.ApplicationData (лекции 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript". Сейчас мне хотелось бы лишь указать на некоторые вспомогательные механизмы, которые предоставляет для этих целей WinJS.
Во-первых, это событие WinJS.Application.checkpoint, которое предоставляет единое удобное пространство для сохранения и состояния сеанса работы, и любых других постоянных данных, которые могут у вас быть, если вы еще не сохранили их.
Во-вторых, это объект WinJS.Application.sessionState. При нормальном старте приложения он представляет собой пустой объект, в который вы можете добавить любые свойства по своему желанию, в том числе - другие объекты. Обычный подход заключается в том, чтобы использовать sessionState напрямую, как контейнер для переменных. В событии checkpoint WinJS автоматически сериализует содержимое этого объекта (используя JSON.stringify) в файл, расположенный в папке локальных данных вашего приложения (это значит, что переменные в sessionState должны иметь строковое представление). Обратите внимание на то, что WinJS гарантирует то, что его собственный обработчик события checkpoint всегда вызывается после того, как ваше приложение получит это событие, вы можете быть уверены в том, что WinJS сохранит всё, что вы запишете в sessionState в любое время до того, как сработает команда выхода из вашего обработчика checkpoint.
Затем, когда приложение активируется с предыдущим состоянием - terminated, WinJS автоматически восстанавливает объект sessionState, в итоге, всё что вы в нём разместили снова будет доступным. Если вы использовали этот объект для хранения переменных, вам лишь нужно избегать записи в них значений по умолчанию при перезагрузке состояния сеанса работы.
В-третьих, если вы не хотите использовать объект sessionState, или у вас есть сессия, которая с ним не работает, объект WinJS.Application упрощает запись ваших собственных файлов без необходимости использования асинхронного API WinRT. В особенности, он предоставляет (как показано в документации (http://msdn.microsoft.com/library/windows/apps/br229774.aspx)), объекты local, temp и roaming, каждый из которых имеет методы readText, writeText, exists, и remove. Эти объекты работают с соответствующими им папками данных приложения и предоставляют упрощенное API для операций файлового ввода/вывода, как показано в Сценарии 1, примера "Модель приложения" (http://code.msdn.microsoft.com/windowsapps/ApplicationModel-Sample-4be6575d).
Последнее вспомогательное средство связано с механизмом отложенного исполнения, похожим на тот, который применяется при активации. Отложенные операции важны, так как Windows приостановит приложение, как только оно завершит обработку события suspending. Если вам нужно откладывание для асинхронных операций, аргументы события WinJS.Application.oncheckpoint предоставляют метод setPromise, который связан с более глубокими механизмами отложенного выполнения WinRT. Как и ранее, вы передаете promise-объект для асинхронной операции (или комбинации операций) методу setPromise, который, в ответ, вызывает отложенный метод complete, когда ожидаемые результаты будут получены.
На уровне WinRT, аргументы события suspending содержат экземпляр Windows.UI.WebUI.- WebUIApplication.SuspendingOperation (http://msdn.microsoft.com/library/windows/apps/windows.applicationmodel.suspendingoperation.aspx). Он предоставляет метод getDeferral, который возвращает отложенный объект с методом complete, как при активации.
Ух ты! Звучит заманчиво! Может быть, это хитрый способ обойти ограничение на исполнение приложений для Магазина Windows в фоновом режиме? Будет ли приложение исполняться вечно, если я запрошу отсроченную операцию, никогда не вызывая complete?
Не тут-то было, амиго. Примите мои извинения за то, что подарил вам волшебный миг восторга. С отсроченной операцией или нет, пять секунд - это наибольшее время, на которое вы можете рассчитывать. Тем не менее, вы можете в полной мере использовать преимущества этого времени, возможно, сначала выполнив критически важную асинхронную операцию (вроде сбрасывания кэша), и затем попытавшись произвести менее важные действия (вроде синхронизации с сервером), которые могут значительно улучшить опыт взаимодействия пользователя с приложением. Для подобных целей объект suspendingOperation так же содержит свойство deadline, имеющее тип Date, показывающее время в будущем, когда Windows принудительно приостановит приложение вне зависимости от наличия отсроченных операций. Как только первая операция завершится, вы можете проверить, имеется ли время на то, чтобы начать вторую, и так далее.
Базовая демонстрация использования отсроченных операций при приостановке, кстати, сделана в примере "Активация, возобновление работы, приостановка приложения" (лекции 1 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой". Пример управления состояниями приложения, в дополнение к обновлениям, которые мы внесем в нашу программу "Here My Am!" в следующем разделе, можно найти в Сценарии 3 примера "Модель приложения" ((http://code.msdn.microsoft.com/windowsapps/ApplicationModel-Sample-4be6575d).
Для того, чтобы показать основные приемы обработки состояний сеанса приложения, я внес некоторые изменения в "Here My Am!". Их можно найти в упражнении HereMyAm3c. У нас есть два набора данных, о которых мы беспокоимся: переменные lastCapture (объект StorageFile с изображением) и lastPosition (набор координат). Мы хотим быть уверенными в том, что сохранили их при приостановке приложения, в итоге, мы сможем подходящим образом использовать эти значения, когда приложение будет запущено с предыдущим состоянием terminated.
В случае с lastPosition мы можем просто поместить эти данные в объект sessionState (вставив app.sessionState.) в обработчик завершения getGeoPositionAsync:
gl.getGeopositionAsync().done(function (position) {
app.sessionState.lastPosition = {
latitude: position.coordinate.latitude,
e
longitude: position.coordinate.longitud
};
updatePosition();
}, function (error) {
console.log("Unable to get location.");
});
}
Так как нам нужно установить позицию на карте и отсюда, и из ранее сохраненных координат, я переместил данный участок кода в другую функцию, которая так же позволяет быть уверенным в том, что данные о местоположении есть в sessionState:
function updatePosition() {
if (!app.sessionState.lastPosition) {
return;
}
callFrameScript(document.frames["map"], "pinLocation", [app.sessionState.lastPosition.latitude, app.sessionState.lastPosition.longitude]);
}
Отметим так же, что app.sessionstate инициализируется по умолчанию пустым объектом, {}, поэтому lastPosition будет иметь значение undefined до определения местоположения. Это полезно нам при восстановлении состояния приложения. Вот как могут выглядеть, с учетом этого, условия с previousExecutionState:
if (args.detail.previousExecutionState !==
activation.ApplicationExecutionState.terminated) {
//Нормальный старт: инициализируем lastPosition с помощью API
//определения местоположения
} else {
//WinJS перезагружает здесь объект sessionState. Поэтому попытаемся отобразить на карте
//местоположение из сохраненных данных.
updatePosition();
}
Так как мы храним lastPosition в sessionState, эти данные автоматически сохраняются в WinJS.Application.checkpoint, когда приложение было запущено ранее. Когда мы перезапускаемся из состояния terminated, WinJS автоматически перезагружает sessionState; если ранее мы сохранили здесь данные, они будут загружены и updatePosition просто будет работать.
Вы можете проверить это, запустив приложение с данными изменениями и затем использовав команду Приостановить и завершить работу (Suspend and shutdown) на панели инструментов Visual Studio. Установите точку останова на вызов updatePosition выше и затем перезапустите приложение в отладчике. Вы увидите, что в этот момент sessionState.lastPosition уже инициализировано.
В случае с последним захваченным изображением, нам не нужно сохранять объект типа StorageFile, нам нужно сохранить лишь путь к файлу: мы копируем файл в папку локальных данных приложения (в итоге, файл сохраняется между сеансами работы) и можем просто использовать схему URI ms-appdata:// для того, чтобы сослаться на него. Когда мы захватываем изображение, мы просто сохраняем этот URI в sessionState.imageURL (имя свойства может быть произвольным) в конце цепочки promise-вызовов внутри capturePhoto:
app.sessionState.imageURL = "ms-appdata:///local/HereMyAm/" + newFile.name; that.src = app.sessionState.imageURL
Это значение так же будет перезагружено в соответствующем месте при перезагрузке, в итоге, мы можем просто инициализировать соответствующим образом img src:
if (app.sessionState.imageURL) {
document.getElementById("photo").src = app.sessionState.imageURL;
}
Этот код инициализирует отображаемое изображение из sessionState, но нам, кроме того, нужно инициализировать lastCapture для того, чтобы то же самое изображение было доступно для контракта Общий доступ. Для этого нам нужно, кроме того, сохранить полный путь к файлу, в итоге, мы можем повторно получить объект типа StorageFile посредством Windows.SttorageFile.getFileFromPathAsync (что не работает с URI ms-appdata://). В итоге, в capturePhoto:
app.sessionState.imagePath = newFile.path;
И при загрузке:
if (app.sessionState.imagePath) {
Windows.Storage.StorageFile.getFileFromPathAsync(app.sessionState.imagePath)
.done(function (file) {
lastCapture = file;
if (app.sessionState.imageURL) {
document.getElementById("photo").src = app.sessionState.imageURL;
}
});
Я поместил код для установки img.src внутрь обработчика завершения, так как нам нужно, чтобы изображение появлялось только в том случае, если мы можем получить доступ к StorageFile снова для целей общего доступа. В противном случае эти две функции приложения не будут синхронизированы.
При всём этом, отметим снова, что нам не нужно явно перезагружать эти переменные внутри ветви кода, соответствующей состоянию terminated, так как WinJS перезагружает sessionState автоматически. Если бы мы управляли состоянием приложения напрямую, например, сохраняли бы некоторые переменные среди перемещаемых параметров внутри события checkpoint, мы бы перезагрузили и применили их значения в это время.
ms-appdata:/// и getFileFromPathAsync возможно, так как файл находится в месте, куда мы можем получить программный доступ по умолчанию. Это, кроме того, работает для библиотек, которые мы указали среди возможностей в манифесте. Если, однако, мы получили объект StorageFile после работы со средством выбора файла, мы должны сохранить его в Windows.Storage.AccessCashe для того, чтобы сохранить права доступа между сеансами работы с приложением.Хотя мы видели примеры использования данных из пакета приложения (с помощью URI или Windows.ApplicationModel.Package.current.installedLocation), так же, как и из папок данных приложения, весьма вероятно, что ваше приложение будет включать в себя данные из веб-сервисов, и, возможно, так же отправлять данные в эти сервисы. Наиболее распространённым для этих целей методом является XmlHttpRequest. Вы можете использовать его в исходной (асинхронной) форме, если хотите, или вы можете предохранить себя от множества сложностей, используя функцию WinJS.xhr, которая удобно оборачивает все эти действия в promise-объекты.
Сделать запрос очень просто, как показано в примере SimpleXhr для этой лекции. Здесь мы используем WinJS.xhr для получения RSS-канала с блога разработчиков Windows 8:
WinJS.xhr({ url: "http://blogs.msdn.com/b/windowsappdev/rss.aspx" })
.done(processPosts, processError, showProgress);
То есть, здесь мы предоставляем WinJS.Xhr URI и получаем promise-объект, который доставляет свой результат в наш обработчик завершения (в данном случае это processPosts) и даже вызывает индикатор прогресса, если он предоставлен ему. В предыдущем случае результат содержит свойство responseXML, которое является объектом DomParser. В случае с последним, объект события содержит текущий XML в его свойстве response, который мы можем просто использовать для показа объема загруженных данных:
function showProgress(e) {
var bytes = Math.floor(e.response.length / 1024);
document.getElementById("status").innerText = "Downloaded " + bytes + " KB";
}
В остальном, приложение просто перерабатывает полученный текст в поисках элементов item и отображает поля Windows.Web.Syndication. Вы можете использовать его, если хотите лучше структурировать подобные источники данных. Так же, JavaScript имеет внутренние API для работы с XML, в итоге, вы можете пользоваться тем, что кажется вам более подходящим. В случае вроде этого, API синдикации вместе с Windows.Web.AtomPub и Windows.Data.Xml сильнее нужны приложениям для Windows 8, написанным на других языках, которые не имеют тех же встроенных возможностей, что и JavaScript.
(рис 3.9) Вывод данных в приложении SimpleXhr
Для полной демонстрации XHR и связанных вопросов обратитесь к примеру "XHR, обработка ошибок навигации и URL-схем" (http://code.msdn.microsoft.com/windowsapps/XHR-handling-navigation-50d03a7a) и к руководству: "Создание гибридного веб-приложения" (http://msdn.microsoft.com/library/windows/apps/hh452745.aspx). Я не вхожу в описание подробностей о XHR в данном учебном курсе, так как это, в основном, вопрос получения и обработки данных, что имеет мало общего с платформой Windows 8. Что нас интересует, так это последствия приостановки и возобновления работы приложения.
В частности, приложение не может предсказать, как долго оно будет оставаться в приостановленном состоянии, прежде чем его работа будет возобновлена или прежде чем его работа будет остановлена и оно будет перезапущено.
В первом случае, данные приостановленного приложения находятся в памяти. Очень нужно решить, поэтому, устарели ли данные с момента приостановки приложения и истекли ли тайм-ауты соединений с серверами. Так же вы можете подумать о том, после какого периода времени пользователь не помнит и не забоится о том, что произошло, когда он в последний раз видел приложение? Если это неделя или больше, может быть разумным перезапустить приложение или восстановить его состояние по умолчанию. Далее, если вы приводите приложение в состояние, точно соответствующее тому, в котором оно было, пользователь испытывает уверенность, что он может оставить приложение работать сколько угодно долго и ничего не потерять. Или вы можете пойти на компромисс и дать пользователю возможность настроить поведение приложения. Конечно, вы будете размышлять над собственным сценарием, однако, если сомневаетесь, перезагрузите состояние приложения, которое было оставлено пользователем на какое-то время.
Для того, чтобы проверить, сколько времени прошло, сохраните отметку времени при приостановке приложения (с помощью new Date().getTime()), получите другую отметку времени при обработке события resuming, найдите разницу и сравните её с заданным вами периодом обновления. Приложение Stock (Акции), например, может иметь очень короткий период. В случае с приложением для чтения блога разработчиков Windows 8, с другой стороны, новые записи появляются не чаще раза в день, поэтому здесь, для того, чтобы поддерживать приложение в актуальном состоянии и получать новые записи, подойдёт более длительный период, измеряемый часами.
Это реализовано в SimpleXhr путём размещения вызовов WinJS.hxr в отдельной функции, названной downloadPosts, которая вызывается при запуске приложения. Затем мы регистрируемся для обработки события resuming в WinRT:
Windows.UI.WebUI.WebUIApplication.onresuming = function () {
app.queueEvent({ type: "resuming" });
}
Помните, я говорил о том, что мы могли бы использовать WinJS.Application.queueEvent для того, чтобы вызывать собственные события объекта приложения? Вот отличный пример. WinJS.Application не включает в себя автоматически события resuming, так как ему нечего добавить к этому процессу. Но приведенный ниже код реализует в точности то же самое, позволяя нам регистрировать прослушиватели событий, наряду с другими событиями наподобие checkpoint:
app.oncheckpoint = function (args) {
//Сохраняем в sessionState в том случае, если хотим использовать это с кэшированием
app.sessionState.suspendTime = new Date().getTime();
};
app.addEventListener("resuming", function (args) {
//Обычное сокращение для того, чтобы получить либо значение переменной, либо значение
//по умолчанию
var suspendTime = app.sessionState.suspendTime || 0;
//Определяет, сколько времени, в секундах, прошло
var elapsed = ((new Date().getTime()) - suspendTime) / 1000;
//Обновляет ленту, если > 1 часа (или, для тестирования, используйте меньшее значение)
if (elapsed > 3600) {
downloadPosts();
}
});
Для того, чтобы протестировать этот код, загрузите отладчик Visual Studio и установите точки останова в этих событиях. Затем щёлкните на пункт приостановки (suspend) на панели инструментов (можете вернуться к Рис. 3.7), и вы должны попасть в обработчик события checkpoint. Подождите несколько секунд и щелкните на кнопку возобновления работы приложения (resume), имеющую треугольный зеленый значок, и вы должны оказаться в обработчике события resuming. Затем вы можете пошагово исполнить код и увидеть, что переменная elapsed содержит количество прошедших секунд, и если вы измените её значение (или замените 3600 меньшим значением), вы можете увидеть, как downloadPosts снова вызывается для обновления данных.
А что насчёт запуска после остановки работы приложения? Хорошо, если вы не кэшировали данные ранее, вам, в любом случае, нужно их обновить. Если вы кэшировали какие-то данные, сохранённое состояние сеанса работы приложения (такое, как отметка времени) поможет вам решить, стоит ли использовать кэшированные данные или нужно загрузить их снова.
Полезно будет упомянуть здесь о том, что вы можете использовать механизмы HTML5, наподобие localStorage, IndexedDB и кэша приложения для целей кэширования; подобные данные хранятся в папке локальных данных приложения. И, если говорить о базах данных, вы можете заинтересоваться, что доступно приложениям для Магазина Windows помимо IndexedDB. Одна из возможностей - это SQLite, как описано в материале "Использование SQLite в приложениях для Магазина Windows" (http://timheuer.com/blog/archive/2012/05/20/using-sqlite-in-metro-style-app.aspx) (в блоге Тима Хейера, одного из инженеров Windows 8). Кроме того, вы можете использовать библиотеку OData для JavaScript, которую можно найти по адресу http://www.odata.org/libraries. Это - один из наиболее лёгких способов для взаимодействия с онлайновыми базами данных на SQL Server (или любыми другими, поддерживающими OData), так как он просто использует возможности XmlHttpRequest.
Мы поговорим о сетевом взаимодействии в лекции 3 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой", но есть один важный аспект, о котором вы, разрабатывая приложение, должны знать заранее. Что делает приложение с изменениями состояния сетевого соединения, такими как разъединение, повторное соединение и с изменениями полосы пропускания и стоимости соединения (такими, как связь в роуминге, в другой зоне предоставления услуг)?
API Windows.Networking.Connectivity обеспечивает вас такого рода информацией. Вот три основных способа реагирования на подобные события:
Проще говоря, тестируйте приложение как в состоянии подключения к сети, так и без него для того, чтобы обнаружить небольшие оплошности в коде. В "Here My Am!", например, первая версия скрипта в html/map.html не заботилась о том, чтобы проверить, действительно ли загружен удалённый скрипт карт Bing. Сейчас она проверяет, является ли допустимым пространство имен Microsoft (для Microsoft.Maps.Map) В SimpleXhr, тоже, я добавил обработчик ошибки для операции получения отложенного результата в WinJS.xhr, в итоге я могу, по меньшей мере, отобразить обычное сообщение. Здесь, конечно, гораздо больше работы, но попытайтесь, по меньшей мере, охватить основы, для того, чтобы избежать исключений, которые приведут к аварийному завершению работы приложения.
Не распутывая клубок проблем, которым является XmlHttpRequest, будет полезным посмотреть на пару дополнительных вещей, касающихся WinJS.xhr.
Во-первых, обратите внимание на то, что одиночный аргумент этой функции является объектом, который содержит множество свойств. Свойство url наиболее распространено, конечно, но вы, кроме того, можете установить свойство type (тип) (по умолчанию оно установлено в "GET") и respondeType для транзакций другого рода, информацию об учетных данных пользователя в виде user и password, установить headers (заголовки) (такие, как "If-Modified-Since" с датой для управления кэшированием) и предоставить любые другие дополнительные данные (data), если в этом есть необходимость (такие, как параметры запроса к базе данных для XHR). Кроме того, вы можете поддерживать функцию customRequestInitializer, которая будет вызываться объектом XmlHttpReauest до отправки данных, позволяя вам произвести любые необходимые действия.
Во-вторых, это установка тайм-аута в запросе. Вы можете использовать для этих целей customRequestInitializer, устанавливая свойство XmlHttpRequest.timeout и, возможно, обрабатывая событие ontimeout. С другой стороны, как мы увидим в разделе "Завершение истории promise-объектов" в конце этой лекции, вы можете использовать функцию WinJS.Promise.timeout, которая позволяет вам устанавливать период тайм-аута, после которого promise-вызов (и асинхронная операция, связанная с ним) будет отменен. Отмена производится простым вызовом метода cancel promise-объекта.
Вам может понадобиться включить WinJS.xhr в другой promise-объект, это мы так же рассмотрим в конце данной лекции. Вы можете сделать это для того, чтобы инкапсулировать другие промежуточные результаты с вызовом XHR, когда ваш код просто использует возвращённый отложенных результат обычным образом. В соединении с тайм-аутами, эта так же может быть использовано для реализации механизма множественного повтора.
Далее, если вам нужно скоординировать вместе несколько вызовов XHR, вы можете использовать WinJS.Promise.join, который мы снова увидим позже.
Кроме того, мы увидим, как обрабатывать переданные данные в обработчике прогресса загрузки. Так же, вы можете использовать другие данные в ответах и запросах. Например, аргументы события содержат свойство readyState.
У приложений для Магазина Windows, использующих XHR с localhost: URI (локальная заглушка, петлевой адрес локальной сети) заблокированы при проектировании системы. При разработке, однако, это очень полезно для отладки сервисов без их развёртывания. Вы можете включить локальную заглушку в Visual Studio, открыв диалоговое окно свойств проекта (контекстное меню проекта > Свойства), выбрав группу Отладка (Debugging) в левой части окна и установив параметр Разрешить петлевой адрес в локальной сети (Allow Local Network Loopback) в значение Да. Мы рассмотрим соответствующий пример в лекции 2 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой", где это окажется очень полезным для отладки сервисов, которые выдают данные для обновления плиток и другие оповещения.
Наконец, полезно знать, что по соображениям безопасности куки-данные (cookies) автоматически вырезаются из XHR-данных, поступающих в локальный контекст. Один из способов это обойти - выполнять XHR-вызовы из iframe, находящегося в веб-контексте (в котором вы можете использовать WinJS.XHR), затем извлекать куки-данные, которые вам нужны и передавать их в локальный контекст, используя postMessage. С другой стороны, вы можете решить проблему на стороне сервиса, такую, как реализацию API, предоставляющего необходимые вам данные, которые вы извлекали из куки, напрямую.
Другие подробности о данной функции вы можете найти в документации по WinJS.xhr (http://msdn.microsoft.com/library/windows/apps/br229787.aspx), и по ссылкам на дополнительные материалы, которые она содержит.
Сейчас мы подошли к той стороне приложений для Магазина Windows, которая весьма сильно отличает их от обычных веб-приложений. В веб-приложениях, навигация между страницами реализуется посредством использования гиперссылок <a href> или установки параметра document.location в JavaScript. Всё это замечательно. Часто здесь либо нет данных для передачи между страницами, либо их немного. И даже когда есть что передавать между страницами, существует хорошо налаженный механизм HTML5для этого, такой, как sessionStorage и localStorage (который хорошо работает и с приложениями для Магазина).
Однако, подобные способы навигации приводят к некоторым проблемам в случае с приложениями для Магазина Windows. Первая, перемещение на совершенно новую страницу подразумевает совершенно новый контекст скрипта - все JavaScript-переменные с предыдущей страницы будут потеряны. Конечно, вы можете передать данные между этими страницами, но управление подобным по всему приложению серьезно ухудшает производительность и может очень скоро превратиться в вашу самую нелюбимое дело в работе программиста. Лучше и проще, другими словами, чтобы клиентское приложение поддерживало в памяти постоянное состояние, не зависящее от перемещений по страницам.
Кроме того, природа подсистемы рендеринга HTML/CSS такова, что при переходах между страницами с помощью гиперссылки появляется пустой экран. Пользователи веб-приложений привыкли ждать какое-то время, пока браузер загрузит новую страницу (я много что успеваю сделать за дополнительные 15 секунд!), но это не соответствует тому, что ожидают пользователи от быстрых и динамичных приложений для Магазина Windows. Более того, подобные переходы не позволяют анимацию различных элементов на экране, что помогает создать ощущение неразрывной связи страниц, если это соответствует дизайну приложения.
В итоге, хотя вы можете использовать прямые ссылки, приложения для Магазина обычно реализуют понятие "страница" динамически заменяя секции в DOM внутри контекста одной страницы наподобие default.html, что родственно тому, как работают приложения, основанные на AJAX. Подобный подход позволяет всегда сохранять контекст скрипта и обеспечить переходы между элементами и группами элементов так, как вам нужно. В некоторых случаях имеет значение простой показ и скрытие страниц, в итоге вы можете быстро переходить вперед и назад. Посмотрим на стратегии и инструменты, с помощью которых можно достичь эти цели.
Сама по себе 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.process[All], использовать столько этих элементов на хост-странице, сколько нужно и даже создавать из них вложенных структуры. Смотрите Сценарий 1 в примере "Элементы управления HTML-страницей" (http://code.msdn.microsoft.com/windowsapps/Page-Controls-sample-568b10b4).Эти API предоставляют только средства для загрузки и выгрузки отдельных страниц - они берут HTML из других файлов (вместе с соответствующим CSS и JS-кодом) и присоединяют эти данные к элементу в DOM. Вот и всё. Для того, чтобы по-настоящему реализовать структуру навигации по страницам, нам нужно еще два механизма: что-то, что управляло бы стеком навигации и что-то, что перехватывало бы события навигации в механизме загрузки страниц WinJS.UI.Pages.
Первую задачу можно выполнить с помощью beforenavigation можно использовать для отмены навигации, если нужно. Либо вызов args.preventDefault (args будет объектом события), возвратит true, либо вызов args.setPromise где promise-объект вернет true.
Для выполнения второй задачи вы можете создать собственные связи между WinJS.Navigation и WinJS.UI.Pages. На самом деле, на ранних стадиях разработки приложений для Windows 8, вплоть до первых публичных предварительных релизов для разработчиков, программисты писали один и тот же код снова и снова. В ответ на это, команда разработки в Microsoft, ответственная за шаблоны, великодушно решали создать стандартную реализацию этого механизма, что добавило несколько команд для работы с клавиатурой (для перемещения вперед и назад) и некоторые удобные оболочки для использования в шаблонах. Ура!
Этот инструмент называется WinJS.Navigation.Navigate. Этот инструмент может быть весьма полезен, особенно, если вы импортируете код из веб-приложений.PageControlNavigator в собственных программах, посмотрим, как это всё работает в контексте шаблона Приложение навигации (Navigation App).
Если вспомнить шаблон Пустое приложение, шаблон Приложение навигации демонстрирует основы использования элементов управления страницами. (Более сложные шаблоны строят систему навигации, идущую дальше). Если вы создаёте новый проект с использованием данного шаблона в 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>) и затем вызвав этот конструктор с родительским элементом и объектом, содержащим его параметры.
Хотя, базовая структура для метода ready предоставляется шаблоном, WinJS.UI.Pages и PageControlNavigator будут использовать следующие методы, если они доступны:
| Метод 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 (как мы увидим в лекции 5, "Коллекции и элементы управления для вывода коллекций").
Что касается самого 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.
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 (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> в любом месте приложения.
В приложения для Магазина 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 (смотрите лекцию 5) с набором элементов по умолчанию.
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.
PageControlNavigator.На всякий случай, шаблон Приложение с разделением (Split App) работает похожим образом, когда каждый элемент списка на pages/items привязан, при активизации, к перемещению на pages/split .
В любом случае, шаблон Приложение таблицы, кроме того, служит примером того, что мы называем стилем навигации Хаб-Раздел-Сведения (Hub-Section-Details). Здесь домашняя страница приложения представляет собой центральную страницу, где пользователь может в полной мере изучить приложение. Щелчок по заголовку группы осуществляет навигацию к странице раздела, второму уровню организации, где отображены лишь элементы этой группы. Щелчок по элементу (на центральной странице или на странице разделов) переносит нас на страницу сведений для данного элемента. Вы можете, конечно, реализовать данный стиль навигации любым желаемым способом. Шаблон Приложение таблицы использует элементы управления страниц, WinJS.Navigation и PageControlNavigator. (Контекстное масштабирование (семантический зум, semantic zoom), как мы увидим в лекции 5, так же поддерживается в качестве инструмента навигации для переключения между центральными страницами и страницами разделов.)
Альтернативная модель навигации - это плоский (flat) стиль, который просто имеет один уровень иерархии. Здесь навигация происходит на любую из страниц в любое время посредством панели навигации (navigation bar) (она появляется вместе с панелью приложения, смотрите лекцию 1 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript"). При использовании элементов управления страницы и PageControlNavigator, элементы управления навигацией могут просто запускать WinJS.Navigation.Navigate для этой цели. Обратите внимание, что при таком стиле навигации кнопка Назад обычно не используется.
Информацию об этих стилях, вместе со многими другими аспектами пользовательского интерфейса, касающимися навигации, можно обнаружить в материале "Проектирование навигации для приложений Магазина Windows" (http://msdn.microsoft.com/library/windows/apps/hh761500.aspx). Это - важный материал для дизайнеров.
Некоторые приложения могут требовать либо первоначальную идентификацию пользователя, либо принятие лицензионного соглашения для продолжения работы. Таким образом, целесообразно показывать подобные страницы сразу после экрана-заставки. В подобных случаях, если пользователь не примет условия лицензии или не введет идентификационные данные, приложению следует отобразить сообщение, говорящее о необходимости этих действий, но всегда следует позволять пользователю закрыть приложение, если он этого хочет. Не закрывайте приложение автоматически.
Обычно подобные страницы появляются лишь при первом запуске приложения. Если пользователь ввёл подходящие идентификационные данные, их можно сохранить для последующего использования, воспользовавшись 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 предоставляется шаблоном как часть вашего приложения, вы можете модифицировать его как вам будет угодно для того, чтобы реализовать подобную возможность более последовательным способом.
Обычной практикой в HTML и JavaScript, особенно для веб-сайтов, является то, что мы уже делали в данном учебном курсе. Мы вызывали addEventListener для задания обработчика события или просто присваивали обработчик события свойству on<event> какого-либо объекта. Часто эти обработчики просто объявляют в качестве встроенных анонимных функций:
var myNumber = 1;
element.addEventListener(<event>, function (e) { myNumber++; } );
Благодаря особым правилам обзора данных, действующим в JavaScript, область видимости подобной анонимной функции совпадает с окружающим её кодом, что позволяет коду внутри этой функции ссылаться на локальные переменные наподобие myNumber в вышеприведенном коде.
Для того, чтобы убедиться, что подобные переменные доступны данной анонимной функции, когда она позже будет вызвана в качестве обработчика события, JavaScript-движок создаёт замкнутое выражение (closure), структуру данных, описывающую локальные переменные, доступные данной функции. Обычно замкнутое выражение представляет собой небольшой участок памяти, но в зависимости от кода внутри обработчика события, замкнутое выражение может включить в себя всё глобальное пространство имён, что потребует весьма значительного выделения памяти!
Каждое такое замкнутое выражение увеличивает объем памяти или рабочий набор (working set) приложения, поэтому хорошо поддерживать минимальный объем подобных данных. Например, объявление отдельных именованных функций, которые имеют собственную область видимости - уменьшит размер необходимых замкнутых выражений.
Более важно, нежели уменьшение размера замкнутых выражений, это уверенность в том, что прослушиватели событий сами по себе и, связанные с ними замкнутые выражения соответствующим образом освобождают выделенную им память.
Обычно это не то, о чём вам нужно думать. Когда объекты, такие, как HTML-элементы, уничтожаются, как в случае, когда элементы страницы выгружаются из DOM, связанные с ними прослушиватели автоматически удаляются и ресурсы, занятые замкнутыми выражениями так же освобождаются. Однако, в приложениях для Магазина Windows, написанных на HTML и JavaScript есть и другие источники событий, для которых приложение может добавить прослушиватели в то время, как данные объекты никогда не уничтожаются. Это могут быть объекты из WinJS, объекты из WinRT, window и document. Данные Прослушиватели должны быть соответствующим образом очищены, в противном случае приложение будет иметь утечки памяти (память, которая выделена, но никогда не освобождается при операции сборки мусора).
Особого внимания требуют события, которые исходят от объектов WinRT. Из-за сущности уровня проекции, который делает WinRT доступным в WinJS, WinRT ограничивается хранением ссылок на обработчики событий JavaScript (известных так же как делегаты (delegates)), пока замкнутые выражения JavaScript хранят ссылки на некоторые объекты WinRT. В результате наличия подобных перекрестных ссылок, эти замкнутые выражения могут никогда не быть очищенными.
Это не проблема, помните, если приложение всегда прослушивает конкретные события. Например, события suspending и resuming - это те события, которые приложение обычно прослушивает в течение всего времени жизни приложения, в итоге, любые связанные с ними выделения памяти будут очищены при завершении работы приложения. То же самое справедливо для большинства прослушивателей, которые вы можете добавить для событий объектов window и document, которые постоянно существуют во время жизни приложения.
Утечки памяти, однако, возникают, когда приложение прослушивает события объектов WinRT лишь временно и пренебрегает непосредственным вызовом removeEventListener, или когда приложение вызывает addEventListener для одного и того же события несколько раз (в таком случае вы получите несколько замкнутых выражений). В случае с элементом управления страницы, как обсуждалось в данной лекции, обычная практика заключается в вызове addEventListener в методе ready страницы для некоторого WinRT-объекта. Когда вы делаете это, убедитесь в том, что есть соответствующий данному вызову вызов removeEventListener в методе страницы unload, который освободит ресурсы, занятые замкнутым выражением. Я сделал это в примере HereMyAm3d с datarequested, для ясности.
В этом учебном курсе события WinRT, на которые вам следует обратить внимание, выделены специальным цветом, как datarequested (за исключением текста, который является гиперссылкой). Это напоминание для проверки того, нужен ли явный вызов removeEventListener. Опять же, если вы всегда прослушиваете событие, удалять прослушиватель не нужно, но если вы добавили его, когда загружали элемент управления страницы, вам практически гарантированно понадобится выполнить этот дополнительный вызов. Особенно обратите внимание на то, что примеры не обязательно уделяют внимание этой особенности, поэтому не повторяйте примеры, не задумываясь об этом. И, наконец, обратите внимание на то, что события от объектов WinJS не нуждаются в подобном внимании, так как библиотека уже обрабатывает удаление прослушивателей событий.
В следующих лекциях я напомню вам о том, что мы только что обсудили на нашей первой значимой встрече с событиями WinRT. В любом случае, будьте внимательны к цветовому выделению текста.
Ух ты! Мы прошли большой путь в этой лекции, через множество тонких деталей того, как строятся приложения, и как они выполняются (или не выполняются!). Вы могли заметить, что наша постоянная тема касалась promise-объектов - они появлялись почти в каждом разделе. На самом деле, WinJS и WinRT изобилуют асинхронными операциями и выполняются они с помощью получения отложенных результатов, promise-объектов.
Я хочу завершить эту лекцию, однако, завершив историю promise-объектов, так как они обеспечивают гораздо более обширную функциональность, чем мы использовали. Демонстрацию того, что мы рассмотрим здесь, можно найти в примере "WinJS Promise" (http://code.msdn.microsoft.com/windowsapps/Promise-e1571015), а если вам нужна самая полная история асинхронных операций, прочтите материал "Использование асинхронности в среде выполнения Windows для создания быстрых и гибких приложений" (http://blogs.msdn.com/b/windowsappdev_ru/archive/2012/03/28/windows.aspx) в блоге разработчиков Windows 8.
Давайте сделаем шаг назад и уточним, что, на самом деле означает "promise". Говоря просто, это объект, который возвращает значение, простое или сложное, когда-то в будущем. Способ, благодаря которому вы узнаёте, когда доступно это значение - это вызов методов promise-объекта then или done с обработчиком завершения (completed handler). Этот обработчик будет вызван со значением, предоставленным promise-объектом (с обещанным значением) (с результатом (result)), когда это значение будет готово - это происходит немедленно, если значение уже доступно. Более того, вы можете вызывать then/done множество раз для одного и того же promise-объекта и вы просто получаете тот же самый результат в каждом обработчике завершения. Это не приведет к системному сбою или к чему-то подобному.
Если происходит ошибка, второй параметр у then/done это - обработчик ошибки (error handler), который будет вызван вместо обработчика завершения. В противном случае исключение будет либо поглощено в then, либо передано в цикл событий приложений done, как мы уже говорили об этом.
Третий параметр у IAsync[Action | Operation]WithProgress, значит она может использовать функцию прогресса, переданную promise-объекту. Если же в её описании присутствует лишь IAsync[Action | Operation], то прогресс не поддерживается. Больше об этом вы можете найти в лекции 16.WinJS.xhr периодически вызывают функцию прогресса для отображения изменения "состояния готовности" при загрузке данных, запрошенных с сервера.
Сейчас нет требований к тому, чтобы promise-объект инкапсулировал асинхронные операции или синхронные. Вы можете, фактически, "обернуть" в этот объект любое значение, воспользовавшись статическим методом WinJS.Promise.wrap. Подобный контейнер для уже существующего значения (будущее - это сейчас!) будет ждать своего часа и вызовет обработчик завершения с данным значением, как только вы вызовете then или done. Это позволяет вам использовать любое значение там, где ожидается promise-объект, или возвращать что-то вроде ошибок из функций, которые в противном случае возвращают promise-объекты для асинхронных операций. WinJS.Promise.wraperror существует именно для этой специфической цели.
WinJS.Promise (http://msdn.microsoft.com/library/windows/apps/br211867.aspx), кроме того, поддерживает набор полезных статических методов, вызываемых напрямую из WinJS.Promise вместо того, чтобы пользоваться каким-то конкретным экземпляром promise-объекта.
is определяет, является ли произвольное значение promise-объектом, она обычно проверяет, обладает ли объект функцией с именем "then"; она не проверяет объекты на "done"
as работает схожим с wrap образом, за исключением того, что если вы предоставите ей promise-объект, она просто вернет этот объект. Если вы предоставите promise-объект функции wrap, она инкапсулирует его в еще один promise-объект.
any похожа на join, но группирует результаты с использованием логического ИЛИ (OR) (снова используя then)
cancel останавливает асинхронную операцию. Если предоставлен обработчик ошибок, он вызывается со значением Error("canceled").
theneach применяет обработчики завершения, ошибки, прогресса к группе promise-объектов (используя then), возвращая результаты в виде другой группы значений внутри promise-объекта.
timeout имеет двойственную природу. Если вы просто зададите значение тайм-аута, он вернет promise-объект, инкапсулирующий вызов setTimeout. Если вы, кроме того, предоставите promise-объект в качестве второго параметра, он отменит выполнение операции этого promise-объекта, если она не будет получена в заданное время. В последнем случае это обычная оболочка для стандартных шаблонов добавления тайм-аута для некоторых других асинхронных операций, у которых тайм-аута нет.
В дополнение к использованию функций наподобие as и wrap, вы так же можете создать promise-объект из заготовки, используя команду new WinJS.Promise(<init> [, <oncancel>). Здесь <init> - это функция, которая принимает диспетчеры (dispatcher) завершения, ошибки и прогресса, а oncancel - это необязательная функция, которая вызывается в ответ на WinJS.Promise.Cancel. Диспетчеры - это то, что вы вызываете, чтобы вызвать любые обработчики завершения, ошибки или прогресса, заданные методам promise-объекта then или done, в то время как oncancel - это ваша собственная функция, которую promise-объект вызовет, если он подвергнется операции отмены. Создание новых promise-объектов подобным способом обычно используют, когда создают собственные асинхронные функции. Например, мы увидим, как это используется для упаковывания асинхронного рабочего веб-процесса (web worker) в лекции 5 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой".
Кроме того, если возможностей WinJS.Promise.as недостаточно, создание promise-объектов подобным образом полезно для упаковывания других операций (не только значений) в структуру promise-объекта, в итоге, такая операция может быть объединена в цепочку или соединена с другими операциями. Например, если у вас есть библиотека, которая обменивается данными с веб-сервисом посредством обычного асинхронного XmlHttpRequest, вы можете упаковать каждое API этой библиотеки в Promise-объект. Вы можете, кроме того, использовать новый promise-объект для того, чтобы упаковать множество асинхронных операций (или других promise-объектов!) из различных источников в единый promise-объект, в то время, как join или any не дадут вам нужного уровня контроля. Другой пример - инкапсуляция специфических функций обработки завершения, ошибок, прогресса операции в promise-объект, как при реализации механизма множественных вызовов поверх отдельных XHR-операций, для перехвата доступа к стандартному интерфейсу индикатора прогресса, или для добавления фонового ведения журнала, или подсистемы аналитики с вызовами сервиса, в итоге вашему коду никогда не понадобится знать об этих механизмах.
iframe.
iframe.
ms-appdata для адресации мультимедийного содержимого из локальной, перемещаемой, временной папок приложения.
then и done.
WinJS.UI.Application.
WinJS.xhr и как это связано с событием resuming.
WinJS.Navigation и PageControlNavigator из шаблонов Visual Studio / Blender, таких, как шаблон Приложение навигации.
Файлы к данной лекции Вы можете скачать здесь.
На ранних стадиях написания курса, я, кроме того, плотно работал с подрядчиком на постройке дома для моей семьи. Даже когда я не был на месте строительства, прилагая все усилия, чтобы оно продвигалось, я был, конечно, вовлечен в процесс принятия решений о различных фазах строительства и иногда участвовал в самом строительстве.
В предгорьях Сьерра-Невады в Калифорнии, где я живу, каркас дома построен из местного дерева, и вся сантехника и проводка должны быть в стенах до того, как будет установлена изоляция и древесные плиты (гипсокартон). Меня поразило, сколько времени потребовалось для того, чтобы завершить монтаж этой инфраструктуры. Строители потратили много времени на добавление маленьких брусков дерева, то тут, то там, чтобы упростить себе отделочную работу позже (вроде развешивания шкафов), и много времени заняла правильная сборка сантехники и проводки. Всё это стало совершенно невидимым для глаз, когда панели были закреплены и была готова отделка.
Представьте себе, на что будет похож дом, построенный без такого внимания к его деталям. Представьте, что некоторые выключателя просто не работают или управляют не теми светильниками. Представьте себе, что трубы в стенах протекают. Представьте, как шкафы и отделка падают со стен через пару недель после того, как они попали в дом. Даже если дому удалось пройти окончательную проверку, подобные недостатки сделают его почти непригодным для жизни, и неважно, каким красивым он может показаться на первый взгляд. Это походило бы на несколько работ знаменитого архитектора Фрэнка Ллойда Райта: очень интересно с архитектурной точки зрения, но совершенно неудобно для того, чтобы жить там.
С приложениями - та же история. На самом деле, я удивляюсь, как много похожего между этими двумя начинаниями! Это значит, что приложение может замечательно выглядеть, даже ошеломляюще, но как только вы начнете по-настоящему пользоваться им, день за днем, недостатки внимания к фундаментальным основам станет болезненно очевидным. В результате, ваши клиенты, вероятнее всего, начнут искать себе новый дом, то есть - приложение какого-нибудь другого разработчика.
Эта лекция посвящена тем самым основам: ключевым глубинным конструкциям приложения, на основе которых можно построить нечто, выглядящее красиво и работающее по-настоящему хорошо. Мы сначала доведем до полного понимания окружение хост-процесса приложения и посмотрим на активацию (то, как приложение запускается) и на переходы между стадиями жизненного цикла приложения. Затем мы посмотрим на навигацию по страницам внутри приложения и разберем еще некоторые важные попутные вопросы, такие, как работа с несколькими асинхронными операциями.
Позвольте мне предупредить вас о том, что эта лекция гораздо больше и сложнее, чем многие, следующие за ней, так как она имеет дело с программными эквивалентами создания каркаса дома, построения сантехнических коммуникаций и электропроводки. В случае с нашим домом, я могу с полной уверенностью говорить о том, что установка прекрасных светильников, которые выбрала моя жена, принесло больше удовольствия, чем сам процесс строительства, которым я занимался месяцами ранее. Но сейчас, по-настоящему живя в доме, я чувствую глубокую признательность за всю ту менее привлекательную работу, благодаря которой дом был построен. Это место, где я хочу быть, место, в котором я и моя семья будем счастливы провести большую часть нашей жизни. Вы ведь хотите, чтобы клиенты чувствовали то же самое по отношению к вашим приложениям? Абсолютно точно! Зная о том удовольствии, которое хорошо спроектированные приложения способны принести вашим клиентам, займёмся нашим делом и ощутим удовольствие от исследования деталей!
Как было описано в лекции 1, приложения, написанные на HTML, CSS и JavaScript не являются исполняемыми, как их скомпилированные аналоги, написанные на C#, Visual Basic или C++. В пакетах приложений нет EXE-файлов, там есть лишь .html, .css и .js-файлы (и ресурсы тоже, конечно), которые не содержат ничего, кроме обычного текста. В итоге, что-то должно превратить эти тексты, определяющие приложение, во что-то, что запускается и работает в памяти. Это "кое-что" - хост-процесс приложения (app host), wwahost.exe, который создаёт то, что мы называем управляемой средой (hosted environment) приложений для Магазина Windows.
Рассмотрим то, что мы уже узнали в лекции 1 и лекции 2 о характеристиках управляемой среды.
ms-appx:/// или ms-appx-web:///), использованной для обращения к контенту (третий / означает "пакет приложения"). Удалённый контент (обращение к нему ведется с помощью http[s]://) всегда исполняется в веб-контексте.
postMessage может быть использована для организации межконтекстного взаимодействия между iframe и средой, которая её содержит. Это может быть полезным для исполнения удаленного скрипта в веб-контексте и передачи результатов в локальный контекст; скрипт из веб-контекста не нужно перемещать в локальный контекст и исполнять там. (Правила Магазина Windows запрещают это и приложения, отправленные в Магазин, анализируются на предмет применения подобных методов работы).
В этой лекции мы больше будем заниматься не самими этими характеристиками, а тем, как они воздействуют на структуру приложения. (Для того, чтобы посмотреть характеристики, обратитесь к примеру "Интеграция содержимого и элементов управления с веб-сервисов", (http://code.msdn.microsoft.com/windowsapps/Mashup-Sample-10689f5b).)
В первую очередь, и прежде всего, отметим, что домашняя страница приложения, та, которую вы указываете в манифесте, в поле Начальная страница (Start page), на закладке Интерфейс приложения (Application UI)<href> или команды document.location) так же должна принадлежать локальному контексту.
Далее, страница локального контекста может содержать элемент iframe, имеющий локальный или веб-контекст, предоставляя ему атрибут src, ссылающийся на контент в пакете приложения (и, кстати, программный доступ только для чтения к содержимому вашего пакета, который можно получить посредством Windows.ApplicationMode.Package.Current.InstalledLocation). Ссылки на любые другие местоположения (http[s]:// или другие протоколы) всегда помещаются в iframe, который исполняется в веб-контексте.
<!-- iframe в локальном контексте, с источником из пакета приложения --> <!-- подобное разрешено лишь из локального контекста --> <iframe src="/frame-local.html"></iframe> <iframe src="ms-appx:///frame-local.html"></iframe> <!-- iframe в веб-контексте с источником в пакете приложения --> <iframe src="ms-appx-web:///frame-web.html"></iframe> <!-- iframe с внешним источником автоматически присваивается веб-контекст --> <iframe src="http://www.bing.com"></iframe>
Кроме того, если вы используете тег <a href="..." target="..."> с target, указывающем на iframe, схема в href определяет контекст.
Страница в веб-контексте, в свою очередь, может содержать только iframe, который так же имеет веб-контекст. Например, выше можно использовать последние два элемента iframe, в то время как два первых элемента - нет. Кроме того, вы можете использовать ms-appx-web:/// в веб-контексте для того, чтобы ссылаться на другое содержимое в пакете приложения, например, на изображения.
Хотя это не вполне справедливо внутри приложений для Магазина Windows, по причинам, которые мы рассмотрим ниже в этой лекции, похожие правила применимы к навигации между страницами с использованием <href> или document.location. Так как всё то, о чём мы здесь говорили, похоже сейчас на кашу из разных сведений, точное поведение для этих вариаций и iframe приведено в следующей таблице:
| Цель | Результат на странице в локальном контексте | Результат на странице в веб-контексте |
|---|---|---|
<iframe src="ms-appx:///"> | iframe в локальном контексте | Не разрешено |
<iframe src="ms-appx-web:///"> | iframe в веб-контексте | iframe в веб-контексте |
<iframe src="http[s]:// "> или другая схема | iframe в веб-контексте | iframe в веб-контексте |
<a href="[uri]" target="myFrame"> <iframe name="myFrame"> | iframe в локальном или веб-контексте, в зависимости от [uri] | iframe в веб-контексте; [uri] не может начинаться с ms-appx. |
<a href="ms-appx:///"> | Ссылка на страницу в локальном контексте | Не разрешено, если только не задано явно (смотрите ниже) |
<a href="ms-appx-web:///"> | Не разрешено | Ссылка на страницу в веб-контексте |
<a href="[uri]"> с любым другим протоколом, включая http[s] | Открывает браузер по умолчанию с [uri] | Открывает браузер по умолчанию с [uri] |
Когда iframe работает в веб-контексте, обратите внимание на то, что страница может иметь ms-appx-web-ссылки на ресурсы пакета приложения, даже если страница загружена с удалённого источника (http[s]). Подобная страница, конечно, не будет работать в браузере.
Последние два элемента в таблице, на самом деле, имеют в виду то, что приложение не может осуществить переход со своей страницы верхнего уровня (в локальном контексте) прямо к странице любого вида в веб-контексте (удалённой или локальной) и при этом отображать её в приложении. Вместо приложения будет запущен браузер. Такова жизнь приложения в хост-процессе! Подобный контент нужно размещать в iframe.
Аналогично, навигация со страницы веб-контекста к страницам локального контекста по умолчанию не разрешена, но вы можете включить это, вызвав сверхсекретную функцию MSApp.addPublicLocalApplicationUri (http://msdn.microsoft.com/library/windows/apps/hh465759.aspx) из кода на локальной странице (на самом деле, функция хорошо документирована) для каждого конкретного URI, который вам нужен:
//Это должно быть вызвано из локального контекста
MSApp.addPublicLocalApplicationUri("ms-appx:///frame-local.html");
Упражнение для этой лекции, "Direct Navigation", содержит демонстрацию этой возможности (как в Сценарии 6, в примере "Интеграция содержимого и элементов управления с веб-сервисов", (http://code.msdn.microsoft.com/windowsapps/Mashup-Sample-10689f5b). Будьте осторожны, когда URI содержит параметры запроса. Например, вы можете не захотеть позволять вебсайту переходить на что-то вроде: ms-appx:///delete.html?file=superimportant.doc , выполняя операцию по удалению очень важных данных!
Здесь возникает еще один вопрос, о возможности предоставить странице в веб-контексте доступ к специфическим функциям, наподобие геолокации, записи данных в буфер обмена, доступ к кэшу приложения, к IndexedDB - к тому, чем обычно пользуются веб-страницы. По умолчанию веб-контекст в приложениях для Магазина Windows не имеют доступа к подобным возможностям уровня операционной системы. Например, создайте новое приложение по шаблону Пустое приложение в Visual Studio с этой единственной HTML-строкой в теле страницы default.html:
<iframe src="http://maps.bing.com" style="width:1366px; height: 768px"></iframe>
Затем, включите возможность Расположение (Location) в манифесте (я об этом забыл, когда проводил данный эксперимент!) и запустите приложение. Вы, как и ожидалось, увидите страницу Bing
(рис 3.1) Использование возможности, доступ к которой контролируется брокером, наподобие геолокации, внутри веб-контекста, приводит к появлению ошибки
Подобные возможности заблокированы, так как веб-контент, загруженный в iframe, может легко осуществить переход на любые другие страницы. Со страницы карт Bing, изображенной выше, пользователь может перейти на домашнюю страницу поисковой системы Bing, выполнить поиск, и затем перейти на любое количество непроверенных, потенциально опасных, страниц. В любом случае, эти страницы могут запрашивать доступ к критически важным ресурсам, и если это приведет к показу обычного вопроса для пользователя, который выводит приложение, пользователи могут быть обмануты при предоставлении подобного доступа.
К счастью, если вы хорошо попросите, Windows позволит вам включить эти возможности для веб-страниц, о которых знает приложение. Всё, что для этого нужно - письменные показания, под присягой, подписанные вами и шестнадцатью свидетелями… Ладно, я шучу! Вам просто нужно добавить то, что называется правила URI содержимого приложения (application content URI rules) в ваш манифест. Каждое правило заявляет о том, что контент, доступный по некоторому URI, известен приложению, и оно доверяет ему. Таким образом, этот контент может действовать от имени приложения. Вы так же можете исключать URI, что обычно используется для исключения определенных страниц, которые могли бы быть включены в другое правило.
Подобные правила создаются на закладке URI содержимого (Content URI) редактора манифеста в Visual Studio, как показано на Рис. 3.2. Каждое правило должно содержать точный URI, по которому может быть сделан запрос, такой, как http://www.bing.com/maps/ . Как только мы добавим это правило (как в полном упражнении для этой лекции, "ContentURI"), картам Bing разрешено будет использовать геолокацию. Когда они попытаются это сделать, отобразится диалоговое окно (Рис. 3.3), как тогда, когда приложение само пытается выполнить подобное действие. (Примечание. При выполнении в отладчике, пример "ContentURI" может показать исключение Отсутствие прав доступа (Permission Denied) при запуске. Если это произошло, нажмите Продолжить (Continue) в Visual Studio, так как это не влияет на запуск приложения вне отладчика).
(рис 3.2) Добавление URI содержимого в манифест приложения; содержимое текстовых полей сохраняется при сохранении манифеста. Кнопка Добавить новый URI (Add New URI) создаёт набор элементов управления, с помощью которых можно добавлять дополнительные правила
(рис 3.3) Когда правило URI содержимого должным образом настроено, веб-контент в iframe действует так же, как приложение, что говорит о том, почему правила URI содержимого необходимы для защиты пользователя от страниц, неизвестных приложению, которые, в противном случае могли бы обмануть пользователя, получая доступ к критически важным ресурсам
Так как мы говорим здесь об iframe, вот несколько дополнительных советов, которые вы можете счесть полезными при работе с этим элементом. Во-первых, чтобы предотвратить выделение, настройте стиль элемента с помощью -ms-user-select: none (http://msdn.microsoft.com/library/windows/apps/hh779846.aspx) или установите его свойство style.msUserSelect в значение none с помощью JavaScript. Во-вторых, некоторые веб-страницы содержат код, который препятствует их загрузке в iframe, в таком случае, страница будет открыта в стандартном браузере, а не в iframe. Если эта страница важна для вашего приложения, вам нужно поработать с владельцем страницы для создания альтернативной страницы, которая предназначена специально для вас. В-третьих, так как плагины в приложениях для Магазина Windows не поддерживаются, они так же не будут загружены для страницы, которая загружена в iframe. В итоге, загружать в приложение веб-содержимое, которое не принадлежит приложению - дело рисковое.
Далее, поддержка iframe не предназначена для того, чтобы позволить вам строить приложение исключительно из удалённых веб-страниц. Раздел 2.4. "Сертификационных требований к приложениям для Windows 8" (http://msdn.microsoft.com/library/windows/apps/hh694083.aspx), фактически, прямо запрещает приложения, которые являются веб-сайтами - основная функциональность приложения должна содержаться внутри приложения, то есть, подразумевается, что она не должна реализовываться средствами веб-сайта, загруженного в элемент iframe. Несколько ключевых причин для этого - то, что веб-сайты обычно не очень хорошо настроены для сенсорного взаимодействия (что нарушает требование пункта 3.5.) и часто не работают нормально в прикрепленном режиме (нарушение требования 3.6.). В итоге, чрезмерное использование веб-контента означает, что приложение не пройдёт сертификацию в Магазине Windows.
Как мы уже видели, схема ms-appx[-web]:/// позволяет приложению ссылаться в элементах iframe на страницы, которые существуют внутри пакета приложения или в веб. Встаёт вопрос: может ли приложение сослаться на содержимое в локальной файловой системе, которое существует за пределами пакета, такое, как динамически создаваемый файл в папке данных приложения? Быть может, приложение использует протокол file:// для адресации содержимого или доступа к нему?
Как бы не хотелось мне сказать вам, что это просто работает, ответ несколько неоднозначен. Во-первых, протокол file:// полностью заблокирован при проектировании системы по различным причинам, связанным с безопасностью, даже для папок с данными приложения, к которым вы обладаете полным доступом. (Другие обычные протоколы так же не поддерживаются в URI iframe src.) К счастью, имеется заменитель, ms-appdata:///, который удовлетворяет часть потребностей. Внутри локального контекста приложения, ms-appdata:/// это - короткое имя для папки с данными приложения, где существуют папки локальных, перемещаемых, временных данных. Итак, если вы создали изображение, названное image65.png в папке локальных данных приложения, вы можете сослаться на него, используя команду ms-appdata:///local/image65.png, и похожим образом - для roaming и temp, где URI может быть включено в CSS, как, например, background.
К несчастью, есть оговорка, как это обычно бывает с контейнером приложения. Она заключается в том, что ms-appdata может быть использована только для ресурсов, а именно, это может быть атрибут src для элементов img (изображение), video (видео) и audio (аудио). Данный подход нельзя использовать для загрузки HTML-страниц, таблиц стилей CSS или JavaScript, не подходит он и для целей навигации (iframe, гиперссылки и так далее). Это так, потому что не представляется возможным создания суб-изолированной среды для подобных страниц, а без этого страница, загруженная с помощью ms-appdata://, получит полный доступ к приложению.
Можете ли вы выполнять динамическую генерацию страниц? Да: вам нужно загрузить содержимое файла и обработать его вручную, вставив в DOM посредством свойства innerHTML или чего-то подобного. Вы можете получить доступ к папкам с данными приложения, воспользовавшись API Windows.Storage.ApplicationData. Для загрузки и рендеринга полной HTML-страницы, нужно, чтобы вы обработали все внешние ссылки, разобрались бы со скриптами, но сделать это можно - если вы действительно этого хотите.
Похожий вопрос касается возможности динамической генерации и исполнения скриптов. Ответ снова подразумевает ограничения. Да, вы можете взять строку JavaScript и передать её в функцию eval или exeScript. Однако, учтите, что требования сертификации Магазина Windows прямо запрещают выполнять это со скриптами, полученными из удалённых ресурсов в локальном контексте (раздел 3.9. требований). Другое предостережение заключается в том, что для подобного кода применяется автоматическая фильтрация, которая предотвращает внедрение скриптов (и другого опасного кода) в DOM через свойства вроде innerHTML и outerHTML, и методы, такие, как document.write и DOMParser.parseFromString. Но есть ситуации, когда вы, как разработчик точно знаете, что делаете, наслаждаясь жонглированием бензопилами и горящими кинжалами, и, таким образом, хотите обойти подобные ограничения, особенно - используя библиотеки сторонних разработчиков (смотрите врезку ниже). Понимая это, Microsoft предоставляет механизм для того, чтобы сознательно всё это обойти: MSApp.execUnsafeLocalFunction. Подробности об этом вы можете найти в материале "Разработка безопасных приложений", (http://msdn.microsoft.com/library/windows/apps/hh849625.aspx), который, помимо этой темы, содержит информацию еще по некоторым вещам, которые я сюда не включал. Один из подразделов этого документа - касается различных вариаций атрибута sandbox для iframe. Так же, для того, чтобы лучше разобраться в этом, можете посмотреть пример "JavaScript iframe sandbox attribute sample" (http://code.msdn.microsoft.com/windowsapps/JavaScript-iframe-sandbox-0f077ece)
Как ни странно, WinJS, на самом деле, упрощает жонглирование опасными предметами! WinJS.Utilities.setInnerHTMLUnsafe, setOuterHTMLUnsafe, и insertAdjacentHTMLUnsafe- это "обёртки" для вызова методов DOM, которые могут отфильтровать опасное содержимое.
Рассказав всё это (вы ведь любите углубляться в детали?), давайте рассмотрим пример использования ms-appdata. Скорее всего, этот механизм вы будете широко использовать в своих приложениях.
В целом, приложения для Магазина Windows могут использовать библиотеки наподобие jQuery, Prototype, Dojo и так далее, как сказано в лекции 1. Тем не менее, существуют некоторые ограничения и оговорки.
Во-первых, так как страницы локального контекста не могут загружать скрипты из удалённых источников, приложение обычно нуждается во включении подобных библиотек в свой пакет, если не планируется использовать эти возможности только в веб-контексте. WinJS, заметьте, не нужно встраивать в приложение, так как он предоставляется Магазином Windows, но подобные "пакеты фреймворков" не поддерживаются для других библиотек в Windows 8.
Во-вторых, изменения в DOM API и ограничения пакета приложения могут повлиять на библиотеку. Например, библиотечные функции, использующие window.alert, работать не будут. Библиотека, кроме того, не может загружать другую библиотеку из удалённого источника в локальный контекст. Важно отметить, что всё в библиотеке, что подразумевает более высокий уровень доверия, чем обеспечивает контейнер приложения (например, открытый доступ к файловой системе) будет сопряжено с проблемами.
Самая распространённая проблема возникает, когда библиотека пытается добавить элементы или скрипты в DOM (как с помощью innerHTML), это - широко распространённый подход для веб-приложений, который, как правило, не разрешен внутри контейнера приложения. Например, попытка создать виджет для выбора даты jQuery ($("myCalendar").datepicker()) выдаст такого рода ошибку. Вы можете обойти эту проблему на уровне приложения, заключив вышеприведенный код в MSApp.execUnsafeLocalFunction, но это не решит проблем с внедрением кода, которое происходит из более глубокого уровня библиотеки. В случае с примером с jQuery, который здесь показан, элемент управления может быть создан, но щелчок по нему выдаст другую ошибку.
В итоге, вы можете использовать библиотеки сторонних разработчиков, понимая, что обычно они написаны, исходя из предположений, не всегда применимых к контейнеру приложения. Со временем, конечно, появятся версии библиотек, полностью совместимые с Windows 8.
Отлично! Пережив семь страниц эзотерики, давайте поиграем с настоящим кодом и вернемся к приложению "Here My Am!", которое мы написали в лекции 2. Эта программа использует удобный метод URL.createObjectURL для показа изображения, полученного с камеры в элементе img:
captureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo)
.done(function (capturedFile) {
if (capturedFile) {
that.src = URL.createObjectURL(capturedFile);
}
});
Всё это замечательно: мы просто берем адрес, полагая, что изображение хранится где-то в то время когда мы сформировали URI. На самом деле, фотографии (и видео), захваченные с помощью соответствующего API камеры, просто хранятся во временном файле. Если вы установите точку останова в отладчике и посмотрите на capturedFile, вы увидите, что там находится уродливый путь к файлу, наподобие C:\Users\kraigb\AppData\Local\Packages\ ProgrammingWin8-JS-CH3- HereMyAm3a_5xchamk3agtd6\TempState\picture001.png. Ничего себе! Не выглядит дружественно, да и типичный пользователь вряд ли захочет видеть подобное.
В случае с приложением, подобным данному, скопируем данный временный файл в более подходящее место, для того, чтобы разрешить пользователю, например, выбирать одну из ранее сделанных фотографий (так, как мы сделаем в лекции 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript". Мы создадим копию файла в папке локальных данных приложения и используем ms-appdata для установки img src на данное расположение файла. Начнём с вызова captureUI.captureFileAsync, как и ранее.
//Для использования в promise-вызовах,
// объединенных в цепочку
var capturedFile = null;
captureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo)
.then(function (capturedFileTemp) {
//Убедитесь в том, что проверили правильность полученных данных;
//это может быть null, если пользователь отменит операцию.
if (!capturedFileTemp) { throw ("no file captured"); }
Обратите внимание на то, что вместо вызова done для получения promise-результатов, мы используем then. Это происходит потому, что нам нужно построить цепочку из асинхронных операций и then позволяет информации об ошибках распространяться по цепочке, как мы увидим в следующем разделе. В любом случае, как только мы получим результат в captureFileTemp (файл расположен в подобной, странно выглядящей, папке), затем мы откроем или создадим папку "HereMyAm" внутри папки с локальными данными приложения. Это может быть сделано с помощью Windows.Storage.ApplicationData.current.localFolder, что позволяет нам получить объект Windows.Storage.StorageFolder, который даёт нам метод createFolderAsync:
//Для демонстрации использования ms-appdata usage, скопируем StorageFile в папку HereMyAm
//распложенную в папке appdata/local, и используем ms-appdata для того, чтобы
//обратиться к ней.
var local = Windows.Storage.ApplicationData.current.localFolder; capturedFile = capturedFileTemp;
return local.createFolderAsync("HereMyAm", Windows.Storage.CreationCollisionOption.openIfExists);
})
.then(function (myFolder) {
//Снова, проверяем правильность результата операции
if (!myFolder) { throw ("could not create local appdata folder"); }
Предполагая, что папка создана успешно, myFolder будет содержать другой объект StorageFolder. Затем мы используем его в качестве целевого параметра для файлового метода copyAsync, который, в качестве второго параметра, принимает имя файла. Для этого имени мы просто используем исходное имя, добавив к нему дату и время (заменив двоеточия на дефисы, чтобы имя файла было корректным):
//Присоединяем время создания файла (следует избегать коллизий,
//но нужно заменить двоеточия)
var newName = capturedFile.displayName + " - "
+ capturedFile.dateCreated.toString().replace(/:/g, "-") + capturedFile.fileType;
return capturedFile.copyAsync(myFolder, newName);
})
.done(function (newFile) {
if (!newFile) { throw ("could not copy file"); }
Так как это - последняя асинхронная операция в цепочке, мы используем метод promise-объекта done по причинам, о которых скоро поговорим. В любом случае, если копирование удалось, переменная newFile содержит объект StorageFile для копии, и мы можем сослаться на данный файл, используя URI ms-appdata:
lastCapture = newFile;
//Сохраним для целей общего доступа
that.src = "ms-appdata:///local/HereMyAm/" + newFile.name;
},
function (error) {
console.log(error.message);
});
Полный код вы можете найти в упражнении HereMyAm3a.
Конечно, мы можем использовать URL.createObjectURL с newFile, как ранее (убедившись в установке параметра { oneTimeOnly=true } для того, чтобы избежать утечек памяти). Несмотря на то, что это расходится с целями данного упражнения, работает это отлично (и нагрузка на память, в общем, та же самая, при использовании этих двух методов). На самом деле, нам нужно использовать этот подход, если мы скопируем изображения в библиотеку изображений пользователя. Для того, чтобы это сделать, просто замените Windows.Storage.ApplicationData.current.localFolder на Windows.Storage.KnownFolders.picturesLibrary и объявите возможность Библиотека изображений (Pictures Library) в манифесте. Оба API возвращают StorageFolder, поэтому оставшийся код выглядит так же, за исключением того, что мы используем URL.createObjectURL, так как ни ms-appdata://, ни file:// мы не можем использовать для доступа к библиотеке изображений. Упражнение HereMyAm3a содержит данный код в комментариях.
В предыдущем примере кода вы могли заметить, как мы передаем информацию об исключениях, когда мы не получаем ожидаемый результат в любой из асинхронных операций. Кроме того, у нас есть только один обработчик ошибок в конце конструкции, у нас имеется эта странная конструкция, возвращающая результат (promise-объект, отложенный результат) от каждой последующей асинхронной операции вместо того, чтобы обрабатывать promise-объекты то тут, то там.
Хотя, на первый взгляд это может выглядеть странным, подобный подход, на самом деле, является наиболее распространённым шаблоном для работы с последовательными асинхронными операциями, так как он работает лучше, чем более очевидный подход с использованием вложенных конструкций. Вложенность подразумевает вызов следующего асинхронного API внутри обработчика завершения предыдущего, каждый из них завершается оператором done. Вот как код из предыдущего примера может быть переписан с использованием такого подхода (посторонний код удалён для упрощения примера):
captureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo)
.done(function (capturedFileTemp) {
//...
local.createFolderAsync("HereMyAm", ...)
.done(function (myFolder) {
//...
capturedFile.copyAsync(myFolder, newName)
.done(function (newFile) {
})
})
});
Единственное преимущество такого подхода заключается в том, что каждый обработчик завершения будет иметь доступ ко всем переменным, объявленным ранее. А вот недостатков у него довольно много. С одной стороны, здесь, между асинхронными вызовами, обычно достаточно много промежуточного кода, что делает структуру выглядящей неопрятно. Важнее то, что обработка ошибок значительно усложняется. Когда promise-вызовы вложены друг в друга, обработку ошибок нужно проводить на каждом из уровней. Если ошибка возникнет на одном из внутренних уровней, обработчик на внешнем уровне не сможет её обработать. Таким образом, каждый promise-вызов нуждается в собственном обработчике ошибок, что превращает базовую структуру подобного кода в нечто, напоминающее спагетти:
captureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo)
.done(function (capturedFileTemp) {
//...
local.createFolderAsync("HereMyAm", ...)
.done(function (myFolder) {
//...
capturedFile.copyAsync(myFolder, newName)
.done(function (newFile) {
},
function (error) {
})
},
function (error) {
});
},
function (error) {
});
Не знаю, как вы, а я теряюсь во всех этих } и ) (хотя очень старался вспомнить занятия по LISP в колледже), здесь непросто увидеть, какая функция обработки ошибок применяется к конкретному асинхронному вызову.
Объединение promise-вызовов в цепочку решает все эти проблемы, в обмен на небольшую плату в виде необходимости объявления нескольких дополнительных переменных за пределами цепочки. При объединение в цепочку, вы выполняете команду return для следующего promise-объекта в каждом из обработчиков завершения, вместо того, чтобы дополнять его вызовом done. Это позволяет вам выравнивать все асинхронные вызовы лишь однажды, и даёт эффект распространения ошибок по цепочке. Когда в promise-вызове произошла ошибка, вы видите, что то, что возвращается, является promise-объектом, и если вы вызовете метод then (но не done - смотрите следующий раздел), снова будет возвращён другой promise-объект, содержащий ошибку. В результате, любые ошибки быстро доходят по цепочке до первого доступного обработчика ошибок, что позволяет вам иметь лишь один обработчик ошибок в самом конце:
captureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo)
.then(function (capturedFileTemp) {
//...
return local.createFolderAsync("HereMyAm", ...);
})
.then(function (myFolder) {
//...
return capturedFile.copyAsync(myFolder, newName);
})
.done(function (newFile) {
},
function (error) {
})
Для моих глаз (и моего ума) эта структура кода кажется более чистой - и такой код легче отлаживать и поддерживать. Если хотите, вы даже можете завершить цепочку вызовом done(null, errorHandler), заменив предыдущий done на then:
captureUI.captureFileAsync(Windows.Media.Capture.CameraCaptureUIMode.photo)
//...
.then(function (newFile) {
})
.done(null, function (error) {
})
})
И, наконец, немного об отладке promise-вызовов, объединенных в цепочку (или вложенных, если уж на то пошло). Каждый шаг включает асинхронную операцию, поэтому вы не можете просто применить пошаговое исполнение, как это делается с синхронным кодом (в противном случае, вы окажетесь глубоко в WinJS). Вместо этого, установите точку останова на первой строке внутри каждого обработчика завершения и на первой строке функции обработки ошибок, которая находится в конце. Когда каждая из точек останова сработает, вы можете пошагово исполнить обработчик завершения. Когда вы дойдёте до следующего асинхронного вызова, нажмите на кнопку Далее (Continue) в Visual Studio, в итоге сможет выполниться асинхронная операция, после которой сработает точка останова в следующем обработчике завершения (или точка останова в обработчике ошибок).
Хотя обрабатывать ошибки в конце цепочки promise-вызовов - это обычная практика, как показано в коде выше, вы можете использовать обработчик ошибок в любом месте цепочки - и then, и done принимают одинаковые аргументы. Если на данном уровне возникает исключение, оно будет обработано ближайшим внутренним обработчиком ошибок.
Это приводит нас к различию между then и done. Во-первых, then возвращает еще один promise-объект, тем самым позволяя объединять команды в цепочки, в то время как done возвращает undefined, поэтому он должен быть в конце цепочки. Во-вторых, если исключение возникает внутри асинхронной операции с методом then и на данном уровне нет обработчика ошибок, информация об ошибке сохраняется в promise-объекте, который возвращает then. В противоположность этому, если done видит исключение и не имеет обработчика ошибок, он направляет исключение в цикл событий (event loop) приложения. Оно обходит любые локальные (синхронные) блоки try/catch, хотя вы можете перехватить их с помощью обработчиков WinJS.Application.onerror и window.onerror. (Последний получит ошибку, если первый её не обработал). Если вы этого не сделаете, приложение будет остановлено и информация об ошибке будет отправлена в Магазин Windows и появится на информационной панели. По этой причине мы рекомендуем, чтобы вы реализовывали обработчик WinJS.Application.onerror.
На практике, это означает, что если вы завершите цепочку promise-вызовов
С promise-объектами вы можете делать и многое другое, кстати, вроде комбинирования их, отмены и так далее. Мы вернемся к этому в конце данной лекции.
Когда идёт разговор об исключениях и обработке ошибок, разработчиков обычно огорчает то, что команды startLog (http://msdn.microsoft.com/en-us/library/windows/apps/hh701617.aspx), stopLog (http://msdn.microsoft.com/en-us/library/windows/apps/hh701626.aspx) и formatLog (http://msdn.microsoft.com/en-us/library/windows/apps/hh701587.aspx) из пространства имен WinJS.Utilities (http://msdn.microsoft.com/en-us/library/windows/apps/br229783.aspx), которые обеспечивают дополнительную функциональность на базе console.log. Думаю, вы сможете узнать об этом из документации, однако, считаю важным довести информацию о них до вашего сведения.
Другая функция DOM API, которой вам может захотеться воспользоваться - это window.close. Вы можете пользоваться ей во время разработки, но в опубликованном приложении Windows воспринимает её как аварийное завершение программы и генерирует в ответ отчёт об ошибке. Этот отчёт появится в информационной панели Магазина Windows для вашего приложения, с сообщением о том, что вам не следует пользоваться данной функцией! В конце концов, приложения для Магазина Windows не должны предоставлять собственных средств для закрытия приложения, как описано в разделе 3.6. сертификационных требований.
Однако, возможна ситуация, когда опубликованное приложение должно завершить работу в ответ на неисправимое происшествие. Хотя вы можете использовать для этого команду window.close, лучше воспользоваться командой MSApp.terminateApp, так как она позволяет вам, кроме того, включить информацию о произошедшей ошибке. Эти подробности будут показаны в информационной панели Магазина Windows, упрощая диагностику проблемы.
В дополнение к информационной панели Магазина Windows, вам следует ознакомиться со средством просмотра событий Windows (Windows Event Viewer)
Для того, чтобы активировать эту возможность, для начала вам нужно перейти к разделу Журналы приложений и служб, развернуть ветку Microsoft > Windows > AppHost, щёлкнуть левой кнопкой мыши по элементу Администратор (Admin) для того, чтобы выделить его (это важно), после чего щёлкнуть по элементу Администратор правой кнопкой мыши и выбрать Вид > Отобразить аналитический и отладочный журналы (View > Show Analytic and Debug logs) для включения вывода полной информации, как показано на Рис. 3.4. Это включит отслеживание ошибок и исключений. Затем щёлкните правой кнопкой пункт AppTracing (так же в разделе AppHost) и выберите пункт Включить журнал (Enable Log). Это позволит отслеживать ваши вызовы console.log, так же, как и другую диагностическую информацию, поступающую от хост-процесса приложения.
(рис 3.4) События хост-процесса приложения, такие, как необработанные исключения и ошибки загрузки, можно найти в средстве просмотра событий
Мы уже говорили о диалоговом окне Исключения в Visual Studio в лекции 2. Вернитесь к Рис. 2.16. Для каждого типа JavaScript-исключений это диалоговое окно предоставляет два флага, названные Вызванное (Thrown) и Не обработанное пользовательским кодом (User-unhandled). Установка флага Вызванное приведет к отображению диалогового окна в отладчике (Рис. 3.5), когда будет вызвано исключение, независимо от того, было ли оно обработано и до того, как сработают любые из ваших обработчиков событий. Если у вас есть обработчики событий, вы можете без проблем нажать на кнопку Продолжить (Continue) в диалоговом окне и ошибка будет передана вашим обработчикам ошибок. (В противном случае работа приложения завершится). Если вы, вместо этого нажмёте на кнопку Остановить отладку (Break), вы можете увидеть подробности об исключении в окне Локальные (Locals), как показано на Рис. 3.6.
(рис 3.5) Диалоговое окно исключения в Visual Studio. Как показывает диалоговое окно, можно безопасно нажать на кнопку Продолжить (Continue), если в вашем приложении есть обработчик ошибок; в противном случае работа приложения завершится. Обратите внимание на флаг в этом окне, который позволяет быстро включить опцию Вызванное (Thrown) для данного типа исключений в диалоговом окне Исключения
(рис 3.6) Информация в окне Visual Studio Локальные (Locals), когда вы останавливаете работу приложения при исключении
Опция Не обработанное пользовательским кодом (User-unhandled) (включенная для всех типов исключений по умолчанию) отобразит похожее диалоговое окно всякий раз, когда исключение попадёт в цикл событий, показывая то, что оно не было обработано в функции обработки ошибок приложения (в "пользовательском" коде - с точки зрения системы).
Обычно вы включаете параметр Вызванное только для тех исключений, которые вы собираетесь обрабатывать. Включение их всех может привести к сложностям при работе с приложением. Вы можете сделать это в качестве эксперимента и затем оставить данный параметр установленным лишь для тех исключений, которые вы ожидаете перехватить. Оставьте параметр Не обработанное пользовательским кодом для всех остальных исключений. На самом деле, если у вас нет особых причин не делать этого, убедитесь в том, что параметр Не обработанное пользовательским кодом включен у группы Ошибки времени выполнения JavaScript (JavaScript Runtime Exceptions), так как эта установка включает в себя все исключения, даже не перечисленные. При таком подходе вы можете перехватить (и исправить) любое исключение, которое может внезапно завершить работу приложения, а это кое-что из того, с чем вашим пользователям никогда не следует сталкиваться.
Для начала, позвольте мне поздравить вас с тем, что вы продвинулись так далеко в этой лекции, полной подробностей. В качестве награды, давайте поговорим кое о чем гораздо более осязаемом и привлекательном: о том, как активируется приложение, о последовательности действий, выполняемых при его запуске. Это может случиться множеством способов, с плитки на Начальном экране, с помощью контракта, при ассоциации с типом файлов или схемой URI. Во всех этих случаях активации вы будете писать много кода для инициализации структур данных, перезагрузки ранее сохранённого состояния и делать всё для того, чтобы предоставить пользователям хороший опыт взаимодействия с приложением.
Говоря об активации приложения, мы, на самом деле, должны сделать шаг назад, во время до того момента, когда будет загружен хост-процесс приложения, к тому моменту, когда пользователь нажал на плитку приложения на Начальном экране или когда ваше приложение было запущено каким-то другим способом. Первое, что происходит, еще до того, как загружается или исполняется код приложения, это - показ Windows экрана-заставки приложения, состоящего из изображения и фонового цвета, которые заданы в вашем манифесте.
Экран-заставка, который показывается, как минимум, на 0.75 секунды, это не просто картинка , которая показывает пользователям что-то интересное, пока приложение запускается (что лучше, чем песочные часы). Она так же занимает то пространство, где будет запущено приложение, в итоге, прямо привлекая внимание пользователя к этому месту. Это может быть область заполняющего просмотра, наложенная на другое приложение область, выводимая чудо-кнопкой Общий доступ или Поиск, или область режима прикрепленного просмотра, если приложение запускается сразу в прикрепленном режиме просмотра. В течение этого времени, запускается экземпляр хост-процесса приложения, обрабатываются и выводятся ваши HTML- страницы с CSS и загружается, обрабатывается и исполняется ваш JavaScript-код, по пути вызывая события, как мы увидим в следующем разделе. Когда готова первая страница приложения, система убирает экран-заставку.
Экран-заставка, вместе с плиткой вашего приложения, это один из весьма важных путей для брендирования вашего приложения, поэтому убедитесь в том, что ваш художник (или художники) уделили этим вопросам максимум внимания. Есть и другие графические элементы и установки в манифесте, которые так же играют роль в брендинге и в общем виде приложения в системе, как показано ниже, в таблице. Особо отметьте то, что шаблоны в Visual Studio и Blend предоставляют некоторые материалы-заполнители по умолчанию, которые совершенно непривлекательны. Поэтому дайте прямо сейчас торжественную клятву, что вы никогда не будете загружать приложения в Магазин, когда в проекте всё еще присутствуют эти материалы! (За дополнительными сведениями обратитесь к документу: "Руководство и контрольный список для экранов-заставов", (http://msdn.microsoft.com/library/windows/apps/hh465338.aspx).
Вы можете видеть, что в таблице перечислено по несколько размеров для различных изображений, заданных в манифесте для поддержки различной плотности пикселей: для масштабирования в 100%, 140%, 180%, и даже несколько для 80% (не пренебрегайте последним, он, как правило, используется для большинства настольных мониторов). И, в то время, как вы можете просто предоставить одно изображение в масштабе 100% для каждого из перечисленных элементов, практически гарантированно, что версия с увеличенным масштабом графических элементов будет выглядеть не очень хорошо. Поэтому, почему бы не сделать ваше приложение выглядящим наилучшим образом? Уделите время на то, чтобы осознанно создать каждый отдельный графический элемент.
| Закладка манифеста | Раздел | Элемент | Использование | Размеры изображений | ||
|---|---|---|---|---|---|---|
| 100% | 140% | 180% | ||||
| Упаковка | n/a | Значок | Изображение плитки/значка, используемое для приложения на странице описания в Магазине Windows. | 50x50 | 70x70 | 90x90 |
| Интерфейс приложения | n/a | Отображаемое имя пакета | Появляется при просмотре Начального экрана в режиме "все приложения" , в результатах поиска, при использовании чудо-кнопки Параметры, в Магазине Windows. | n/a | n/a | n/a |
| Плитка | Значок | Квадратное изображение плитки | 150x150 (+80% масштаб 120x120) | 210x210 | 270x270 | |
| Широкий значок | Необязательное изображение для широкой плитки. Если присутствует, отображается по умолчанию, но пользователь, если захочет, может использовать вместо него квадратное изображение. | 310x150 (+80% масштаб 248x120) | 434x210 | 558x270 | ||
| Мелкий значок | Плитка, которая используется при уменьшенном просмотре Начального экрана, при просмотре в режиме "все приложения", в панелях чудо-кнопок Поиск и Общий доступ, если приложение поддерживает соответствующие контракты в качестве целевого. Кроме того, используется на плитке приложения, если вы выбрали опцию показа логотипа приложения в левом нижнем углу плитки вместо его названия. | 30x30 (+80% масштаб 24x24) | 42x42 | 54x54 | ||
| Показывать имя | Задаёт, следует ли показывать имя приложения на плитке приложения (на всех, не показывать, только на стандартном или на широком значке). Установите этот параметр в значение "Нет значков", если плитка вашего приложения включает имя приложения. | n/a | n/a | n/a | ||
| Краткое имя | Необязательное. Если задано, используется в качестве имени приложения на плитке, заменяя Отображаемое имя пакета, так как это имя может быть слишком длинным для квадратной плитки. | n/a | n/a | n/a | ||
| Текст переднего плана | Цвет текста, которым выведено имя приложения на плитке, если применимо (смотрите Показывать имя). Возможны варианты Светлый (Light) и Тёмный (Dark). Соотношение контраста между этим текстом и цветом фона должен равняться 1.5. | n/a | n/a | n/a | ||
| Цвет фона | Цвет, используемый для прозрачных областей любых изображений на плитке, кроме того, задаёт фон по умолчанию для дополнительных плиток, фон для окон уведомлений, цвет кнопок в диалоговых окнах приложения, цвет границ для тех случаев, когда приложение обеспечивает функциональность контракта средства выбора файлов или контактов, цвет заголовков в панели параметров, цвет страницы приложения в Магазине Windows. Кроме того, задаёт фоновый цвет для экрана-заставки, если он не задан отдельно. | n/a | n/a | n/a | ||
| Уведомления | Эмблема | Отображается около уведомления индикатора событий для идентификации приложения на экране блокировки (редко, так как это требует объявления дополнительных возможностей) | 24x24 | 33x33 | 43x43 | |
| Заставка | Заставка | Когда приложение запускается, это изображение выводится в центре экрана на фоне, цвет которого задаёт Цвет фона. Если нужно, изображение может использовать прозрачность. | 620x300 | 868x420 | 1116x540 | |
| Цвет фона | Цвет, который заполняет основную область экрана-заставки. Если не задан, вместо него будет использован Цвет фона из Интерфейса приложения | n/a | n/a | n/a | ||
Обратите внимание на то, что в таблице 80% масштаб указан для графических элементов, используемых в особых случаях, таких, как режим низкого DPI (обычно, когда DPI меньше 130 и разрешение ниже, чем 2560х1440) и изображения в таком масштабе следует предоставлять вместе с другими. Кроме того, обратите внимание на дополнительные графические элементы, помимо Значка (Packaging Logo) (первый элемент в таблице), которые вам понадобятся перед отправкой приложения в Магазин Windows. Подробности вы найдете в материале "Выбор изображений для вашего приложения" (http://msdn.microsoft.com/library/windows/apps/hh846296.aspx), в разделе "Рекламные изображения".
Когда вы сохраняете эти файлы, добавьте .scale-80, .scale-100, .scale-140, и .scale-180 к именам файлов перед расширениями, как, например, в имени: splashscreen.scale-140.png. Это позволит вам и в манифесте, и в других местах приложения ссылаться на изображение, используя лишь базовое имя, такое, как splashscreen.png, и Windows автоматически загрузит подходящий вариант для текущего масштаба. В противном случае она будет искать изображение без суффикса. И не нужно дополнительного кода! Это показано в упражнении HereMyAm3b, где я добавил всю необходимую для брендинга графику (с некоторым дополнительным текстом на каждом рисунке для того, чтобы показать масштаб). Для того, чтобы протестировать эти различные графические элементы, используя кнопку Изменить разрешение (Set resolution/scaling) в симуляторе - обратитесь к Рис. 2.5 во второй лекции. Вы можете выбирать различные плотности пикселей на экране размером 10,6" (1366 x 768 =100%, 1920 x 1080 =140%, и 2560 x 1440 = 180%). Кроме того, вы увидите, что 80%-й масштаб используется при других установках экрана, в том числе - на 23" и 27". Во всех случаях, установки влияют на то, какое изображение будет использовано на Начальном экране и на экране-заставке, но обратите внимание на то, что вам может понадобиться выйти и перезапустить симулятор для того, чтобы изменение масштаба возымело действие.
Кроме того, вы должны знать о том, что полноцветные изображения фотографического качества не очень хорошо поддаются качественному уменьшению (Значок (Store Logo, Эмблема магазина), Мелкий значок (Small Logo, Мелкая эмблема)). Это одна из причин того, что данные значки обычно имеют простой дизайн в стиле Магазина Windows, который, кроме того, способствует уменьшению их размеров при сжатии. Это - отличное соображение, следуя которому можно сделать размер пакета вашего приложения меньше, когда вы создаёте больше версий изображений для различных контрастов и языков. Больше об этом будет в лекции 6 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой".
Так как хост-процесс приложения построен на тех же самых механизмах обработки и рендеринга, что и Internet Explorer, общая последовательность событий при активации более или менее похожа на последовательность, характерную для веб-приложений и наблюдаемую в браузере. На самом деле, скорее "более", чем "менее"! Когда вы запускаете приложение с помощью его плитки, вот что происходит с точки зрения Windows:
document.DOMContentLoaded. Вы можете использовать его для того, чтобы произвести дальнейшую инициализацию, в особенности, связанную с DOM (используется редко).
Windows.UI.WebUI.WebUIApplication.onactivated. Это обычно происходит, когда вы выполните все стартовые задачи, создадите экземпляр WinJS и элементов управления, инициализируете состояние и так далее.
activated завершится (если приложение не запросило отложенную операцию, как рассказано далее, в разделе "Задержанная активация").
body.onload. Обычно оно не используется в приложениях для Магазина Windows, хотя оно может использоваться кодом из библиотек сторонних разработчиков.Важной особенностью является то, что приложение снова может быть активировано по множеству различных причин, таких, как контракт или ассоциация, даже тогда, когда оно уже запущено. Как мы увидим в дальнейших лекциях, загружаемая страница (шаг 3), может варьироваться при реализации контракта, и если определенная страница уже загружена, она получит только событие Windows.UI.WebUI.WebUIApplication.onactivated, не получив другие.
Сейчас, однако, давайте сконцентрируемся на работе с этим базовым процессом загрузки и, так как вы обычно выполняете работу по инициализации внутри события activated, рассмотрим его структуру поближе.
Для того, чтобы оптимизировать генерирование байт-кода при обработке HTML, CSS и JavaScript-файлов, Магазин Windows требует, чтобы все .html, .css и .js-файлы были сохранены в кодировке UTF-8. Это установлено по умолчанию для всех файлов, создаваемых в Visual Studio или в Blend. Если вы импортируете активы из других источников, проверьте их кодировку. В диалоге Сохранить как (Save As) в Visual Studio (в Blend сейчас нет этой возможности), выберите Сохранить с кодировкой (Save with Encoding) и установите кодировку в Юникод (UTF-8, с сигнатурой), кодовая страница 65001 (Unicode (UTF-8 with signature) - Codepage 65001). Комплект сертификации приложений для Windows (Windows App Certification Kit) выдаст предупреждение, если обнаружит файлы не в этой кодировке.
В том же духе, минимизация JavaScript-кода не особенно важна приложениям для Магазина Windows. Так как пакет приложения загружается из Магазина Windows целиком и часто содержит другие активы, которые гораздо больше, чем ваши файлы с программным кодом, минимизация здесь не играет большой роли. Когда пакет установлен, генерация байт-кода подразумевает, что JavaScript-файлы в пакете уже обработаны и оптимизированы, в итоге, минимизация не даёт дополнительного выигрыша в производительности.
Как мы видели в лекции 2, новый проект, создаваемый в Visual Studio или в Blend даёт вам следующий код в файле js/default.jd (некоторые комментарии удалены):
(function () { "use strict";
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
app.onactivated = function (args) {
if (args.detail.kind === activation.ActivationKind.launch) {
if (args.detail.previousExecutionState !==
activation.ApplicationExecutionState.terminated) {
// TODO: Это приложение было вновь запущено. Инициализируйте
// приложение здесь.
} else {
// TODO: Это приложение было вновь активировано после приостановки.
// Восстановите состояние приложения здесь.
}
args.setPromise(WinJS.UI.processAll());
}
};
app.oncheckpoint = function (args) {
};
app.start();
})();
Пройдёмся по этому коду для того, чтобы пересмотреть то, что мы уже знаем и завершить понимание структуры этого базового кода:
(function () { … })(); содержащая всё, это, основа, шаблон модуля JavaScript.
"use strict" предписывает интерпретатору JavaScript применить Строгий режим (Strict Mode) (http://msdn.microsoft.com/library/br230269.aspx), функцию ECMAScript 5. Это проверка для неоптимальных методов программирования, таких, как использование неявно объявленных переменных, в итоге, его стоит оставить там, где он есть.
var app = WinJS.Application; и var activation = Windows.ApplicationMode.Activation; оба создают существенно сокращенные псевдонимы для часто используемых пространств имен. Это обычный подход для упрощения множественных ссылок на одни и те же части WinJS или WinRT.
app.onactivated = function (args) {…} назначает обработчик событию WinJS.UI.onactivated, которое является оболочкой для события Windows.UI.WebUI.WebUIApplication.onactivated. В этом обработчике:args.detail.kind идентифицирует тип активации.
args.detail.previousExecutionState идентифицирует состояние приложения до активации, что определяет, нужно ли перезагружать состояние сеанса работы.
WinJS.UI.processAll создаст экземпляры элементов управления WinJS - то есть, элементы, которые содержат атрибут data-win-control , как мы рассмотрим в лекции 4, "Элементы управления, их стилизация и привязка данных".
args.setPromise указывает Windows ждать до тех пор, пока завершится WinJS.UI.processAll, прежде чем убирать экран-заставку (смотрите раздел "Задержанная активация" дальше в этой лекции).app.oncheckpoint получает пустой обработчик в шаблоне: мы остановимся на этом в разделе "Переход между событиями жизненного цикла приложений".
app.start() (WinJS.Application.start()) инициирует обработку событий, которые WinJS поставил в очередь при запуске.Обратите внимание на то, как мы не обрабатываем напрямую любые события, запускаемые Windows, наподобие DOMContentLoaded или Windows.UI.WebUI.WebUIApplication.onactivated. Может быть, мы просто игнорируем эти события? Вовсе нет: один из удобных сервисов, которые WinJS предоставляет посредством WinJS.UI.Application - это упрощённая структура активации и других событий времени жизни приложения. Совершенно необязательно, но очень полезно.
В случае со start, например, происходит пара вещей. Во-первых, объект WinJS.Application прослушивает различные события, которые исходят из различных источников (DOM, WinRT и других) и собирает их в один объект, с которым вы регистрируете ваши собственные обработчики. Во-вторых, когда WinJS.Application принимает события активации, он не просто передает их в обработчики приложения, так как таких обработчиков, на самом деле, может и не быть. Поэтому он устанавливает эти события в очередь до тех пор, пока приложение не сообщит о том, что оно действительно готово, вызывая start. В этот момент WinJS проходит по очереди и запускает эти события. Вот и всё, что нужно сделать.
Как показывает код из шаблона, приложения обычно выполняют большую часть работы по инициализации в событии activated, где существует несколько потенциальных ветвей кода, прохождение по которым зависит от args.details (объект IActivatedEventArgs (http://msdn.microsoft.com/library/windows/apps/windows.applicationmodel.activation.iactivatedeventargs.aspx)). Если вы посмотрите документацию по WinJS.Application.onactivated (http://msdn.microsoft.com/library/windows/apps/br212679.aspx), вы увидите, что реальное содержимое args.details зависит от конкретного вида активации. Все виды активации, однако, имеют три общих свойства:
| args.details Свойства | Тип (в Windows.Application-Model.Activation) | Описание |
|---|---|---|
Kind | ActivationKind (http://msdn.microsoft.com/library/windows/apps/windows.applicationmodel.activation.activationkind.aspx) | Причина активации. Возможные значения launch (наиболее типично); search, shareTarget, file, protocol, fileOpenPicker, fileSavePicker, contactPicker, и cachedFileUpdater (для обслуживания контрактов); и device, printTask, Settings, cameraSettings (обычно используется приложениями, работающими с устройствами). Для каждого поддерживаемого типа активации, приложение будет иметь соответствующий путь инициализации. |
previousExecutionState | ApplicationExecutionState (http://msdn.microsoft.com/library/windows/apps/windows.applicationmodel.activation.applicationexecutionstate.aspx) | Состояние приложения до данной активации. Возможные значения notRunning, running, suspended, terminated, и closedByUser. Обработка случая terminated наиболее распространена, так как в данном случае вам потребуется восстановить ранее сохраненное состояние сеанса работы с приложением (смотрите "Переход между событиями жизненного цикла приложений"). |
splashScreen | splashScreen (http://msdn.microsoft.com/library/windows/apps/windows.applicationmodel.activation.splashscreen.aspx) | Содержит событие ondismissed для тех случаев, когда экран-заставка не отображается. Так же содержит свойство imageLocation (Windows.Foundation.Rect) с координатами отображения экрана-заставки, как отмечено в разделе "Расширенные экраны-заставки" |
Дополнительные свойства предоставляют соответствующие данные по активации. Например, свойство launch предоставляет titleId и arguments от дополнительных плиток (смотрите лекцию 2 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой"). Тип активации search (следующий наиболее часто используемый) предоставляет queryText и language, protocol предоставляет uri и так далее. Мы увидим, как использовать многие из этих свойств в соответствующем им контексте, и иногда они применимы не к default.html, а к другим страницам. То, что включено в шаблон (и то, что мы уже используем в приложении вроде "Here My Am!"), в основном, касается обработки обычного процесса запуска приложения с плитки (или внутри отладчика Visual Studio).
WinJS.Application не связан только с активацией - его цель - централизовать события от нескольких различных источников и превратить их в собственные события. Опять же, это позволяет приложению прослушивать события из одного источника (назначая обработчик с помощью addEventListener(<event>) или on<event>, поддерживается и то, и другое). Вот полный перечень этих событий и их источников (если поставлено в очередь, событие вызывается в WinJS.Application.start):
activated Ставится в очередь в локальном контексте для Windows.UI.WebUI.WebUIApplication.-onactivated. В веб-контексте, где WinRT неприменима, вместо этого ставится в очередь для DOMContentLoader (где тип запуска - launch, а previousExecutionState установлено в notRunning ).
Utilities.ready (http://msdn.microsoft.com/en-us/library/windows/apps/br211903.aspx), посредством которой вы можете задать обратный вызов для DOMContentLoaded. Это используется внутри WinJS, на самом деле, для того, чтобы гарантировать, что любое обращение к WinJS.UI.processAll будет обработано после DOMContentLoaded.activated.
ready Ставится в очередь после loaded и activated. Это событие - последнее из событий в цепочке событий активации.
error Вызывается, если возникает исключение при диспетчеризации другого события. (Если ошибка не обработана, она передаётся в window.onerror).
checkpoint Это событие сообщает приложению, когда следует сохранить состояние сеанса работы, которое понадобится при повторном запуске приложения из предыдущего состояния terminated. Это событие вызывается в ответ на событие документа beforeunload и на событие Windows.UI.WebUI.WebUIApplication.onsuspending.
unload Так же вызывается для beforeunload после того, как будет вызвано событие checkpoint.
settings Вызывается в ответ на Windows.UI.ApplicationSettings.SettingsPane.oncommandsrequested (Смотрите лекцию 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript").С большинством из этих событий (за исключением error и settings), args, которые вы получите, содержат метод, который называется setPromise. Если вам нужно произвести любую асинхронную операцию (вроде XmlHttpRequest), вы можете получить promise-объект для этой работы и передать его в setPromise вместо того, чтобы самостоятельно вызывать его then или done. WinJS, таким образом, не обработает следующее событие в очереди до тех пор, пока получение данного отложенного результата не будет выполнено. Отметим, чтобы быть честными, что нет разницы между этим подходом и обычным вызовом done у promise-объекта самостоятельно внутри событий loaded, ready и unload. А вот в случае с activated и checkpoint (в особенности в состоянии suspending) разница есть, так как Windows, в противном случае, полагает, что вы сделали всё, что нужно, как только вы возвратились из обработчика; подробнее об этом - в разделе "Задержанная активация". В итоге, если у вы выполняете асинхронные задачи в обработчиках этих событий, лучше всего использовать setPromise. Так как WinJS.UI.processAll - это сама по себе асинхронная операция, шаблон заключает её в оболочку из setPromise, в итоге, экран-заставка не исчезнет до тех пор, пока не буду созданы экземпляры элементов управления WinJS.
Я думаю, что вы сочтёте WinJS.Application удобным инструментом для ваших приложений, и это средство, кроме того, предоставляет еще некоторые возможности, как описано здесь: "Пространство имен WinJS.Application" (лекции 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript".
Еще нужно сказать о методах queueEvent и stop. Метод queueEvent помещает событие в очередь, оно попадёт на диспетчеризацию, когда существующие элементы выйдут из очереди, любым прослушивателем события, который вы установили с помощью WinJS.Application. События просто идентифицируются по строке, в итоге, вы можете ставить в очередь события с любым именем, которое вам нравится, и вызывать WinJS.Application.addEventListener с тем же самым именем в любом месте приложения. Это можно использовать для централизации пользовательских событий, вызываемых и при старте и в другое время в процессе выполнения приложения без создания отдельной глобальной функции для подобных целей. Кроме того, это мощное средство, с помощью которого раздельно объявленные, независимые компоненты, могут вызывать события, которые агрегирует один обработчик. (В качестве примера использования queueEvent смотрите Сценарий 2 в примере "Модель приложения" (http://code.msdn.microsoft.com/windowsapps/ApplicationModel-Sample-4be6575d)).
Что касается stop, это событие предоставляется для помощи во время модульного тестирования, во время которого вы можете симулировать различные последовательности активации, не перезапуская приложение и так или иначе симулировать нужные условия при перезапуске. Когда вы вызываете stop, WinJS удаляет прослушиватели событий, очищает очередь событий и очищает объект sessionState, но приложение продолжает работать. Затем вы можете вызвать queueEvent для того, чтобы заполнить очередь событий теми событиями, которыми хотите, и затем снова вызвать start для того, чтобы обработать эту очередь. Этот процесс может повторяться столько раз, сколько нужно.
Сейчас, хотя экран-заставка, отображаемый по умолчанию, позволяет привлечь внимание пользователя, пользователь не может наблюдать за ним, если тот же самый экран отображается действительно долго. На самом деле, "действительно долго" для типичного пользователя - это около 15 секунд, после чего он начинает полагать, что приложение зависло и возвращается на Начальный экран для того, чтобы запустить другое приложение, которое не будет бесполезно тратить его время.
Честно говоря, до тех пор, пока пользователь держит ваше приложение на переднем плане и не переключается, Windows даёт приложению всё необходимое ему время. Но когда пользователь переключается на Начальный экран или на другое приложение, вы ограничены 15-ю секундами. Если ваше приложение не на переднем плане, Windows даёт приложению 15 секунд на то, что приложение выполнит то, что есть в app.start и вызовет событие activated, в этот момент домашняя страница должна быть отрисована. В противном случае, бабах! Windows автоматически завершает ваше приложение.
Первое условие, конечно, заключается в том, чтобы оптимизировать процесс загрузки для того, чтобы он был как можно более быстрым. Но, всё таки, иногда приложениям, на самом деле, нужно больше, чем 15 секунд для того, чтобы запуститься, в особенности при первом запуске после установки, в итоге, ему следует дать пользователю знать о том, что что-то происходит. Например, пакет приложения может содержать изрядное количество сжатых данных, когда оно загружено из Магазина Windows, и которые должны быть распакованы в локальную файловую систему при первой загрузке, в итоге, последующие запуски будут происходить гораздо быстрее. Многие игры поступают так с графикой и другими ресурсами, оптимизируя локальное хранение данных под характеристики устройства. Другие приложения могут заполнять локальную IndexedDB из данных, хранящихся в JSON-файле или загружать и кэшировать данные из онлайнового сервиса.
Возможно, что пользователь попытается загрузить ваше приложение вскоре после перезагрузки системы, в таком случае система может быть загружена множеством дисковых операций. Если вы загружаете данные с диска при активации приложения, этот процесс может занять больше времени, чем обычно.
Во всех эти случаях, всегда, когда приложение рискует превысить лимит в 15 секунд, вам понадобится реализовать расширенный экран-заставку (extended splash screen). Это подразумевает скрытие вашей реальной домашней страницы за другим элементов div, который выглядит точно так же, как системный экран-заставка, однако, находится под контролем приложения, и, в итоге, способен отображать индикаторы прогресса или другие элементы интерфейса, пока происходит инициализация приложения.
В целом, Microsoft рекомендует, чтобы расширенный экран-заставка совпадал с системным для того, чтобы избежать мерцания и резкого изменения изображения. (Смотрите материал "Руководство и контрольный список для экранов-заставок" (http://msdn.microsoft.com/library/windows/apps/hh465338.aspx)). В этот момент многие приложения просто добавляют индикатор прогресса и сообщение вроде: "Пожалуйста, возьмите напиток, выполните несколько физических упражнений или насладитесь несколькими минутами медитации, пока мы всё загрузим". Соответствие системному экрану-заставке, однако, не означает, что расширенный экран-заставка должен действовать подобным образом. Многие приложения, стартуют с копией системного экрана-заставки и затем используют анимированную графику на одной из его сторон, чтобы освободить место для других элементов. Другие приложения плавно скрывают существующие изображения и запускают видео.
Создание плавного перехода - это цель объекта args.detail.splashScreen, включенного в событие activated. Этот объект (смотрите: "Windows.ApplicationModel.Activation.SplashScreen" (http://msdn.microsoft.com/library/windows/apps/windows.applicationmodel.activation.splashscreen.aspx)) содержит свойство imageLocation (типа Windows.Foundation.Rect), которое содержит данные о расположении и размере изображения для экрана-заставки. Так как ваше приложение может быть запущено на устройствах с экранами различных размеров, это подсказывает вам где расположить то же самое изображение на вашей странице, где начать анимацию, и/или где поместить объекты вроде сообщений и индикаторов прогресса, связанных с данным изображением.
Объект splashScreen, кроме того, предоставляет событие ondismissed, в итоге вы можете произвести специфические действия, когда системный экран-заставка исчезает и появляется первая страница вашего приложения. Обычно это удобно для того, чтобы начать анимацию на странице, начать проигрывание видео и так далее.
Для того, чтобы увидеть пример расширенного экрана-заставки, обратитесь к примеру "Экран-заставка" (http://code.msdn.microsoft.com/windowsapps/Splash-screen-sample-89c1dc78). Еще одна деталь, о которой важно упомянуть, заключается в том, что так как экран-заставка - это обычная страница в вашем приложении, она может быть помещена в различные режимы просмотра - в такие, как режим прикрепленного просмотра. В итоге, как и со всеми остальными страницами вашего приложения, убедитесь в том, что расширенный экран-заставка поддерживает эти состояния!
Как упоминалось ранее, как только вы вернетесь из события activated, Windows считает, что вы сделали всё, что нужно, при старте приложения. По умолчанию, таким образом, Windows удалит свой экран-заставку и сделает домашнюю страницу приложения видимой. Но что, если вам нужно завершить одну или более асинхронных операций до того, как домашняя страница будет действительно готова, например, завершить WinJS.UI.processAll?
Это, снова, то, для чего существует метод args.setPromise внутри события activated. Если вы установили для асинхронной операции отложенный результат, использовав setPromise, Windows будет ждать до тех пор, пока отложенный результат не будет получен, и только тогда скроет экран-заставку. Шаблоны используют этот метод для того, чтобы экран-заставка отображался до тех пор, пока не будет завершен WinJS.UI.processAll.
Так как setPromise просто ожидает окончание выполнения отдельной отложенной операции, как обработать несколько асинхронных операций? Вы можете сделать это парой способов. Во-первых, если вам нужно управлять последовательностью подобных операций, вы можете связать их в цепочку, так, как мы уже это умеем - просто убедитесь в том, что в конце цепочки promise-объектов находится тот, который служит аргументом для setPromise - не вызывайте его метод done (вместо этого, если нужно, используйте then)! Если последовательность исполнения неважна, но вам нужно, чтобы все операции завершились, вы можете скомбинировать эти отложенные результаты, используя команду WinJS.Promise.join, передавая результат в setPromise. Если вам нужно, чтобы завершилась лишь одна операция, вы, вместо вышеупомянутой, можете использовать WinJS.Promise.any. Команды join и any будут разъяснены в последнем разделе этой лекции.
Другой подход заключается в том, чтобы зарегистрировать более чем один обработчик с WinJS.Application.onactivated; каждый обработчик получит собственные аргументы события и собственную функцию setPromise, и WinJS объединит эти возвращенные promise-результаты с помощью WinJS.Promise.join.
Метод setPromise из WinJS, на самом деле, реализован с использованием более общего механизма отложенных операций из WinRT. Объект args, передаваемый в Windows.UI.WebUI.WebUIApplication.onactivated (событие WinRT), содержит небольшой метод, который называется getDeferral (технически - Windows.UI.WebUI.ActivatedOperation.getDeferral (http://msdn.microsoft.com/library/windows/apps/windows.ui.webui.activateddeferral.aspx)). Эта функция возвращает отложенный объект, который содержит метод complete, и Windows не скроет системный экран-заставку до тех пор, пока вы не вызовете этот метод (хотя, это не меняет тот факт, что пользователи нетерпеливы, и ваше приложение всё еще ограничено 15-секундным лимитом). Код может быть похож на этот:
//В обработчике события activated
var activatedDeferral = Windows.UI.WebUI.ActivatedOperation.getDeferral();
someOperationAsync().done(function () {
//После завершения инициализации activatedDeferral.complete();
}
Конечно, setPromise, в конечном счете, делает то же самое, и если вы прямо добавите обработчик события WinRT activated, вы сможете реализовать ту же задержку активации своими силами.
Для приложений и для тех, кто их публикует, идеальным миром был бы мир, в котором пользователи запускают приложения и остаются в них навсегда (и, без сомнения, совершают множество покупок в приложении!). Но в жестокой реальности это невозможно. Независимо от того, как вам хотелось бы, чтобы это было иначе, ваше приложение - не единственное, которое захочется запустить пользователю. В конце концов, зачем были бы нужны функции вроде общего доступа или закрепления контента, если бы несколько приложений не могли исполняться вместе? К лучшему, или к худшему, пользователи будут переключаться между приложениями, менять состояния просмотра, и, возможно, закрывать ваше приложения. Но то, что вы можете сделать - это направить силы в "лучшую" сторону уравнения, убедившись в том, что ваше приложение хорошо ведет себя в любом из этих обстоятельств.
Первое соображение касается фокуса (focus), который применим к элементам управления в вашем приложении и к приложению в целом. Здесь вы можете просто использовать стандартные события HTML blur и focus. Например, в игре в стиле "экшн", или в чём-то подобном, с таймером, постановка на паузу обычно происходит по событию blur, а запуск - по событию focus.
Похожее, но иное условие касается видимости (visibility). Приложение может быть видимым, но не иметь фокуса, как тогда, когда оно работает в прикрепленном режиме. В подобных случаях приложение может продолжать действия, наподобие анимации или обновления ленты новостей, но оно приостановит подобную активность, когда потеряет видимость (то есть, когда оно окажется в фоновом режиме). Для этого используйте событие visibilityChange (http://msdn.microsoft.com/library/windows/apps/hh441213.aspx) из DOM API и затем проверьте свойство visibilityState (http://msdn.microsoft.com/library/windows/apps/hh453385.aspx) объекта window или document, так же, как и свойство document.hidden. (Событие применимо и к видимости отдельных элементов). Изменение видимости, кроме того, отличное время для того, чтобы сохранить пользовательские данные, наподобие документов или состояния прохождения игры.
Приложение может узнать об изменении состояния просмотра (view state change) несколькими способами. Как показано в примере "Here My Am!", приложение обычно использует медиа-запросы (объявленные в CSS или посредством прослушивателей медиа-запросов в коде) для того, чтобы перенастроить макет и видимость элементов, что, на самом деле, единственное, на что должно влиять изменение состояния просмотра. (Опять же, изменение состояния просмотра никогда не изменяет режим работы приложения, только макет и видимость объектов). В любое время приложение может получить сведения о текущем режиме просмотра посредством Windows.UI.ViewManagement.ApplicationView.value. Эта команда возвращает одно из значений типа Windows.UI.ViewManagement.ApplicationViewState, а именно: snapped, filled, fullScreenLandscape и fullScreenPortrait; подробности вы можете найти в лекции 6.
Когда приложение завершает работу (пользователь провёл сверху вниз по экрану или нажал Alt+F4), важно отметить, что сначала приложение попадает во внеэкранный (скрытый) режим, приостанавливается, а потом закрывается, в итоге обычные DOM-события, наподобие unload, не применимы. Пользователь, кроме того, может остановить процесс вашего приложения через Диспетчер задач, однако, при таком стечении обстоятельств, никакие события в коде приложения не генерируются. Помните так же о том, что приложению не следует самому себя закрывать, как говорилось ранее, но оно может воспользоваться командой MSApp.terminateApp для того, чтобы закрыться в ответ на происшествия, последствия которых невозможно исправить.
Помимо фокуса, видимости и состояний просмотра есть три других критических момента в жизненном цикле приложения:
Windows.UI.WebUI.WebUIApplication.onsuspending, которое так же видимо через WinJS.Application.oncheckpoint. Приложение должно завершить обработку этого события за пять секунд, или Windows решит, что приложение зависло и завершит его (время!). В течение этого времени, приложения сохраняют переходящие во времени состояния сеанса работы и освобождают любые захваченные ранее монопольные (exclusive) ресурсы, такие, как файловые потоки или доступ к устройствам (смотрите материал "Приостановка работы приложения" (http://msdn.microsoft.com/library/windows/apps/hh465138.aspx)).
Windows.UI.WebUI.WebUIApplication.onresuming. (Оно не отображается посредством WinJS.Application, так как оно обычно не используется и у WinJS нет значения для его добавления). Мы скоро поговорим об этом подробнее в разделе "Данные от сервисов и WinJS.xhr", так как нужда в этом событии часто возникает при использовании сервисов. В дополнение к этому, если вы отслеживаете данные от сенсоров любого рода (вроде компаса, GPS-приёмника или сенсора ориентации в пространстве), возобновление приложения - это подходящее время для того, чтобы обновить эти данные. Кроме того, здесь вы можете проверить статус лицензии приложения и покупок внутри приложения, если вы используете пробную версию приложения или модель с ограничением по времени (смотрите лекцию 6 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой"). Кроме того, это время, когда вы можете захотеть обновить макет приложения (как мы увидим в лекции 6) к иному состоянию просмотра, чем то, в котором оно было приостановлено, так как возможно возобновление работы приложения напрямую в иное состояние просмотра или в другое разрешение экрана, как тогда, когда устройство было подключено к внешнему монитору. То же самое касается активации/деактивации команд работы с буфером обмена.
previousExecutionState, когда приложение перезапускается.Полезно знать, что вы можете симулировать эти состояния в отладчике Visual Studio, используя выпадающее меню панели инструментов, как показано на Рис. 3.7. Эти команды вызывают соответствующие события и установку значения previousExecutionState для следующего запуска приложения. (Отнеситесь с благодарностью к этим элементам управления. Было время, когда у нас их не было, и было очень неудобно отлаживать эти состояния приложения!).
(рис 3.7) Выпадающее меню в панели инструментов Visual Studio для симуляции приостановки, возобновления и завершения работы приложения
Мы уже вкратце обсудили эти состояния, но давайте посмотрим, как они связаны с запускаемыми событиями и со значением previousExecutionState, которое используется при следующем старте приложения. Это может выглядеть несколько сложным, поэтому переходы между состояниями показаны на Рис. 3.8, а в таблице ниже описано, как определяется значение previousExecutionState.
Значение previousExecutionState | Сценарии |
|---|---|
notRunning | Первый запуск после установки из Магазина Windows. Первый запуск после перезагрузки или выхода. Приложение запущено через примерно 10 секунд после того, как было закрыто пользователем (приблизительное время, необходимое для скрытия, приостановки, и полного завершения работы приложения; если пользователь перезапустит приложение быстрее, Windows предварительно немедленно завершит его работу, не завершая операции, выполняемые при приостановке). Приложение было остановлено с помощью Диспетчера задач во время выполнения или самостоятельно закрылось с помощью MSApp.terminateApp. |
running | Приложение уже исполняется и вызвано способом, отличающимся от запуска с помощью плитки, такого, как чудо-кнопки Поиск или Общий доступ, дополнительные плитки, всплывающее уведомление и реализация других контрактов. Когда приложение запущено и пользователь щёлкает по плитке, Windows просто переключается на уже работающее приложение без вызова событий активации (хотя, и focus, и visibilitychange вызываются). |
suspended | Приложение приостановлено и затем вызвано способом, отличающимся от запуска с помощью плитки (как показано выше, для running). В дополнение к событиям focus/visibility, приложение принимает событие resuming. |
terminated | Ранее приложение было приостановлено, после чего завершено Windows по причине нехватки ресурсов. Обратите внимание на то, что это не применимо к MSApp.terminateApp,так как приложение должно выполняться для того, чтобы вызвать данную функцию. |
closedByUser | Приложение было закрыто с помощью не прерванного жеста закрытия (проведение сверху вниз или Alt+F4). "Прерванное" закрытие происходит, когда пользователь возвращается к приложению в течение 10 секунд, в таком случае предыдущее состояние будет установлено в notRunning. |
(рис 3.8) Обработка событий жизненного цикла и значения previousExecutionState
Активировано, загружено и так далее (activated, load, etc.)
Выполняется (в памяти) (running (in memory))
Приостановка/контрольная точка (suspending/checkpoint)
Приостановлено (в памяти) (suspended (in memory))
Возобновление (resuming)
Нет событий, предыдущее состояние равно значению завершено только если Windows закрыла приложение ((no event) previous state == terminated only if Windows closed the app)
Не исполняется, закрыто пользователем или завершено (notRunning, closedByUser, or terminated)
Основной вопрос для приложения, конечно, не в том, чтобы определить значение previousExecutionState, а в том, что, на самом деле, делать с этим значением при активации. К счастью, эта история немного проще и кое-что мы видим в коде шаблона:
launch и предыдущее состояние - это notRunning или closedByUser, приложению следует запуститься с установками интерфейса по умолчанию и применить любые постоянные настройки и сохраненные состояния. В случае с closedByUser возможны сценарии, когда приложению следует выполнить дополнительные действия (такие, как обновление кэшированных данных) после того, как пользователь явным образом завершит работу приложения и оставит его на какое-то время закрытым.
launch и предыдущее состояние - terminated, приложению следует запуститься в том же самом состоянии, в котором оно было перед последней приостановкой.
launch и при других видах активации, которые включают дополнительные аргументы или параметры (как в случае с дополнительными плитками, всплывающими уведомлениями, контрактами), ему следует инициализироваться для обслуживания целей такого запуска, используя дополнительные параметры. Приложение может быть уже запущено, в итоге ему не обязательно инициализировать своё состояние по умолчанию.Именно по причине наличия второго из вышеперечисленных требований, приложения предоставляют структуру кода для этого случая вместе с обработчиком checkpoint. Мы рассмотрим в подробностях сохранение и перезагрузку состояний в лекции 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript". Основная идея заключается в том, что приложению следует, когда оно приостанавливается, если не раньше, сохранит любые передаваемые состояния сеанса работы, которые ему нужны для самовосстановления после остановки. Эти состояния включают в себя непереданные данные форм, позицию прокрутки контента, стек навигации по приложению и другие переменные. Это так потому, что хотя Windows может приостановить приложение и выгрузить его из памяти, пользователь всё еще воспринимает приложение работающим. Таким образом, когда пользователи активируют приложение снова для обычного использования (это будет скорее вид активации launch, чем активация посредством контракта), они ожидают, что приложение будет в том же состоянии, в котором было до этого. В течение времени, когда приложение приостанавливается, таким образом, ему нужно сохранить любые состояния, которые могут сделать вышеупомянутое возможным. Приложение затем восстанавливает это состояние, в том случае, если previousExecutionState равно terminated.
Для того, чтобы узнать подробности о том, где это важно при проектировании приложений, обратитесь к материалу "Руководство по приостановке и возобновлению работы приложений" (http://msdn.microsoft.com/library/windows/apps/hh465088.aspx). Ясно то, что если пользователь непосредственно закроет приложение с помощью Alt+F4 или жеста закрытия, будут вызваны события suspending и checkpoint, в итоге приложение сохранит состояния сеанса работы Однако, приложение автоматически завершает работу после приостановки и перезагрузка состояния сеанса работы не будет запрошена при повторном запуске, так как значение previousExecutionState будет notRunning или closedByUser.
Лучше всего, на самом деле, сохранять состояние сеанса работы при его изменении в течение времени выполнения приложения, таким образом, минимизируя объём работы, необходимый для обработки события suspending (где у вас есть всего пять секунд). Помните, что состояние сеанса работы (сессии) не включает данные, которые постоянно присутствуют от сессии к сессии, такие, как файлы пользователя, информация о наивысших достижениях в играх и параметры приложения, так как приложение всегда перезагружает или применяет подобные постоянные данные при каждом способе активации. Единственное, о чём вам стоит беспокоиться - это о поддержании иллюзии того, что приложение всегда запущено.
Вы всегда сохраняете состояние сеанса работы в папку с данными приложения или контейнеры параметров, которые предоставляет API Windows.Storage.ApplicationData (лекции 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript". Сейчас мне хотелось бы лишь указать на некоторые вспомогательные механизмы, которые предоставляет для этих целей WinJS.
Во-первых, это событие WinJS.Application.checkpoint, которое предоставляет единое удобное пространство для сохранения и состояния сеанса работы, и любых других постоянных данных, которые могут у вас быть, если вы еще не сохранили их.
Во-вторых, это объект WinJS.Application.sessionState. При нормальном старте приложения он представляет собой пустой объект, в который вы можете добавить любые свойства по своему желанию, в том числе - другие объекты. Обычный подход заключается в том, чтобы использовать sessionState напрямую, как контейнер для переменных. В событии checkpoint WinJS автоматически сериализует содержимое этого объекта (используя JSON.stringify) в файл, расположенный в папке локальных данных вашего приложения (это значит, что переменные в sessionState должны иметь строковое представление). Обратите внимание на то, что WinJS гарантирует то, что его собственный обработчик события checkpoint всегда вызывается после того, как ваше приложение получит это событие, вы можете быть уверены в том, что WinJS сохранит всё, что вы запишете в sessionState в любое время до того, как сработает команда выхода из вашего обработчика checkpoint.
Затем, когда приложение активируется с предыдущим состоянием - terminated, WinJS автоматически восстанавливает объект sessionState, в итоге, всё что вы в нём разместили снова будет доступным. Если вы использовали этот объект для хранения переменных, вам лишь нужно избегать записи в них значений по умолчанию при перезагрузке состояния сеанса работы.
В-третьих, если вы не хотите использовать объект sessionState, или у вас есть сессия, которая с ним не работает, объект WinJS.Application упрощает запись ваших собственных файлов без необходимости использования асинхронного API WinRT. В особенности, он предоставляет (как показано в документации (http://msdn.microsoft.com/library/windows/apps/br229774.aspx)), объекты local, temp и roaming, каждый из которых имеет методы readText, writeText, exists, и remove. Эти объекты работают с соответствующими им папками данных приложения и предоставляют упрощенное API для операций файлового ввода/вывода, как показано в Сценарии 1, примера "Модель приложения" (http://code.msdn.microsoft.com/windowsapps/ApplicationModel-Sample-4be6575d).
Последнее вспомогательное средство связано с механизмом отложенного исполнения, похожим на тот, который применяется при активации. Отложенные операции важны, так как Windows приостановит приложение, как только оно завершит обработку события suspending. Если вам нужно откладывание для асинхронных операций, аргументы события WinJS.Application.oncheckpoint предоставляют метод setPromise, который связан с более глубокими механизмами отложенного выполнения WinRT. Как и ранее, вы передаете promise-объект для асинхронной операции (или комбинации операций) методу setPromise, который, в ответ, вызывает отложенный метод complete, когда ожидаемые результаты будут получены.
На уровне WinRT, аргументы события suspending содержат экземпляр Windows.UI.WebUI.- WebUIApplication.SuspendingOperation (http://msdn.microsoft.com/library/windows/apps/windows.applicationmodel.suspendingoperation.aspx). Он предоставляет метод getDeferral, который возвращает отложенный объект с методом complete, как при активации.
Ух ты! Звучит заманчиво! Может быть, это хитрый способ обойти ограничение на исполнение приложений для Магазина Windows в фоновом режиме? Будет ли приложение исполняться вечно, если я запрошу отсроченную операцию, никогда не вызывая complete?
Не тут-то было, амиго. Примите мои извинения за то, что подарил вам волшебный миг восторга. С отсроченной операцией или нет, пять секунд - это наибольшее время, на которое вы можете рассчитывать. Тем не менее, вы можете в полной мере использовать преимущества этого времени, возможно, сначала выполнив критически важную асинхронную операцию (вроде сбрасывания кэша), и затем попытавшись произвести менее важные действия (вроде синхронизации с сервером), которые могут значительно улучшить опыт взаимодействия пользователя с приложением. Для подобных целей объект suspendingOperation так же содержит свойство deadline, имеющее тип Date, показывающее время в будущем, когда Windows принудительно приостановит приложение вне зависимости от наличия отсроченных операций. Как только первая операция завершится, вы можете проверить, имеется ли время на то, чтобы начать вторую, и так далее.
Базовая демонстрация использования отсроченных операций при приостановке, кстати, сделана в примере "Активация, возобновление работы, приостановка приложения" (лекции 1 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой". Пример управления состояниями приложения, в дополнение к обновлениям, которые мы внесем в нашу программу "Here My Am!" в следующем разделе, можно найти в Сценарии 3 примера "Модель приложения" ((http://code.msdn.microsoft.com/windowsapps/ApplicationModel-Sample-4be6575d).
Для того, чтобы показать основные приемы обработки состояний сеанса приложения, я внес некоторые изменения в "Here My Am!". Их можно найти в упражнении HereMyAm3c. У нас есть два набора данных, о которых мы беспокоимся: переменные lastCapture (объект StorageFile с изображением) и lastPosition (набор координат). Мы хотим быть уверенными в том, что сохранили их при приостановке приложения, в итоге, мы сможем подходящим образом использовать эти значения, когда приложение будет запущено с предыдущим состоянием terminated.
В случае с lastPosition мы можем просто поместить эти данные в объект sessionState (вставив app.sessionState.) в обработчик завершения getGeoPositionAsync:
gl.getGeopositionAsync().done(function (position) {
app.sessionState.lastPosition = {
latitude: position.coordinate.latitude,
e
longitude: position.coordinate.longitud
};
updatePosition();
}, function (error) {
console.log("Unable to get location.");
});
}
Так как нам нужно установить позицию на карте и отсюда, и из ранее сохраненных координат, я переместил данный участок кода в другую функцию, которая так же позволяет быть уверенным в том, что данные о местоположении есть в sessionState:
function updatePosition() {
if (!app.sessionState.lastPosition) {
return;
}
callFrameScript(document.frames["map"], "pinLocation", [app.sessionState.lastPosition.latitude, app.sessionState.lastPosition.longitude]);
}
Отметим так же, что app.sessionstate инициализируется по умолчанию пустым объектом, {}, поэтому lastPosition будет иметь значение undefined до определения местоположения. Это полезно нам при восстановлении состояния приложения. Вот как могут выглядеть, с учетом этого, условия с previousExecutionState:
if (args.detail.previousExecutionState !==
activation.ApplicationExecutionState.terminated) {
//Нормальный старт: инициализируем lastPosition с помощью API
//определения местоположения
} else {
//WinJS перезагружает здесь объект sessionState. Поэтому попытаемся отобразить на карте
//местоположение из сохраненных данных.
updatePosition();
}
Так как мы храним lastPosition в sessionState, эти данные автоматически сохраняются в WinJS.Application.checkpoint, когда приложение было запущено ранее. Когда мы перезапускаемся из состояния terminated, WinJS автоматически перезагружает sessionState; если ранее мы сохранили здесь данные, они будут загружены и updatePosition просто будет работать.
Вы можете проверить это, запустив приложение с данными изменениями и затем использовав команду Приостановить и завершить работу (Suspend and shutdown) на панели инструментов Visual Studio. Установите точку останова на вызов updatePosition выше и затем перезапустите приложение в отладчике. Вы увидите, что в этот момент sessionState.lastPosition уже инициализировано.
В случае с последним захваченным изображением, нам не нужно сохранять объект типа StorageFile, нам нужно сохранить лишь путь к файлу: мы копируем файл в папку локальных данных приложения (в итоге, файл сохраняется между сеансами работы) и можем просто использовать схему URI ms-appdata:// для того, чтобы сослаться на него. Когда мы захватываем изображение, мы просто сохраняем этот URI в sessionState.imageURL (имя свойства может быть произвольным) в конце цепочки promise-вызовов внутри capturePhoto:
app.sessionState.imageURL = "ms-appdata:///local/HereMyAm/" + newFile.name; that.src = app.sessionState.imageURL
Это значение так же будет перезагружено в соответствующем месте при перезагрузке, в итоге, мы можем просто инициализировать соответствующим образом img src:
if (app.sessionState.imageURL) {
document.getElementById("photo").src = app.sessionState.imageURL;
}
Этот код инициализирует отображаемое изображение из sessionState, но нам, кроме того, нужно инициализировать lastCapture для того, чтобы то же самое изображение было доступно для контракта Общий доступ. Для этого нам нужно, кроме того, сохранить полный путь к файлу, в итоге, мы можем повторно получить объект типа StorageFile посредством Windows.SttorageFile.getFileFromPathAsync (что не работает с URI ms-appdata://). В итоге, в capturePhoto:
app.sessionState.imagePath = newFile.path;
И при загрузке:
if (app.sessionState.imagePath) {
Windows.Storage.StorageFile.getFileFromPathAsync(app.sessionState.imagePath)
.done(function (file) {
lastCapture = file;
if (app.sessionState.imageURL) {
document.getElementById("photo").src = app.sessionState.imageURL;
}
});
Я поместил код для установки img.src внутрь обработчика завершения, так как нам нужно, чтобы изображение появлялось только в том случае, если мы можем получить доступ к StorageFile снова для целей общего доступа. В противном случае эти две функции приложения не будут синхронизированы.
При всём этом, отметим снова, что нам не нужно явно перезагружать эти переменные внутри ветви кода, соответствующей состоянию terminated, так как WinJS перезагружает sessionState автоматически. Если бы мы управляли состоянием приложения напрямую, например, сохраняли бы некоторые переменные среди перемещаемых параметров внутри события checkpoint, мы бы перезагрузили и применили их значения в это время.
ms-appdata:/// и getFileFromPathAsync возможно, так как файл находится в месте, куда мы можем получить программный доступ по умолчанию. Это, кроме того, работает для библиотек, которые мы указали среди возможностей в манифесте. Если, однако, мы получили объект StorageFile после работы со средством выбора файла, мы должны сохранить его в Windows.Storage.AccessCashe для того, чтобы сохранить права доступа между сеансами работы с приложением.Хотя мы видели примеры использования данных из пакета приложения (с помощью URI или Windows.ApplicationModel.Package.current.installedLocation), так же, как и из папок данных приложения, весьма вероятно, что ваше приложение будет включать в себя данные из веб-сервисов, и, возможно, так же отправлять данные в эти сервисы. Наиболее распространённым для этих целей методом является XmlHttpRequest. Вы можете использовать его в исходной (асинхронной) форме, если хотите, или вы можете предохранить себя от множества сложностей, используя функцию WinJS.xhr, которая удобно оборачивает все эти действия в promise-объекты.
Сделать запрос очень просто, как показано в примере SimpleXhr для этой лекции. Здесь мы используем WinJS.xhr для получения RSS-канала с блога разработчиков Windows 8:
WinJS.xhr({ url: "http://blogs.msdn.com/b/windowsappdev/rss.aspx" })
.done(processPosts, processError, showProgress);
То есть, здесь мы предоставляем WinJS.Xhr URI и получаем promise-объект, который доставляет свой результат в наш обработчик завершения (в данном случае это processPosts) и даже вызывает индикатор прогресса, если он предоставлен ему. В предыдущем случае результат содержит свойство responseXML, которое является объектом DomParser. В случае с последним, объект события содержит текущий XML в его свойстве response, который мы можем просто использовать для показа объема загруженных данных:
function showProgress(e) {
var bytes = Math.floor(e.response.length / 1024);
document.getElementById("status").innerText = "Downloaded " + bytes + " KB";
}
В остальном, приложение просто перерабатывает полученный текст в поисках элементов item и отображает поля Windows.Web.Syndication. Вы можете использовать его, если хотите лучше структурировать подобные источники данных. Так же, JavaScript имеет внутренние API для работы с XML, в итоге, вы можете пользоваться тем, что кажется вам более подходящим. В случае вроде этого, API синдикации вместе с Windows.Web.AtomPub и Windows.Data.Xml сильнее нужны приложениям для Windows 8, написанным на других языках, которые не имеют тех же встроенных возможностей, что и JavaScript.
(рис 3.9) Вывод данных в приложении SimpleXhr
Для полной демонстрации XHR и связанных вопросов обратитесь к примеру "XHR, обработка ошибок навигации и URL-схем" (http://code.msdn.microsoft.com/windowsapps/XHR-handling-navigation-50d03a7a) и к руководству: "Создание гибридного веб-приложения" (http://msdn.microsoft.com/library/windows/apps/hh452745.aspx). Я не вхожу в описание подробностей о XHR в данном учебном курсе, так как это, в основном, вопрос получения и обработки данных, что имеет мало общего с платформой Windows 8. Что нас интересует, так это последствия приостановки и возобновления работы приложения.
В частности, приложение не может предсказать, как долго оно будет оставаться в приостановленном состоянии, прежде чем его работа будет возобновлена или прежде чем его работа будет остановлена и оно будет перезапущено.
В первом случае, данные приостановленного приложения находятся в памяти. Очень нужно решить, поэтому, устарели ли данные с момента приостановки приложения и истекли ли тайм-ауты соединений с серверами. Так же вы можете подумать о том, после какого периода времени пользователь не помнит и не забоится о том, что произошло, когда он в последний раз видел приложение? Если это неделя или больше, может быть разумным перезапустить приложение или восстановить его состояние по умолчанию. Далее, если вы приводите приложение в состояние, точно соответствующее тому, в котором оно было, пользователь испытывает уверенность, что он может оставить приложение работать сколько угодно долго и ничего не потерять. Или вы можете пойти на компромисс и дать пользователю возможность настроить поведение приложения. Конечно, вы будете размышлять над собственным сценарием, однако, если сомневаетесь, перезагрузите состояние приложения, которое было оставлено пользователем на какое-то время.
Для того, чтобы проверить, сколько времени прошло, сохраните отметку времени при приостановке приложения (с помощью new Date().getTime()), получите другую отметку времени при обработке события resuming, найдите разницу и сравните её с заданным вами периодом обновления. Приложение Stock (Акции), например, может иметь очень короткий период. В случае с приложением для чтения блога разработчиков Windows 8, с другой стороны, новые записи появляются не чаще раза в день, поэтому здесь, для того, чтобы поддерживать приложение в актуальном состоянии и получать новые записи, подойдёт более длительный период, измеряемый часами.
Это реализовано в SimpleXhr путём размещения вызовов WinJS.hxr в отдельной функции, названной downloadPosts, которая вызывается при запуске приложения. Затем мы регистрируемся для обработки события resuming в WinRT:
Windows.UI.WebUI.WebUIApplication.onresuming = function () {
app.queueEvent({ type: "resuming" });
}
Помните, я говорил о том, что мы могли бы использовать WinJS.Application.queueEvent для того, чтобы вызывать собственные события объекта приложения? Вот отличный пример. WinJS.Application не включает в себя автоматически события resuming, так как ему нечего добавить к этому процессу. Но приведенный ниже код реализует в точности то же самое, позволяя нам регистрировать прослушиватели событий, наряду с другими событиями наподобие checkpoint:
app.oncheckpoint = function (args) {
//Сохраняем в sessionState в том случае, если хотим использовать это с кэшированием
app.sessionState.suspendTime = new Date().getTime();
};
app.addEventListener("resuming", function (args) {
//Обычное сокращение для того, чтобы получить либо значение переменной, либо значение
//по умолчанию
var suspendTime = app.sessionState.suspendTime || 0;
//Определяет, сколько времени, в секундах, прошло
var elapsed = ((new Date().getTime()) - suspendTime) / 1000;
//Обновляет ленту, если > 1 часа (или, для тестирования, используйте меньшее значение)
if (elapsed > 3600) {
downloadPosts();
}
});
Для того, чтобы протестировать этот код, загрузите отладчик Visual Studio и установите точки останова в этих событиях. Затем щёлкните на пункт приостановки (suspend) на панели инструментов (можете вернуться к Рис. 3.7), и вы должны попасть в обработчик события checkpoint. Подождите несколько секунд и щелкните на кнопку возобновления работы приложения (resume), имеющую треугольный зеленый значок, и вы должны оказаться в обработчике события resuming. Затем вы можете пошагово исполнить код и увидеть, что переменная elapsed содержит количество прошедших секунд, и если вы измените её значение (или замените 3600 меньшим значением), вы можете увидеть, как downloadPosts снова вызывается для обновления данных.
А что насчёт запуска после остановки работы приложения? Хорошо, если вы не кэшировали данные ранее, вам, в любом случае, нужно их обновить. Если вы кэшировали какие-то данные, сохранённое состояние сеанса работы приложения (такое, как отметка времени) поможет вам решить, стоит ли использовать кэшированные данные или нужно загрузить их снова.
Полезно будет упомянуть здесь о том, что вы можете использовать механизмы HTML5, наподобие localStorage, IndexedDB и кэша приложения для целей кэширования; подобные данные хранятся в папке локальных данных приложения. И, если говорить о базах данных, вы можете заинтересоваться, что доступно приложениям для Магазина Windows помимо IndexedDB. Одна из возможностей - это SQLite, как описано в материале "Использование SQLite в приложениях для Магазина Windows" (http://timheuer.com/blog/archive/2012/05/20/using-sqlite-in-metro-style-app.aspx) (в блоге Тима Хейера, одного из инженеров Windows 8). Кроме того, вы можете использовать библиотеку OData для JavaScript, которую можно найти по адресу http://www.odata.org/libraries. Это - один из наиболее лёгких способов для взаимодействия с онлайновыми базами данных на SQL Server (или любыми другими, поддерживающими OData), так как он просто использует возможности XmlHttpRequest.
Мы поговорим о сетевом взаимодействии в лекции 3 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой", но есть один важный аспект, о котором вы, разрабатывая приложение, должны знать заранее. Что делает приложение с изменениями состояния сетевого соединения, такими как разъединение, повторное соединение и с изменениями полосы пропускания и стоимости соединения (такими, как связь в роуминге, в другой зоне предоставления услуг)?
API Windows.Networking.Connectivity обеспечивает вас такого рода информацией. Вот три основных способа реагирования на подобные события:
Проще говоря, тестируйте приложение как в состоянии подключения к сети, так и без него для того, чтобы обнаружить небольшие оплошности в коде. В "Here My Am!", например, первая версия скрипта в html/map.html не заботилась о том, чтобы проверить, действительно ли загружен удалённый скрипт карт Bing. Сейчас она проверяет, является ли допустимым пространство имен Microsoft (для Microsoft.Maps.Map) В SimpleXhr, тоже, я добавил обработчик ошибки для операции получения отложенного результата в WinJS.xhr, в итоге я могу, по меньшей мере, отобразить обычное сообщение. Здесь, конечно, гораздо больше работы, но попытайтесь, по меньшей мере, охватить основы, для того, чтобы избежать исключений, которые приведут к аварийному завершению работы приложения.
Не распутывая клубок проблем, которым является XmlHttpRequest, будет полезным посмотреть на пару дополнительных вещей, касающихся WinJS.xhr.
Во-первых, обратите внимание на то, что одиночный аргумент этой функции является объектом, который содержит множество свойств. Свойство url наиболее распространено, конечно, но вы, кроме того, можете установить свойство type (тип) (по умолчанию оно установлено в "GET") и respondeType для транзакций другого рода, информацию об учетных данных пользователя в виде user и password, установить headers (заголовки) (такие, как "If-Modified-Since" с датой для управления кэшированием) и предоставить любые другие дополнительные данные (data), если в этом есть необходимость (такие, как параметры запроса к базе данных для XHR). Кроме того, вы можете поддерживать функцию customRequestInitializer, которая будет вызываться объектом XmlHttpReauest до отправки данных, позволяя вам произвести любые необходимые действия.
Во-вторых, это установка тайм-аута в запросе. Вы можете использовать для этих целей customRequestInitializer, устанавливая свойство XmlHttpRequest.timeout и, возможно, обрабатывая событие ontimeout. С другой стороны, как мы увидим в разделе "Завершение истории promise-объектов" в конце этой лекции, вы можете использовать функцию WinJS.Promise.timeout, которая позволяет вам устанавливать период тайм-аута, после которого promise-вызов (и асинхронная операция, связанная с ним) будет отменен. Отмена производится простым вызовом метода cancel promise-объекта.
Вам может понадобиться включить WinJS.xhr в другой promise-объект, это мы так же рассмотрим в конце данной лекции. Вы можете сделать это для того, чтобы инкапсулировать другие промежуточные результаты с вызовом XHR, когда ваш код просто использует возвращённый отложенных результат обычным образом. В соединении с тайм-аутами, эта так же может быть использовано для реализации механизма множественного повтора.
Далее, если вам нужно скоординировать вместе несколько вызовов XHR, вы можете использовать WinJS.Promise.join, который мы снова увидим позже.
Кроме того, мы увидим, как обрабатывать переданные данные в обработчике прогресса загрузки. Так же, вы можете использовать другие данные в ответах и запросах. Например, аргументы события содержат свойство readyState.
У приложений для Магазина Windows, использующих XHR с localhost: URI (локальная заглушка, петлевой адрес локальной сети) заблокированы при проектировании системы. При разработке, однако, это очень полезно для отладки сервисов без их развёртывания. Вы можете включить локальную заглушку в Visual Studio, открыв диалоговое окно свойств проекта (контекстное меню проекта > Свойства), выбрав группу Отладка (Debugging) в левой части окна и установив параметр Разрешить петлевой адрес в локальной сети (Allow Local Network Loopback) в значение Да. Мы рассмотрим соответствующий пример в лекции 2 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой", где это окажется очень полезным для отладки сервисов, которые выдают данные для обновления плиток и другие оповещения.
Наконец, полезно знать, что по соображениям безопасности куки-данные (cookies) автоматически вырезаются из XHR-данных, поступающих в локальный контекст. Один из способов это обойти - выполнять XHR-вызовы из iframe, находящегося в веб-контексте (в котором вы можете использовать WinJS.XHR), затем извлекать куки-данные, которые вам нужны и передавать их в локальный контекст, используя postMessage. С другой стороны, вы можете решить проблему на стороне сервиса, такую, как реализацию API, предоставляющего необходимые вам данные, которые вы извлекали из куки, напрямую.
Другие подробности о данной функции вы можете найти в документации по WinJS.xhr (http://msdn.microsoft.com/library/windows/apps/br229787.aspx), и по ссылкам на дополнительные материалы, которые она содержит.
Сейчас мы подошли к той стороне приложений для Магазина Windows, которая весьма сильно отличает их от обычных веб-приложений. В веб-приложениях, навигация между страницами реализуется посредством использования гиперссылок <a href> или установки параметра document.location в JavaScript. Всё это замечательно. Часто здесь либо нет данных для передачи между страницами, либо их немного. И даже когда есть что передавать между страницами, существует хорошо налаженный механизм HTML5для этого, такой, как sessionStorage и localStorage (который хорошо работает и с приложениями для Магазина).
Однако, подобные способы навигации приводят к некоторым проблемам в случае с приложениями для Магазина Windows. Первая, перемещение на совершенно новую страницу подразумевает совершенно новый контекст скрипта - все JavaScript-переменные с предыдущей страницы будут потеряны. Конечно, вы можете передать данные между этими страницами, но управление подобным по всему приложению серьезно ухудшает производительность и может очень скоро превратиться в вашу самую нелюбимое дело в работе программиста. Лучше и проще, другими словами, чтобы клиентское приложение поддерживало в памяти постоянное состояние, не зависящее от перемещений по страницам.
Кроме того, природа подсистемы рендеринга HTML/CSS такова, что при переходах между страницами с помощью гиперссылки появляется пустой экран. Пользователи веб-приложений привыкли ждать какое-то время, пока браузер загрузит новую страницу (я много что успеваю сделать за дополнительные 15 секунд!), но это не соответствует тому, что ожидают пользователи от быстрых и динамичных приложений для Магазина Windows. Более того, подобные переходы не позволяют анимацию различных элементов на экране, что помогает создать ощущение неразрывной связи страниц, если это соответствует дизайну приложения.
В итоге, хотя вы можете использовать прямые ссылки, приложения для Магазина обычно реализуют понятие "страница" динамически заменяя секции в DOM внутри контекста одной страницы наподобие default.html, что родственно тому, как работают приложения, основанные на AJAX. Подобный подход позволяет всегда сохранять контекст скрипта и обеспечить переходы между элементами и группами элементов так, как вам нужно. В некоторых случаях имеет значение простой показ и скрытие страниц, в итоге вы можете быстро переходить вперед и назад. Посмотрим на стратегии и инструменты, с помощью которых можно достичь эти цели.
Сама по себе 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.process[All], использовать столько этих элементов на хост-странице, сколько нужно и даже создавать из них вложенных структуры. Смотрите Сценарий 1 в примере "Элементы управления HTML-страницей" (http://code.msdn.microsoft.com/windowsapps/Page-Controls-sample-568b10b4).Эти API предоставляют только средства для загрузки и выгрузки отдельных страниц - они берут HTML из других файлов (вместе с соответствующим CSS и JS-кодом) и присоединяют эти данные к элементу в DOM. Вот и всё. Для того, чтобы по-настоящему реализовать структуру навигации по страницам, нам нужно еще два механизма: что-то, что управляло бы стеком навигации и что-то, что перехватывало бы события навигации в механизме загрузки страниц WinJS.UI.Pages.
Первую задачу можно выполнить с помощью beforenavigation можно использовать для отмены навигации, если нужно. Либо вызов args.preventDefault (args будет объектом события), возвратит true, либо вызов args.setPromise где promise-объект вернет true.
Для выполнения второй задачи вы можете создать собственные связи между WinJS.Navigation и WinJS.UI.Pages. На самом деле, на ранних стадиях разработки приложений для Windows 8, вплоть до первых публичных предварительных релизов для разработчиков, программисты писали один и тот же код снова и снова. В ответ на это, команда разработки в Microsoft, ответственная за шаблоны, великодушно решали создать стандартную реализацию этого механизма, что добавило несколько команд для работы с клавиатурой (для перемещения вперед и назад) и некоторые удобные оболочки для использования в шаблонах. Ура!
Этот инструмент называется WinJS.Navigation.Navigate. Этот инструмент может быть весьма полезен, особенно, если вы импортируете код из веб-приложений.PageControlNavigator в собственных программах, посмотрим, как это всё работает в контексте шаблона Приложение навигации (Navigation App).
Если вспомнить шаблон Пустое приложение, шаблон Приложение навигации демонстрирует основы использования элементов управления страницами. (Более сложные шаблоны строят систему навигации, идущую дальше). Если вы создаёте новый проект с использованием данного шаблона в 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>) и затем вызвав этот конструктор с родительским элементом и объектом, содержащим его параметры.
Хотя, базовая структура для метода ready предоставляется шаблоном, WinJS.UI.Pages и PageControlNavigator будут использовать следующие методы, если они доступны:
| Метод 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 (как мы увидим в лекции 5, "Коллекции и элементы управления для вывода коллекций").
Что касается самого 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.
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 (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> в любом месте приложения.
В приложения для Магазина 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 (смотрите лекцию 5) с набором элементов по умолчанию.
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.
PageControlNavigator.На всякий случай, шаблон Приложение с разделением (Split App) работает похожим образом, когда каждый элемент списка на pages/items привязан, при активизации, к перемещению на pages/split .
В любом случае, шаблон Приложение таблицы, кроме того, служит примером того, что мы называем стилем навигации Хаб-Раздел-Сведения (Hub-Section-Details). Здесь домашняя страница приложения представляет собой центральную страницу, где пользователь может в полной мере изучить приложение. Щелчок по заголовку группы осуществляет навигацию к странице раздела, второму уровню организации, где отображены лишь элементы этой группы. Щелчок по элементу (на центральной странице или на странице разделов) переносит нас на страницу сведений для данного элемента. Вы можете, конечно, реализовать данный стиль навигации любым желаемым способом. Шаблон Приложение таблицы использует элементы управления страниц, WinJS.Navigation и PageControlNavigator. (Контекстное масштабирование (семантический зум, semantic zoom), как мы увидим в лекции 5, так же поддерживается в качестве инструмента навигации для переключения между центральными страницами и страницами разделов.)
Альтернативная модель навигации - это плоский (flat) стиль, который просто имеет один уровень иерархии. Здесь навигация происходит на любую из страниц в любое время посредством панели навигации (navigation bar) (она появляется вместе с панелью приложения, смотрите лекцию 1 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript"). При использовании элементов управления страницы и PageControlNavigator, элементы управления навигацией могут просто запускать WinJS.Navigation.Navigate для этой цели. Обратите внимание, что при таком стиле навигации кнопка Назад обычно не используется.
Информацию об этих стилях, вместе со многими другими аспектами пользовательского интерфейса, касающимися навигации, можно обнаружить в материале "Проектирование навигации для приложений Магазина Windows" (http://msdn.microsoft.com/library/windows/apps/hh761500.aspx). Это - важный материал для дизайнеров.
Некоторые приложения могут требовать либо первоначальную идентификацию пользователя, либо принятие лицензионного соглашения для продолжения работы. Таким образом, целесообразно показывать подобные страницы сразу после экрана-заставки. В подобных случаях, если пользователь не примет условия лицензии или не введет идентификационные данные, приложению следует отобразить сообщение, говорящее о необходимости этих действий, но всегда следует позволять пользователю закрыть приложение, если он этого хочет. Не закрывайте приложение автоматически.
Обычно подобные страницы появляются лишь при первом запуске приложения. Если пользователь ввёл подходящие идентификационные данные, их можно сохранить для последующего использования, воспользовавшись 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 предоставляется шаблоном как часть вашего приложения, вы можете модифицировать его как вам будет угодно для того, чтобы реализовать подобную возможность более последовательным способом.
Обычной практикой в HTML и JavaScript, особенно для веб-сайтов, является то, что мы уже делали в данном учебном курсе. Мы вызывали addEventListener для задания обработчика события или просто присваивали обработчик события свойству on<event> какого-либо объекта. Часто эти обработчики просто объявляют в качестве встроенных анонимных функций:
var myNumber = 1;
element.addEventListener(<event>, function (e) { myNumber++; } );
Благодаря особым правилам обзора данных, действующим в JavaScript, область видимости подобной анонимной функции совпадает с окружающим её кодом, что позволяет коду внутри этой функции ссылаться на локальные переменные наподобие myNumber в вышеприведенном коде.
Для того, чтобы убедиться, что подобные переменные доступны данной анонимной функции, когда она позже будет вызвана в качестве обработчика события, JavaScript-движок создаёт замкнутое выражение (closure), структуру данных, описывающую локальные переменные, доступные данной функции. Обычно замкнутое выражение представляет собой небольшой участок памяти, но в зависимости от кода внутри обработчика события, замкнутое выражение может включить в себя всё глобальное пространство имён, что потребует весьма значительного выделения памяти!
Каждое такое замкнутое выражение увеличивает объем памяти или рабочий набор (working set) приложения, поэтому хорошо поддерживать минимальный объем подобных данных. Например, объявление отдельных именованных функций, которые имеют собственную область видимости - уменьшит размер необходимых замкнутых выражений.
Более важно, нежели уменьшение размера замкнутых выражений, это уверенность в том, что прослушиватели событий сами по себе и, связанные с ними замкнутые выражения соответствующим образом освобождают выделенную им память.
Обычно это не то, о чём вам нужно думать. Когда объекты, такие, как HTML-элементы, уничтожаются, как в случае, когда элементы страницы выгружаются из DOM, связанные с ними прослушиватели автоматически удаляются и ресурсы, занятые замкнутыми выражениями так же освобождаются. Однако, в приложениях для Магазина Windows, написанных на HTML и JavaScript есть и другие источники событий, для которых приложение может добавить прослушиватели в то время, как данные объекты никогда не уничтожаются. Это могут быть объекты из WinJS, объекты из WinRT, window и document. Данные Прослушиватели должны быть соответствующим образом очищены, в противном случае приложение будет иметь утечки памяти (память, которая выделена, но никогда не освобождается при операции сборки мусора).
Особого внимания требуют события, которые исходят от объектов WinRT. Из-за сущности уровня проекции, который делает WinRT доступным в WinJS, WinRT ограничивается хранением ссылок на обработчики событий JavaScript (известных так же как делегаты (delegates)), пока замкнутые выражения JavaScript хранят ссылки на некоторые объекты WinRT. В результате наличия подобных перекрестных ссылок, эти замкнутые выражения могут никогда не быть очищенными.
Это не проблема, помните, если приложение всегда прослушивает конкретные события. Например, события suspending и resuming - это те события, которые приложение обычно прослушивает в течение всего времени жизни приложения, в итоге, любые связанные с ними выделения памяти будут очищены при завершении работы приложения. То же самое справедливо для большинства прослушивателей, которые вы можете добавить для событий объектов window и document, которые постоянно существуют во время жизни приложения.
Утечки памяти, однако, возникают, когда приложение прослушивает события объектов WinRT лишь временно и пренебрегает непосредственным вызовом removeEventListener, или когда приложение вызывает addEventListener для одного и того же события несколько раз (в таком случае вы получите несколько замкнутых выражений). В случае с элементом управления страницы, как обсуждалось в данной лекции, обычная практика заключается в вызове addEventListener в методе ready страницы для некоторого WinRT-объекта. Когда вы делаете это, убедитесь в том, что есть соответствующий данному вызову вызов removeEventListener в методе страницы unload, который освободит ресурсы, занятые замкнутым выражением. Я сделал это в примере HereMyAm3d с datarequested, для ясности.
В этом учебном курсе события WinRT, на которые вам следует обратить внимание, выделены специальным цветом, как datarequested (за исключением текста, который является гиперссылкой). Это напоминание для проверки того, нужен ли явный вызов removeEventListener. Опять же, если вы всегда прослушиваете событие, удалять прослушиватель не нужно, но если вы добавили его, когда загружали элемент управления страницы, вам практически гарантированно понадобится выполнить этот дополнительный вызов. Особенно обратите внимание на то, что примеры не обязательно уделяют внимание этой особенности, поэтому не повторяйте примеры, не задумываясь об этом. И, наконец, обратите внимание на то, что события от объектов WinJS не нуждаются в подобном внимании, так как библиотека уже обрабатывает удаление прослушивателей событий.
В следующих лекциях я напомню вам о том, что мы только что обсудили на нашей первой значимой встрече с событиями WinRT. В любом случае, будьте внимательны к цветовому выделению текста.
Ух ты! Мы прошли большой путь в этой лекции, через множество тонких деталей того, как строятся приложения, и как они выполняются (или не выполняются!). Вы могли заметить, что наша постоянная тема касалась promise-объектов - они появлялись почти в каждом разделе. На самом деле, WinJS и WinRT изобилуют асинхронными операциями и выполняются они с помощью получения отложенных результатов, promise-объектов.
Я хочу завершить эту лекцию, однако, завершив историю promise-объектов, так как они обеспечивают гораздо более обширную функциональность, чем мы использовали. Демонстрацию того, что мы рассмотрим здесь, можно найти в примере "WinJS Promise" (http://code.msdn.microsoft.com/windowsapps/Promise-e1571015), а если вам нужна самая полная история асинхронных операций, прочтите материал "Использование асинхронности в среде выполнения Windows для создания быстрых и гибких приложений" (http://blogs.msdn.com/b/windowsappdev_ru/archive/2012/03/28/windows.aspx) в блоге разработчиков Windows 8.
Давайте сделаем шаг назад и уточним, что, на самом деле означает "promise". Говоря просто, это объект, который возвращает значение, простое или сложное, когда-то в будущем. Способ, благодаря которому вы узнаёте, когда доступно это значение - это вызов методов promise-объекта then или done с обработчиком завершения (completed handler). Этот обработчик будет вызван со значением, предоставленным promise-объектом (с обещанным значением) (с результатом (result)), когда это значение будет готово - это происходит немедленно, если значение уже доступно. Более того, вы можете вызывать then/done множество раз для одного и того же promise-объекта и вы просто получаете тот же самый результат в каждом обработчике завершения. Это не приведет к системному сбою или к чему-то подобному.
Если происходит ошибка, второй параметр у then/done это - обработчик ошибки (error handler), который будет вызван вместо обработчика завершения. В противном случае исключение будет либо поглощено в then, либо передано в цикл событий приложений done, как мы уже говорили об этом.
Третий параметр у IAsync[Action | Operation]WithProgress, значит она может использовать функцию прогресса, переданную promise-объекту. Если же в её описании присутствует лишь IAsync[Action | Operation], то прогресс не поддерживается. Больше об этом вы можете найти в лекции 16.WinJS.xhr периодически вызывают функцию прогресса для отображения изменения "состояния готовности" при загрузке данных, запрошенных с сервера.
Сейчас нет требований к тому, чтобы promise-объект инкапсулировал асинхронные операции или синхронные. Вы можете, фактически, "обернуть" в этот объект любое значение, воспользовавшись статическим методом WinJS.Promise.wrap. Подобный контейнер для уже существующего значения (будущее - это сейчас!) будет ждать своего часа и вызовет обработчик завершения с данным значением, как только вы вызовете then или done. Это позволяет вам использовать любое значение там, где ожидается promise-объект, или возвращать что-то вроде ошибок из функций, которые в противном случае возвращают promise-объекты для асинхронных операций. WinJS.Promise.wraperror существует именно для этой специфической цели.
WinJS.Promise (http://msdn.microsoft.com/library/windows/apps/br211867.aspx), кроме того, поддерживает набор полезных статических методов, вызываемых напрямую из WinJS.Promise вместо того, чтобы пользоваться каким-то конкретным экземпляром promise-объекта.
is определяет, является ли произвольное значение promise-объектом, она обычно проверяет, обладает ли объект функцией с именем "then"; она не проверяет объекты на "done"
as работает схожим с wrap образом, за исключением того, что если вы предоставите ей promise-объект, она просто вернет этот объект. Если вы предоставите promise-объект функции wrap, она инкапсулирует его в еще один promise-объект.
any похожа на join, но группирует результаты с использованием логического ИЛИ (OR) (снова используя then)
cancel останавливает асинхронную операцию. Если предоставлен обработчик ошибок, он вызывается со значением Error("canceled").
theneach применяет обработчики завершения, ошибки, прогресса к группе promise-объектов (используя then), возвращая результаты в виде другой группы значений внутри promise-объекта.
timeout имеет двойственную природу. Если вы просто зададите значение тайм-аута, он вернет promise-объект, инкапсулирующий вызов setTimeout. Если вы, кроме того, предоставите promise-объект в качестве второго параметра, он отменит выполнение операции этого promise-объекта, если она не будет получена в заданное время. В последнем случае это обычная оболочка для стандартных шаблонов добавления тайм-аута для некоторых других асинхронных операций, у которых тайм-аута нет.
В дополнение к использованию функций наподобие as и wrap, вы так же можете создать promise-объект из заготовки, используя команду new WinJS.Promise(<init> [, <oncancel>). Здесь <init> - это функция, которая принимает диспетчеры (dispatcher) завершения, ошибки и прогресса, а oncancel - это необязательная функция, которая вызывается в ответ на WinJS.Promise.Cancel. Диспетчеры - это то, что вы вызываете, чтобы вызвать любые обработчики завершения, ошибки или прогресса, заданные методам promise-объекта then или done, в то время как oncancel - это ваша собственная функция, которую promise-объект вызовет, если он подвергнется операции отмены. Создание новых promise-объектов подобным способом обычно используют, когда создают собственные асинхронные функции. Например, мы увидим, как это используется для упаковывания асинхронного рабочего веб-процесса (web worker) в лекции 5 курса "Программная логика приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript и их взаимодействие с системой".
Кроме того, если возможностей WinJS.Promise.as недостаточно, создание promise-объектов подобным образом полезно для упаковывания других операций (не только значений) в структуру promise-объекта, в итоге, такая операция может быть объединена в цепочку или соединена с другими операциями. Например, если у вас есть библиотека, которая обменивается данными с веб-сервисом посредством обычного асинхронного XmlHttpRequest, вы можете упаковать каждое API этой библиотеки в Promise-объект. Вы можете, кроме того, использовать новый promise-объект для того, чтобы упаковать множество асинхронных операций (или других promise-объектов!) из различных источников в единый promise-объект, в то время, как join или any не дадут вам нужного уровня контроля. Другой пример - инкапсуляция специфических функций обработки завершения, ошибок, прогресса операции в promise-объект, как при реализации механизма множественных вызовов поверх отдельных XHR-операций, для перехвата доступа к стандартному интерфейсу индикатора прогресса, или для добавления фонового ведения журнала, или подсистемы аналитики с вызовами сервиса, в итоге вашему коду никогда не понадобится знать об этих механизмах.
iframe.
iframe.
ms-appdata для адресации мультимедийного содержимого из локальной, перемещаемой, временной папок приложения.
then и done.
WinJS.UI.Application.
WinJS.xhr и как это связано с событием resuming.
WinJS.Navigation и PageControlNavigator из шаблонов Visual Studio / Blender, таких, как шаблон Приложение навигации.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.