Разработка HTML5-приложений Windows Phone с применением Apache Cordova

OSzone.net » Microsoft » Разработка приложений » HTML5 » Разработка HTML5-приложений Windows Phone с применением Apache Cordova
Автор: Колин Эберхардт
Иcточник: MSDN Magazine
Опубликована: 19.11.2012

В этой статье вы познакомитесь с Apache Cordova — инфраструктурой для создания кросс-платформенных мобильных приложений, использующих HTML5 и JavaScript, и увидите, как ее можно использовать для разработки приложений Windows Phone.

Windows Phone и ее «родная» платформа разработки позволяют легко создавать красивые приложения в стиле Metro. А теперь в партнерстве с Nokia устройства под управлением Windows Phone начинают активнее завоевывать рынок.

Недавние данные, опубликованные исследовательской фирмой Gartner Inc., предсказывают многообещающее будущее для Microsoft OS (bit.ly/h5Ic32) со значительной долей на фрагментированном рынке. Если вы разрабатываете приложения для смартфонов, эта фрагментация рынка означает, что вы должны либо выбрать одну целевую ОС, либо писать одно и то же приложение несколько раз под разные платформы смартфонов, используя разные языки (C#, Java и Objective-C).

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

Однако есть и другой путь. Браузеры всех этих смартфонов во многих отношениях обладают более широкими возможностями, чем их эквиваленты на настольных компьютерах, где некоторые люди до сих пор используют архаичные браузеры! Современные смартфоны позволяют создавать выполняемые в браузере приложения с применением комбинации HTML5, JavaScript и CSS. С помощью этих технологий потенциально можно написать единственное приложение на основе браузера, выполняемое на самых разнообразных смартфонах.

Введение в Apache Cordova

Вы можете разработать мобильное приложение на основе HTML5, создав общедоступную веб-страницу с JavaScript- и HTML5-контентом и направляя пользователей на URL хостинга. Однако в этом подходе есть пара проблем. Первая связана с моделью дистрибуции через онлайновые магазины приложений. Вы не сможете отправить в такой магазин URL, по которому размещено ваше веб-приложение, и каким же образом вы получите деньги за него? Вторая проблема заключается в том, как осуществляется доступ к аппаратному обеспечению смартфона. Широко поддерживаемых API браузеров для доступа к телефонным контактам, уведомлениям, камерам, датчикам и прочему нет. Apache Cordova (далее просто Cordova для краткости) — бесплатная инфраструктура с открытым исходным кодом — решает обе эти проблемы.

Cordova начиналась как PhoneGap, которая была разработана Nitobi. В октябре 2011 года Nitobi была приобретена компанией Adobe Systems Inc. вместе с открытым исходным кодом инфраструктуры PhoneGap по лицензии Apache Software Foundation, и название PhoneGap было сменено на Cordova. Переходный период не закончился до сих пор.

Cordova предоставляет среду для хостинга вашего контента HTML5/JavaScript внутри тонкой «родной» оболочки. Для ОС на каждой платформе смартфонов она использует родной элемент управления «браузер» для рендеринга контента вашего приложения, причем ресурсы приложения упаковываются в дистрибутив. В случае Windows Phone ваши HTML5-ресурсы упаковываются в XAP-файл и загружаются в изолированное хранилище, когда запускается ваше Cordova-приложение. В период выполнения элемент управления WebBrowser визуализирует контент и исполняет ваш JavaScript-код.

Cordova также предоставляет набор стандартных API для доступа к функциям, общим для всех смартфонов. К некоторым из этих функций относятся:

Cordova предоставляет среду для хостинга вашего контента HTML5/JavaScript внутри тонкой «родной» оболочки.

Каждая из этих функций предоставляется как Java­Script API, который используется из вашего кода на JavaScript. Cordova берет на себя всю черновую работу, связанную с предоставлением необходимой родной реализации, и тем самым гарантирует, что вы будете иметь дело с одинаковыми JavaScript API независимо от ОС смартфона, на котором выполняется ваш код (рис. 1).

*
Рис. 1. Cordova позволяет выполнять одно и то же HTML5-приложение в различных мобильных операционных системах

HTML5 ApplicationHTML5-приложение
Windows PhoneWindows Phone
CordovaCordova
Cordova JavaScript APICordova JavaScript API
Cordova Native Libraries«Родные» библиотеки, предоставляемые Cordova
Silverlight FrameworkSilverlight Framework
iOSiOS
iOS FrameworkiOS Framework


Основное внимание в этой статье уделяется Cordova с точки зрения разработчика для Windows Phone, где процесс процесс разработки происходит в Visual Studio, а тестируется приложение либо в эмуляторе, либо на реальном устройстве. Хотя Cordova — кросс-платформенная технология, разработка обычно ведется с применением привычного редактора или IDE, поэтому разработчик для iOS будет создавать Cordova-приложение в Xcode, а разработчик для Android наверняка предпочтет Eclipse.

В Cordova также имеется облачный сервис Build (build.phonegap.com), которому можно передавать ваш HTML5/­JavaScript-контент. Через непродолжительное время он возвращает дистрибутивы для большинства поддерживаемых в Cordova платформ. Это означает, что вам не нужны экземпляры различных IDE, специфичных для конкретных платформ (и компьютер Mac), чтобы скомпилировать свое приложение для нескольких платформ. Сервис Build принадлежит Adobe и в настоящее время находится в стадии бета-тестирования; кроме того, им можно пользоваться бесплатно. Для проектов с открытым исходным кодом он и в будущем останется бесплатным.

Получение инструментария

Предполагается, что у вас уже есть Visual Studio, Windows Phone SDK и (необязательно) Zune, установленные для разработок под Windows Phone. Если это не так, вы можете получить этот инструментарий бесплатно, скачав Visual Studio 2010 Express for Windows Phone (bit.ly/dTsCH2).

Вы можете использовать любую из стандартных JavaScript/HTML5-библиотек или инфраструктур в своем приложении Cordova при условии, что они совместимы с браузером смартфона.

Вы можете получить последние версии инструментария для разработчиков под Cordova с сайта PhoneGap (phonegap.com), но последующие выпуски будут распространяться через Apache (incubator.apache.org/cordova). Пакет для скачивания включает шаблоны, библиотеки и скрипты, необходимые для разработки Cordova-приложений на всех поддерживаемых платформах. Вы, конечно же, захотите воспользоваться версией для Windows Phone.

После скачивания инструментария Cordova следуйте инструкциям в Windows Phone Get Started Guide (phonegap.com/start#wp) и установите шаблон Visual Studio. Чтобы создать программу в стиле «Hello World», достаточно открыть новый проект на основе предоставляемого шаблона, как показано на рис. 2.

*
Рис. 2. Cordova for Windows Phone включает шаблон Visual Studio

Если вы компилируете и развертываете проект, созданный по этому шаблону, в эмуляторе, то должны увидеть приветственное сообщение «Hello Cordova» (рис. 3).

*
Рис. 3. Приложение на основе шаблона Cordova, выполняемое в эмуляторе

Анатомия Cordova-приложения Windows Phone

Хотя Cordova-приложение можно разрабатывать, не особо вникая в то, как работает эта инфраструктура, вы должны понимать, какие файлы генерируются шаблоном (рис. 4).

*
Рис. 4. Структура папок шаблона Cordova

Если сосредоточиться только на файлах Cordova на рис. 4 (сверху вниз), то обратите внимание на следующее:

Файл MainPage.xaml содержит экземпляр пользовательского элемента управления CordovaView, включающего элемент управления WebBrowser:

<Grid x:Name="LayoutRoot">
  <my:CordovaView Name="PGView" />
</Grid>

Элемент управления CordovaView берет на себя загрузку ресурсов приложения в локальное хранилище и осуществляет навигацию к файлу www/index.html, тем самым запуская ваше приложение. Конечно, вы можете поместить на эту страницу другие элементы управления Silverlight, отредактировав данную XAML-разметку, но я не советую делать этого. Если вы пишете HTML5-приложение, то наверняка стремитесь к кросс-платформенности. А любые элементы управления, добавляемые вами в файл MainPage.xaml, разумеется, будут специфичными для сборки под Windows Phone.

Разработка приложений Cordova

Вы можете добавить свои файлы HTML, JavaScript и CSS в папку www, и — если они помечены как Build Action of Content — они будут включены в ваш проект и доступны при выполнении приложения через элемент управления «браузер». Вы можете использовать любую из стандартных JavaScript/HTML5-библиотек или инфраструктур в своем приложении Cordova при условии, что они совместимы с браузером смартфона.

Cordova API документированы на веб-сайте Cordova, поэтому я не стану подробно описывать их. Один важный момент состоит в том, что вы должны ждать события deviceready, прежде чем пользоваться любым другим API-методом. Если вы просмотрите файл index.html, сгенерированный из шаблона, то заметите, что перед обновлением UI в нем предусмотрено ожидание до тех пор, пока устройство не будет готово:

<script type="text/javascript">
  document.addEventListener("deviceready",onDeviceReady,false);
  function onDeviceReady()
  {
    document.getElementById("welcomeMsg").innerHTML
      += "Cordova is ready! version=" + window.device.cordova;
    console.log(
      "onDeviceReady. You should see this " +
        "message in Visual Studio's output window.");
  }
</script>

Объект console, используемый в предыдущем коде, позволяет добавлять в приложение отладочный вывод. Сообщения посылаются в консоль Visual Studio посредством Cordova.

Архитектура одно- и многостраничных приложений

При создании приложений Cordova можно применять два совершенно разных шаблона.

Выбор между этими двумя шаблонами имеет определяющее влияние на структуру вашего кода.

В целом, многостраничный шаблон лучше подходит приложениям, которые в основном имеют дело со статическим контентом. При этом подходе вы можете брать HTML/CSS/JavaScript, которые в данный момент используются на вашем веб-сайте, и упаковывать их, используя Cordova, для доставки на устройство Phone в виде приложения. Но этому подходу свойствен ряд недостатков. Прежде всего, когда браузер переходит с одной страницы на следующую, ему приходится повторно загружать и разбирать весь JavaScript-код, сопоставленный с новой страницей. При этом возникает заметная пауза, пока длится жизненный цикл Cordova, в ходе которого создается связь между JavaScript API и эквивалентами на C#. Во-вторых, из-за повторной загрузки вашего JavaScript-кода теряется состояние всего приложения.

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

Демонстрационное приложение, описываемое в этой статье, использует одностраничный шаблон. За примером многостраничного шаблона советую обратиться к проекту DemoGAP на CodePlex (demogap.codeplex.com), который представляет собой простую демонстрацию возможностей Cordova API в рамках приложения Windows Phone.

Демонстрационное приложение

В оставшейся части статьи описывается Cordova Twitter Search — простое приложение Windows Phone, которое позволяет вести поиск в Twitter по одному или более ключевых слов, как показано на рис. 5.

*
Рис. 5. Демонстрационное приложение Cordova Twitter Search

Как и Cordova, это приложение использует следующие инфраструктуры.

Я не собираюсь детально обсуждать Knockout в этой статье. Если вы хотите изучить эту инфраструктуру, советую прочитать недавнюю статью Джона Папы (John Papa) «Getting Started with Knockout» (msdn.microsoft.com/magazine/hh781029). А если вы не знакомы с шаблоном MVVM (где вы прятались все это время?), рекомендую превосходную статью Джоша Смита (Josh Smith) «WPF Apps with the Model-View-ViewModel Design Pattern» (msdn.microsoft.com/magazine/dd419663).

Разработка JavaScript-приложений в Visual Studio

Прежде чем углубляться в детали этого приложения, я хочу сказать несколько слов о разработке JavaScript-приложений вообще. Одна из трудностей, с которыми сталкивается разработчик на JavaScript, — динамическая природа этого языка. В случае JavaScript вы не ограничены жесткой системой типов; вместо этого объекты могут конструироваться динамически. Это создает сложности для разработчиков различных редакторов JavaScript-кода и IDE. В строго типизированных языках, таких как C# и Java, информацию о типах можно использовать для более эффективной навигации по коду, рефакторинга и поддержки IntelliSense. Но в JavaScript отсутствие информации о типах означает, что IDE, как правило, предоставляет куда меньше помощи разработчикам.

К счастью, недавно ситуация улучшилась благодаря тому, что Visual Studio 2010 осуществляет псевдо-выполнение вашего JavaScript-кода, чтобы определить «форму» (shape) каждого объекта, и за счет этого обеспечивает для JavaScript поддержку IntelliSense. Чтобы в максимальной мере задействовать поддержку IntelliSense, мы должны предоставить IDE несколько «подсказок» в виде «ссылок», которые сообщают IDE, какие файлы следует включать в процесс псевдо-выполнения. В случае демонстрационного проекта все файлы начинаются с комментария reference, указывающего IDE включить файл intellisense.js. Содержимое этого файла является просто списком ссылок, которые гарантируют, что IDE включит все важные JavaScript-файлы приложения, обеспечив качественную поддержку IntelliSense в работе над этим приложением:

/// IntelliSense охватит все файлы из этого проекта
///
/// <reference path="app.js" />
/// <reference path="viewModel/ApplicationViewModel.js" />
/// <reference path="viewModel/SearchResultsViewModel.js" />
/// <reference path="viewModel/TweetViewModel.js" />
/// <reference path="viewModel/TwitterSearchViewModel.js" />
/// <reference path="lib/jquery-1.6.4.js" />
/// <reference path="lib/cordova-1.5.0.js" />
/// <reference path="lib/knockout-1.2.1.js" />

JavaScript — нестрогий и снисходительный к программисту язык с такими средствами, как автоматическое преобразование типов значений (value coercion) и вставка точек с запятой, которые упрощают его применение в скриптовой среде. Однако те же средства часто создают проблемы, когда приходится управлять большими объемами кода. По этой причине я настоятельно рекомендую использовать JSLint — инструмент, который применяет к JavaScript более жесткий набор стандартов кодирования. Популярное Visual Studio Extension добавляет поддержку JSLint (jslint4vs2010.codeplex.com), сообщая о Lint-ошибках в соответствующей консоли. Я воспользовался JSLint в приложении Twitter Search (и делаю так практически во всех проектах на JavaScript, над которыми мне приходится работать).

Вероятно, вы заметили комментарий globals в начале каждого JavaScript-файла в проекте. JSLint помогает предотвращать утечку переменных в глобальную область видимости из-за случайного пропуска ключевого слова var. Комментарий globals предоставляет JSLint формальное определение того, какие переменные могут считаться глобальными.

Структура MVVM-приложения

Для элемента управления «браузер» на устройстве Phone приложение Twitter Search является одностраничным. С точки зрения пользователя, напротив, оно выглядит многостраничным, как показано на рис. 5. С этой целью конструируется Knockout ViewModel, содержащий стек экземпляров ViewModel, каждый из которых представляет страницу в приложении. Когда пользователь переходит на новую страницу, в этот стек добавляется соответствующий ViewModel, а когда пользователь возвращается, из стека выталкивается самый верхний ViewModel (рис. 6).

Я настоятельно рекомендую использовать JSLint — инструмент, который применяет к JavaScript более жесткий набор стандартов кодирования.

Рис. 6. Knockout ApplicationViewModel

/// <reference path="..//intellisense.js" />
/*globals ko*/
function ApplicationViewModel() {
  /// <summary>
  /// ViewModel, который управляет стеком экземпляров
  /// ViewModel для обратных переходов
  /// </summary>
  // --- свойства
  this.viewModelBackStack = ko.observableArray();
  // --- функции
  this.navigateTo = function (viewModel) {
    this.viewModelBackStack.push(viewModel);
  };
  this.back = function () {
    this.viewModelBackStack.pop();
  };
  this.templateSelector = function (viewModel) {
    return viewModel.template;
  }
}

При запуске приложения создается экземпляр ApplicationViewModel и связывается с UI через Knockout:

document.addEventListener("deviceready",
  initializeViewModel, false);
var application;
function initializeViewModel() {
  application = new ApplicationViewModel();
  ko.applyBindings(application);
}

Сам по себе UI весьма прост и включает элемент div, использующий шаблон привязки Knockout для рендеринга стека ViewModel:

<body>
  <h1>Cordova Twitter Search</h1>
  <div class="app"
    data-bind="template: {name: templateSelector,
                          foreach: viewModelBackStack}">
  </div>
</body>

Шаблон привязки Knockout работает аналогично Silverlight ItemsControl в том плане, что осуществляет связывание с массивом экземпляров ViewModel и отвечает за генерацию View для каждого из них через шаблон. В данном случае в ApplicationViewModel вызывается функция templateSelector, чтобы определить именованный шаблон для каждого ViewModel.

Запустив это приложение, вы обнаружите, что оно ничего не делает. Причина в том, что пока нет никаких ViewModel, представляющих страницы приложения!

TwitterSearchViewModel

Я опишу первый ViewModel — TwitterSearchViewModel, который представляет первую страницу приложения. Этот ViewModel содержит несколько простых наблюдаемых свойств, поддерживающий UI, а именно: searchTerm, связанное с полем ввода, и isSearching — наблюдаемое свойство типа Boolean, которое отключает кнопку поиска при запросе Twitter API через HTTP. В том же ViewModel содержится функция search, связанная с кнопкой поиска во многом по аналогии с тем, как вы связывали бы ICommand с Button в Silverlight (рис. 7).

Рис. 7. TwitterSearchViewModel

/// <reference path="..//intellisense.js" />
/*globals $ application ko localStorage
  SearchResultsViewModel TweetViewModel*/
function TwitterSearchViewModel() {
  /// <summary>
  /// ViewModel для поиска в Twitter указанной строки
  /// </summary>
  // --- свойства
  this.template = "twitterSearchView";
  this.isSearching = ko.observable(false);
  this.searchTerm = ko.observable("");
  // --- открытые функции
  this.search = function () {
    /// <summary>
    /// Поиск по Twitter указанной строки
    /// </summary>
    // Реализация будет детализирована далее в этой статье...
  };
}

Свойство template в ViewModel присваивает имя тому View, который сопоставлен с данным ViewModel. Это View описывается как jQuery-шаблон в файле index.html:

<script type=text/x-jquery-tmpl" charset="utf-8"
  id="twitterSearchView"
  <div>
    <form data-bind="submit: search">
      <input type="text"
        data-bind="value: searchTerm, valueUpdate:
          'afterkeydown'" />
      <button type="submit"
        data-bind="enable: searchTerm().length > 0 &&
          isSearching() == false">Go</button>
    </form>
  </div>
</script>

Если вы добавите экземпляр TwitterSearchViewModel в стек ViewModel, то приложение теперь покажет первую страницу (рис. 8).

*
Рис. 8. TwitterSearchViewModel, визуализируемая через шаблон twitterSearchView

Создание Metro UI с помощью CSS

Одно из наиболее впечатляющих средств Windows Phone OS — язык дизайна Metro (design language), который обеспечивает доскональное соблюдение «буквы и духа» Phone. Этот язык дизайна не только позволяет создавать приятные глазу UI, но и весьма практичен, обеспечивая максимальную четкость интерфейсов на смартфонах малого форм-фактора.

В текущем UI (рис. 8) применяются стандартные стили браузера, и результат не слишком приятен для глаз! Уже существует несколько хорошо продуманных инфраструктур для создания хорошо выглядящих мобильных UI с использованием HTML и CSS, например jQuery Mobile (jquerymobile.com). В настоящее время эти инфраструктуры сконцентрированы в большей мере на эмуляции внешнего вида iOS. Приложение Cordova для Windows Phone можно было бы стилизовать с применением jQuery Mobile, хотя это, вероятно, вызвало бы неприятие у некоторых пользователей, потому что оно просто не соответствовало бы «букве и духу» самой ОС.

К счастью, свободную от «хрома» тему Metro можно достаточно легко реплицировать с помощью HTML и CSS. Фактически Windows 8 работает с HTML как с полноправным языком программирования, давая возможность разрабатывать HTML5-приложения в стиле Metro с использованием Windows Runtime API.

Введя правильные шрифты, их размеры и цвета через простую CSS (рис. 9), вы можете создать UI, который очень близко следует теме Metro, как показано на рис. 10.

Рис. 9. Код, следующий теме Metro

body
{
  background: #000 none repeat scroll 0 0;
  color: #ccc;
  font-family: Segoe WP, sans-serif;
}
h1
{
  font-weight: normal;
  font-size: 42.667px; /* PhoneFontSizeExtraLarge */
}
button
{
  background: black;
  color: white;
  border-color: white;
  border-style: solid;
  padding: 4px 10px;
  border-width: 3px; /* PhoneBorderThickness */
  font-size: 25.333px; /* PhoneFontSizeMediumLarge */
}
input[type="text"]
{
  width: 150px;
  height: 34px;
  padding: 4px;
}

*

Рис. 10. Приложение Twitter Search с примененным CSS-стилем Metro

Открою один маленький секрет — это HTML5-приложение, а не «родное», в котором пользователь все равно сможет движением двух пальцев масштабировать UI. Отчасти эту проблему можно решить добавлением в страницу index.html следующего метасвойства:

<meta name="viewport" content="user-scalable=no" />

Одно из наиболее впечатляющих средств Windows Phone OS — язык дизайна Metro, который обеспечивает доскональное соблюдение «буквы и духа» Phone.

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

Изучая визуальное дерево элемента управления WebBrowser, я обнаружил, что к событиям манипуляций можно подключать обработчики и запрещать им передавать события вверх в родной TileHost, который визуализирует HTML5-контент. На эту тему я опубликовал небольшую статью в блоге (bit.ly/vU2o1q), в которую включил простой вспомогательный класс, реализующий этот вариант. Но вы должны использовать его с осторожностью, так как он зависит от внутренней структуры элемента управления WebBrowser, которая может запросто измениться в будущих версиях Windows Phone OS.

Поиск в Twitter

Если вы внимательнее изучите функцию search в TwitterSearchViewModel, то поймете, что она запрашивает Twitter API через jQuery-функцию ajax, которая возвращает ответ в формате JSONP. Экземпляр TweetViewModel конструируется из каждого возвращенного твита, и они используются для конструирования экземпляра SearchResultsViewModel (рис. 11).

Рис. 11. Функция search в TwitterSearchViewModel

this.search = function () {
  /// <summary>
  /// Поиск в Twitter указанной строки
  /// </summary>
  this.isSearching(true);
  var url = "http://search.twitter.com/search.json?q=" +
    encodeURIComponent(that.searchTerm());
  var that = this;
  $.ajax({
    dataType: "jsonp",
    url: url,
    success: function (response) {
      // Создаем массив для хранения результатов
      var tweetViewModels = [];
      // Добавляем новые элементы
      $.each(response.results, function () {
        var tweet = new TweetViewModel(this);
        tweetViewModels.push(tweet);
      });
      // Переходим к конечному ViewModel
      application.navigateTo(
        new SearchResultsViewModel(tweetViewModels));
      that.isSearching(false);
    }
  });
};

SearchResultsViewModel просто содержит список твитов:

/// <reference path="..//intellisense.js" />
/*globals ko*/
function SearchResultsViewModel(tweetViewModels) {
  /// <summary>
  /// ViewModel, визуализирующий результаты поиска по Twitter
  /// </summary>
  /// <param name="tweetViewModels">массив
  /// экземпляров TweetViewModel</param>
  // --- свойства
  this.template = "searchResultsView";
  this.tweets = ko.observableArray(tweetViewModels);
}

А TweetViewModel предоставляет свойства индивидуального твита и функцию select, которая обеспечивает навигацию к представлению индивидуального твита, как показано на рис. 12.

Рис. 12. TweetViewModel

/// <reference path="..//intellisense.js" />
/*globals application*/
function TweetViewModel(tweet) {
  /// <summary>
  /// ViewModel, представляющий один твит
  /// </summary>
  /// <param name="tweet">Твит, возвращенный
  /// через API поиска</param>
  // --- свойства
  this.template = "tweetDetailView";
  this.author = tweet.from_user;
  this.text = tweet.text;
  this.id = tweet.id;
  this.time = tweet.created_at;
  this.thumbnail = tweet.profile_image_url;
  // --- открытые функции
  this.select = function () {
    /// <summary>
    /// Выбирает этот твит, что заставляет приложение
    /// перейти к представлению данного твита
    /// </summary>
    application.navigateTo(this);
  };
}

И вновь шаблоны, описывающие View для каждого из этих ViewModel, добавляются в файл index.html (рис. 13).

Рис. 13. Добавление шаблонов в файл index.html

<script type=text/x-jquery-tmpl" charset="utf-8"
  id="searchResultsView">
  <div>
    <ul data-bind="template: {name: 'tweetView',
                              foreach: tweets}"> </ul>
  </div>
</script>
<script type="text/x-jquery-tmpl" charset="utf-8"
  id="tweetView">
  <li class="tweet"
      data-bind="click: select">
    <div class="thumbnailColumn">
      <img data-bind="attr: {src: thumbnail}"
                             class="thumbnail"/>
    </div>
    <div class="detailsColumn">
      <div class="author"
           data-bind="text: author"/>
      <div class="text"
           data-bind="text: text"/>
      <div class="time"
           data-bind="text: time"/>
    </div>
  </li>
</script>

Теперь, когда пользователь касается кнопки «go» для поиска по Twitter, в стек ViewModel добавляется SearchResultsViewModel. Это автоматически приводит к визуализации шаблона searchResultsView в «app» div. Однако, поскольку стек ViewModel визуализируется через привязку шаблона foreach, это не вызывает скрытия экземпляров шаблона twitterSearchView; вместо этого они размещаются один над другим. Данную проблему можно устранить добавлением пары простых CSS-правил:

.app>div
{
  display: none;
}

.app>*:last-child
{
  display: block;
}

Первый селектор скрывает всех потомков div, помеченного классом app, а второй селектор, имеющий более высокий приоритет, гарантирует отображение последнего потомка.

Благодаря этим CSS-правилам приложение Twitter Search становится полностью функциональным.

Управление стеком переходов назад

Используя приложение Twitter Search в текущем состоянии, можно переходить со страницы результатов поиска к индивидуальному твиту, но, если нажимается аппаратная кнопка Back, происходит немедленный выход из приложения. Дело в том, что все переходы в приложении осуществляются исключительно в рамках элемента управления «браузер», а значит, с точки зрения инфраструктуры Silverlight, в приложении всего одна страница. Это приведет не только к неудобству для пользователей, но и почти наверняка к отклонению вашего приложения при попытке передать его в Windows Phone Marketplace.

К событиям манипуляций можно подключать обработчики и запрещать им передавать события вверх в родной TileHost, который визуализирует HTML5-контент.

К счастью, решение этой проблемы несложно. ApplicationViewModel содержит стек экземпляров ViewModel. Если в этом стеке находится более чем один экземпляр ViewModel, вы должны обрабатывать нажатие аппаратной кнопки Back и выталкивать из этого стека самый верхний ViewModel. Иначе инфраструктура Silverlight завершит ваше приложение.

Примечательно, что, хотя браузер Windows Phone поддерживает локальное хранилище, эта функция отключается, когда браузер визуализирует страницы из изолированного хранилища.

Для поддержки этого в ViewModel добавляется зависимое наблюдаемое свойство backButtonRequired:

function ApplicationViewModel() {
  // --- свойства
  this.viewModelBackStack = ko.observableArray();
  this.backButtonRequired = ko.dependentObservable(function ()
  {
    return this.viewModelBackStack().length > 1;
  }, this);
  // --- функции
  // ...
}

При инициализации ViewModel вы можете обрабатывать изменения в этом наблюдаемом свойстве и подписываться на события backbutton, предоставляемые инфраструктурой Cordova. Как только срабатывает такое событие, вы вызываете функцию back в ApplicationViewModel, которая выталкивает из стека самый верхний ViewModel. Шаблон привязки Knockout берет на себя удаление сопоставленного с ViewModel представления из UI, и стили CSS гарантируют, что представление, которое теперь стало самым верхним в стеке, будет видимым (рис. 14).

Рис. 14. Обработка нажатия кнопки Back

function initializeViewModel() {
  application = new ApplicationViewModel();
  ko.applyBindings(application);
  // Обработка кнопки Back
  application.backButtonRequired.subscribe(
    function (backButtonRequired) {
    if (backButtonRequired) {
      document.addEventListener("backbutton",
        onBackButton, false);
    } else {
      document.removeEventListener("backbutton",
        onBackButton, false);
    }
  });
  var viewModel = new TwitterSearchViewModel();
  application.navigateTo(viewModel);
}
function onBackButton() {
  application.back();
}

Поскольку событие backbutton предоставляется через Cordova, код на рис. 14 будет нормально работать, даже если вы выполняете приложение в другой ОС (при условии, что у такого смартфона есть аппаратная кнопка Back).

Сохранение состояния

А теперь добавим в приложение Twitter Search последний штрих: когда поиск заканчивается успешно, искомая строка вносится в список последних результатов поиска (рис. 15).

Рис. 15. Добавление строки поиска в список последних результатов

function TwitterSearchViewModel() {
  /// <summary>
  /// ViewModel для поиска по Twitter указанной строки
  /// </summary>
  // --- свойства
  // ...некоторые свойства опущены для упрощения картины...
  this.recentSearches = ko.observableArray();
  // --- функции
  // ...некоторые функции опущены для упрощения картины...
  this.loadState = function () {
    /// <summary>
    /// Загружает сохраненное состояние ViewModel
    /// из локального хранилища
    /// </summary>
    var state = localStorage.getItem("state");
    if (typeof (state) === 'string') {
      $.each(state.split(","), function (index, item) {
        if (item.trim() !== "") {
          that.recentSearches.push(item);
        }
      });
    }
  };
  function saveState() {
    /// <summary>
    /// Сохраняет состояние ViewModel в локальном хранилище
    /// </summary>
    localStorage.setItem("state",
      that.recentSearches().toString());
  }
  function addSearchTermToRecentSearches() {
    /// <summary>
    /// Добавляет текущую строку поиска в историю поиска
    /// </summary>
    that.recentSearches.unshift(that.searchTerm());
    saveState();
  }
}

Функция addSearchTermToRecentSearches использует удобную Knockout-функцию unshift, которая добавляет элемент в начало массива. Всякий раз, когда добавляется результат последнего поиска, состояние сохраняется с использованием локального хранилища HTML5. В данном случае массив преобразуется в разделенный запятыми список с помощью функции toString и преобразуется обратно через split. Более сложный ViewModel скорее всего потребовал бы сохранять множество значений свойств в формате JSON.

Примечательно, что, хотя браузер Windows Phone поддерживает локальное хранилище, эта функция отключается, когда браузер визуализирует страницы из изолированного хранилища. Чтобы сохранить работоспособность более раннего кода, группе Cordova пришлось написать корректировочную реализацию API локального хранилища для сохранения состояния в рамках изолированного хранилища Phone.

После внесения последнего изменения приложение Twitter Search становится полностью функциональным.

Подтверждение портируемости: запуск на iPhone

Как видите, инфраструктура Cordova позволяет создавать HTML5-приложения для Windows Phone. Также можно имитировать родной стиль Metro, используя HTML5 и CSS, а инфраструктуры наподобие Knockout дают возможность должным образом структурировать ваш код.

В этой статье основное внимание уделялось созданию приложений для Windows Phone, но программа Twitter Search является портируемой и должна работать на устройствах iPhone или Android без модификации. Вот только вопрос: годятся ли для этих устройств приложения в стиле Metro?

В качестве последней демонстрации гибкости этого подхода я создал iOS-версию приложения Twitter Search, используя jQuery Mobile для имитации «духа и буквы» этой ОС. Тут очень пригодился шаблон MVVM в том плане, что изменять пришлось только View — вся логика ViewModel остается той же. С помощью облачного сервиса Build я смог создать пакет «ipa» для iOS и установить его на iPhone, причем сделал все это с компьютера под управлением Windows. На рис. 16 показаны два этих приложения, работающих бок о бок.

*
Рис. 16. Twitter Search, выполняемое на iPhone и устройстве Windows Phone

Полный исходный код версий приложения для iOS и Windows Phone вы найдете в пакете скачиваемого кода для этой статьи.


Ссылка: http://www.oszone.net/19165/HTML5