PhoneJS — Новый HTML5-фреймворк для мобильных приложений

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

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

dc0dea7bffe2722f4acd98e2a2702d8e

В настоящее время веб-технологии и 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) и позволит сотрудникам устанавливать необходимое для работы корпоративное ПО на свои смартфоны и планшеты.

66d99b5004d6a57cacd18c5d10a09edd

Сейчас на примере очень простой демки 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, однако есть и более функциональные варианты с навигационной панелью и так популярной сегодня боковой навигацией.

70963c6c9392ac3d9a8c80645d99cccf

Здесь необходимо сделать отступление о том, что же такое 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 результата: totalToPaytotalPerPersontotalTip и 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 современных браузеров это легко позволяют.

9db2ef983e0b031f18ada3cb87a5b26e

Другой удобный вариант — это Ripple Emulator, работающий в браузере.

6377d081acaf416ff83b85073643443f

Но нам хочется получить настоящее мобильное приложение, с возможностью публикации в маркеты (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 непосредственно из среды разработки.

5bc1171e585cd51ad703836a54e759fd

Итак 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 бесплатен для некоммерческого использования. А наша служба поддержки готова ответить на ваши вопросы и помочь преодолеть возникшие трудности.