Новые инструменты для разработки под мобильные устройства появляются сегодня чуть ли не каждый день. Большая часть из них — это HTML5-фреймворки, с помощью которых, обладая навыками веб-программиста, можно создавать приложения для смартфонов и планшетов, не углубляясь в изучение платформенных SDK и других языков.
Мы видим этот тренд и видим примеры реализации пользовательских интерфейсов на HTML5, не уступающих по качеству нативным приложениям, и верим, что за HTML5 стоит будущее кросс-платформенных мобильных приложений.
В настоящее время веб-технологии и JavaScript, в частности — это очень либеральная среда, не привязанная к конкретным средствам и методологиям разработки. Как в любой подобной ситуации, с одной стороны это дает свободу, с другой стороны вносит долю хаоса.
Мы в DevExpress организовали небольшую команду, которая на протяжении нескольких месяцев анализировала и пробовала различные существующие средства и подходы. Результат полученного опыта — PhoneJS — наше комплексное решение для создания кросс-платформенных мобильных приложений на HTML5.
Мы не стали писать PhoneJS с нуля. Взяли jQuery, потому что сегодня для многих это синоним слова JavaScript. Затем добавили упоминавшуюся несколько раз на Хабре библиотеку Knockout, реализующую паттерн Model-View-ViewModel (MVVM) для JavaScript, и выбрали MVVM рекомендованным подходом к структурированию приложения. Использование Apache Cordova (aka PhoneGap) позволило выйти из изоляции внутри WebView браузера и получить доступ к камере, файловой системе, акселерометру и другим функциям устройства. Сочетание этих компонентов помогло создать отличный фундамент для фреймворка.
На этом фундаменте мы построили layout-движок и UI-компоненты, адаптирующие свой внешний вид под три самых популярных платформы — iOS, Android и Windows Phone 8.
Стоит упомянуть, что всё большее количество сотрудников компаний пользуются на рабочем месте своими собственными мобильными устройствами. Идея создания кросс-платформенных приложений хорошо согласуется с этим трендом (известным под названием Bring Your Own Device, BYOD) и позволит сотрудникам устанавливать необходимое для работы корпоративное ПО на свои смартфоны и планшеты.
Сейчас на примере очень простой демки TipCalculator давайте посмотрим, как выглядит приложение на фреймворке PhoneJS. Несмотря на то, что TipCalculator состоит из одного экрана, его будет достаточно для понимания подхода и базовых принципов работы.
Приложение реализует алгоритм расчета чаевых. Исходный код доступен на GitHub, а посмотреть его в действии можно вонлайн-симуляторе.
В сущности, это Single Page Application на HTML5. Точка входа — файл index.html, в котором вы увидите только необходимые META-теги и подключение всех остальных ресурсов. Обратите внимание, как подключаются view-файлы
<link rel="dx-template" type="text/html" href="views/home.html" />
Такой подход позволяет избежать распространенной проблемы Single Page приложений, когда всю HTML разметку приходится держать в одном длинном index.html. Мы соблюдаем принципы декомпозиции приложения и облегчаем последующие этапы разработки и отладки.
В файле index.js объявляется пространство имен TipCalculator и создается объект-приложение:
TipCalculator.app = new DevExpress.framework.html.HtmlApplication(...)
Приложению мы сообщаем тип layout-а, используемый по умолчанию. В данном случае это самый простой из возможных — empty layout, однако есть и более функциональные варианты с навигационной панелью и так популярной сегодня боковой навигацией.
Здесь необходимо сделать отступление о том, что же такое layout вообще. В PhoneJS мы применили знакомую, проверенную и, давно известную вам идею декорации страниц (представлений, экранов) с помощью layout. Так делается в многочисленных серверных web-фреймворках (Ruby on Rails, ASP.NET MVC и многих PHP-библиотеках).
Подробнее про Views и Layouts можно почитать в нашей документации.
Есть и маршрутизация (routing):
TipCalculator.app.router.register(":view", { view: "home" });
Таким образом мы зарегистрировали простой маршрут, который из адресной строки (а если точнее, то из hash-сегмента) получает имя текущего экрана (view), причем экран «home» будет использоваться по умолчанию. Его и рассмотрим.
View Model
В файле views/home.js в пространство имен TipCalculator добавляется функция home, которая создает view-model (модель представления) для экрана home
TipCalculator.home = function(params) {
. . .
};
Есть три входных параметра: сумма чека (billTotal
), количество участников (splitNum
) и размер вознаграждения (tipPercent
). Они объявлены как observable, чтобы их можно было привязывать к UI-компонентам и отслеживать изменения значений
var billTotal = ko.observable(),
tipPercent = ko.observable(DEFAULT_TIP_PERCENT),
splitNum = ko.observable(1);
На основании пользовательского ввода вычисляются 4 результата: totalToPay
, totalPerPerson
, totalTip
и tipPerPerson
. Все четыре являются dependent observable (или computed) — то есть обновляются автоматически при изменении хотя бы одного observable, задействованного в вычислении результата.
var totalTip = ko.computed(...);
var tipPerPerson = ko.computed(...);
var totalPerPerson = ko.computed(...);
var totalToPay = ko.computed(...);
Финальную сумму, как правило, округляют, для чего есть две функции roundUp
и roundDown
, изменяющие значениеroundMode
. Его изменение влечет пересчет зависящего от него totalToPay
:
var totalToPay = ko.computed(function() {
var value = totalTip() + billTotalAsNumber();
switch(roundMode()) {
case ROUND_DOWN:
if(Math.floor(value) >= billTotalAsNumber())
return Math.floor(value);
return value;
case ROUND_UP:
return Math.ceil(value);
default:
return value;
}
});
Однако при изменении каждого из входных параметров необходимо сбросить округление, чтобы пользователь видел точные цифры, для этого с помощью метода subscribe
мы подписываемся на их изменение:
billTotal.subscribe(function() {
roundMode(ROUND_NONE);
});
tipPercent.subscribe(function() {
roundMode(ROUND_NONE);
});
splitNum.subscribe(function() {
roundMode(ROUND_NONE);
});
View model очень проста, особенно для тех читателей, кто уже знаком с Knockout. А для тех, кто не знаком, это хорошая демонстрация его возможностей.
View
Перейдем к HTML-разметке, а именно к файлу views/home.html. На верхнем уровне расположены два специальных div-элемента: для view с именем “home” задается разметка для области с именем “content”.
<div data-options="dxView : { name: 'home' }">
<div data-options="dxContent : { targetPlaceholder: 'content' }">
. . .
</div>
</div>
Внутри расположен тулбар:
<div data-bind="dxToolbar: { items: [ { align: 'center', text: 'Tip Calculator' } ] }"></div>
dxToolbar
— это виджет из состава PhoneJS. Его описание в разметке реализовано в виде Knockout-привязки (binding).
Ниже идут филдсеты. Для их создания применяются предопределенные во фреймворке CSS классы dx-fieldset
и dx-field
. Внутри поле для ввода суммы и два слайдера для процентов и количества человек:
<div data-bind="dxNumberBox: { value: billTotal, placeholder: 'Type here...', valueUpdateEvent: 'keyup', min: 0 }">
</div>
<div data-bind="dxSlider: { min: 0, max: 25, step: 1, activeStateEnabled: true, value: tipPercent }"></div>
<div data-bind="dxSlider: { min: 1, step: 1, max: 10, activeStateEnabled: true, value: splitNum }"></div>
Далее — две кнопки (dxButton
) для округления итоговой суммы и вывод результатов.
<div class="round-buttons">
<div data-bind="dxButton: { text: 'Round Down', clickAction: roundDown }"></div>
<div data-bind="dxButton: { text: 'Round Up', clickAction: roundUp }"></div>
</div>
<div id="results" class="dx-fieldset">
<div class="dx-field">
<span class="dx-field-label">Total to pay</span>
<span class="dx-field-value" style="font-weight: bold" data-bind="text: Globalize.format(totalToPay(), 'c')"></span>
</div>
<div class="dx-field">
<span class="dx-field-label">Total per person</span>
<span class="dx-field-value" data-bind="text: Globalize.format(totalPerPerson(), 'c')"></span>
</div>
<div class="dx-field">
<span class="dx-field-label">Total tip</span>
<span class="dx-field-value" data-bind="text: Globalize.format(totalTip(), 'c')"></span>
</div>
<div class="dx-field">
<span class="dx-field-label">Tip per person</span>
<span class="dx-field-value" data-bind="text: Globalize.format(tipPerPerson(), 'c')"></span>
</div>
</div>
Даже на таком простом примере мы чётко видим, как быстро с помощью PhoneJS создать мобильное приложение, обладая навыками веб программирования. Минимум кода, структурированность и простота освоения.
Запуск, отладка и упаковка для маркетов
Приложения на HTML5 очень легко отлаживать. Достаточно настроить ваш локальный веб-сервер (Apache, IIS, nginx или любой другой) на папку с исходными кодами и открыть URL на устройстве, в эмуляторе устройства или в браузере. Правда в браузере будет необходимо подменить строку UserAgent, чтобы он представлялся смартфоном или планшетом. К счастью, developer tools современных браузеров это легко позволяют.
Другой удобный вариант — это Ripple Emulator, работающий в браузере.
Но нам хочется получить настоящее мобильное приложение, с возможностью публикации в маркеты (AppStore, Google Play и Windows Store).
Конечно же, придется зарегистрироваться как разработчик на сайтах Apple, Google и Microsoft, но этот процесс хорошо описан для каждого из магазинов, так что отдельно заострять внимание на этом не будем.
PhoneGap содержит нужные инструменты и шаблоны проектов. Основной сценарий работы с PhoneGap привязан к SDK мобильной платформы. Например, чтобы собрать APK для Android нужно будет установить Android SDK и создать в Eclipse PhoneGap-проект, а для разработки под iOS понадобится Mac.
Более простой путь, избавляющий нас от установки платформенных SDK — использовать онлайн-сервис PhoneGap Build, который позволяет бесплатно собирать одно приложение (для большего количества необходимо приобрести платный аккаунт). Имея необходимые сертификаты, с помощью PhoneGap Build, буквально через пару кликов можем получить мобильное приложение для разных платформ. Останется только подготовить описания, промо-материалы, картинки, иконки и приступать к публикации вашего приложения.
Небольшая ложка дегтя: на сегодняшний день PhoneGap Build не поддерживает упаковку для Windows Phone 8 (планируют добавить позже в этом году), и для сборки пакетов понадобится Windows Phone SDK и шаблон CordovaWP8App, включенный в дистрибутив PhoneGap.
Для пользователей Visual Stuido у нас есть отдельный продукт DXTREME Mobile (включающий PhoneJS в качестве одного из компонентов), позволяющий собирать приложения для iOS, Android и Windows Phone 8 непосредственно из среды разработки.
Итак PhoneGap + PhoneJS — все что нужно для разработки мобильных приложений
PhoneGap реализует доступ к аппаратным возможностям и решает задачу упаковки в нативные пакеты.
PhoneJS предоставляет инфраструктуру для Single Page приложений и набор оптимизированных под touch-устройства элементов для построения кроссплатформенного UI.
Списки с эластичной прокруткой, «бесконечной подгрузкой» и поддержкой жеста «pull down to refresh», переключатели ON/OFF, различные кнопки и поля ввода, галерея, карты и навигационные элементы — всё это есть в PhoneJS. Посмотреть примеры можно в демо-приложении Kitchen Sink.
Все виджеты можно использовать в двух режимах — как Knockout binding:
<div data-bind="dxCheckbox: {checked: checked} "></div>
<script>
var myViewModel= {
checked: true
};
ko.applyBindings(myViewModel);
</script>
и как jQuery-плагин:
<div id="checkboxContainer" ></div>
<script>
$(function() {
$("#checkboxContainer").dxCheckbox({
checked: true
});
});
</script>
Предвосхищая естественный вопрос читателя, чем PhoneJS лучше / хуже / выделяется на фоне существующих известных решений, мы подготовили нашу реализацию для PropertyCross. Это интересный проект, цель которого сравнить разные существующие подходы к мобильной разработке.
Отметим важную деталь: PhoneJS не похож на большинство наших продуктов — он не привязан к определенному средству разработки. Дистрибутив — это zip-архив, а в качестве инструментов разработки и отладки можно использовать ваш любимый текстовый редактор, ваш любимый веб-сервер и developer tools вашего любимого браузера. Скачать PhoneJS можнопо этой ссылке.
P.S. PhoneJS бесплатен для некоммерческого использования. А наша служба поддержки готова ответить на ваши вопросы и помочь преодолеть возникшие трудности.