Система макетирования Maqetta: Часть 2. Написание специального кода JavaScript для мобильного пользовательского интерфейса, созданного в Maqetta

Разработка интерактивного прототипа UI с применением JavaScript и Dojo Toolkit

Из первой части этого цикла статей читатель узнал, что Maqetta представляет собой инструмент WYSIWYG, который облегчает разработку сложных пользовательских интерфейсов (UI) для настольных или мобильных приложений без написания кода. Но что делать, если нужен более богатый UI, более интеллектуально реагирующий на ввод данных пользователем? В этой статье Тони Эрвин рассматривает процесс совершенствования мобильного UI Maqetta с помощью специального кода JavaScript с применением Dojo и библиотеки Dojo Mobile.

Тони Эрвин, инженер-программист, IBM

Фото автораТони Эрвин (Tony Erwin) — программист группы новых интернет-технологий IBM и один из основных членов группы разработки Maqetta. Работает в IBM с 1998 года и имеет большой опыт проектирования и разработки пользовательских интерфейсов с применением широкого спектра технологий и инструментов. До прихода в IBM получил ученую степень магистра информатики в университете штата Индиана и бакалавра информатики в технологическом институте Rose-Hulman.



12.08.2013

Введение

Об этом цикле статей

Этот цикл статей демонстрирует, как использовать систему Maqetta для прототипирования пользовательских интерфейсов HTML5.

  • В первой части речь шла об основных функциях Maqetta в процессе создания прототипа многофункционального мобильного приложения.
  • Здесь мы поднимем прототип приложения на следующий уровень, написав специальный код JavaScript для добавления интерактивных функций.
  • А в третьей части воспользуемся инструментом PhoneGap, чтобы превратить прототип, сгенерированный Maqetta, в настоящее мобильное приложение, готовое к установке на реальное устройство.

Подробнее об использовании Maqetta можно прочесть в блоге Тони на developerWorks.

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

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

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


Усовершенствованный прототип

Из части 1 вы должны помнить блок-схему приложения для отслеживания веса (см. Рисунок 1). Когда пользователь щелкает на строке в списке весов в представлении mainView, он переходит к представлению detailsView и видит более подробную информацию о данной записи веса с возможностью редактирования. Хотя прототип визуально насыщен и интерактивен, в нем отсутствуют важные моменты:

  • данные, отображаемые в представлениях detailsView, detailsView_Date и detailsView_Notes, не связаны с выбранным элементом списка весов;
  • изменения, внесенные в данные на этих представлениях, не отражаются в основном списке весов, когда пользователь возвращается к представлению mainView;
  • кнопка "плюс" (+) в представлении mainView на самом деле не добавляет новый элемент к списку весов.

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

Рисунок 1. Блок-схема приложения для отслеживания веса Увеличить рисунок
Блок-схема схема приложения для отслеживания веса

Настройка рабочей области

Примеры, приведенные в этой статье, основаны на файлах index.html и weights.json, которые мы создали для прототипа UI программы отслеживания веса в первой статье. В идеале оба эти файла должны быть вам знакомы. Те, кто уже работал с Maqetta, разрабатывая мобильные интерфейсы пользователя, и хочет освоить написание специального кода JavaScript для создания более динамичных компонентов UI, могут загрузить файлы из первой статьи, распаковать их и поместить в рабочую область Maqetta следующим образом:

  1. На палитре Files Maqetta (в левой нижней части дисплея) щелкните правой кнопкой мыши на одном из файлов корневого уровня (например, app.js) и выберите пункт Upload files... (загрузить файлы).
  2. В диалоговом окне Upload Files нажмите кнопку Select Files... (Выбрать файлы). Затем диалоговое окно, зависящее от операционной системы, пригласит вас выбрать файлы из файловой системы. Выберите нужные файлы (index.html и weights.json), и они будут добавлены в список в диалоговом окне загрузки файлов (Рисунок 2).
    Рисунок 2. Загрузка файлов
    Скриншот диалогового окна загрузки файлов
  3. Нажмите кнопку Upload, и файлы будут загружены и отобразятся на вашей палитре файлов. Эти файлы можно использовать так же, как любые другие, созданные вами самостоятельно ― прямо в Maqetta.

Исходный код HTML Maqetta

Прежде чем написать JavaScript для прототипа Maqetta, нужно ознакомиться с исходным кодом HTML, который генерирует прототип. Рассмотрим исходный код index.html:

  1. Откройте index.html в редакторе страниц Maqetta.
  2. Если вы хотите видеть только исходный код (а не холст проекта), нажмите кнопку Source на панели инструментов Maqetta. Выберите Design, чтобы вернуться на холст проекта.
  3. Если вы хотите увидеть исходный код файла вместе с холстом проекта, откройте раскрывающееся меню рядом с кнопкой Source (Рисунок 3) и выберите Split Vertically (разделить по вертикали) или Split Horizontally (разделить по оризонтали). То, что вы выберите, станет представлением по умолчанию при следующем нажатии кнопки Source.
    Рисунок 3. Параметры просмотра в меню Source
    Параметры просмотра в меню Source

Обратите внимание, что работая с разделенным экраном, можно выбрать виджет в области конструктора и увидеть его исходный код на панели Source. Я выбрал EdgeToEdgeDataList из mainView (Рисунок 4), и в области Source выделился соответствующий HTML-код.

Рисунок 4. Горизонтальная панель исходного кода
Горизонтальная панель исходного кода

Исследование исходного кода HTML

Одна из первых вещей, которые делает исходный код HTML, сгенерированный Maqetta, — это установка Dojo. Когда HTML загружен, dojo/parser (см. раздел Ресурсы) анализирует HTML-теги в теле документа и создает виджеты Dojo, как указано. Эти виджеты Dojo представляют собой объекты JavaScript, на которые можно ссылаться и которыми можно манипулировать с помощью специального кода JavaScript.

Справочное руководство по Dojo

Для многофункционального пользовательского интерфейса мобильного приложения, разработанного в этой статье, используются функциональность и виджеты из пакетов Dojo, Dijit и Dojo Mobile (dojox/mobile) набора Dojo Toolkit. Подробнее об этих компонентах можно узнать в Справочном руководстве по Dojo (версия 1.8 на момент написания этой статьи).

Листинг 1 демонстрирует фрагмент HTML-кода из файла index.html, который входит в mainView. Самая первая строка определяет HTML-элемент <div>. Обратите внимание, что значение атрибута data-dojo-type предписывает анализатору Dojo создать экземпляр dojox/mobile/ScrollableView, который представляет собой класс JavaScript из библиотеки Dojo Mobile (см. раздел Ресурсы). Это виджет ScrollableView в нашем представлении mainView.

Также обратите внимание, что ID имеет значение mainView (мы установили его в части 1 с помощью палитры Properties). Как вы вскоре убедитесь, знание типа и ID Dojo-виджета позволяет сравнительно легко написать код JavaScript для изменения поведения виджета во время выполнения.

Листинг 1. Фрагмент mainView из файла index.html
<div data-dojo-type="dojox.mobile.ScrollableView" id="mainView" 
        keepScrollPos="false" scrollBar="true" selected="selected">
    <h1 label="Weight Tracker" data-dojo-type="dojox.mobile.Heading" fixed="top">
        <div label="+" data-dojo-type="dojox.mobile.ToolBarButton" 
                moveTo="detailsView" style="float: right;"></div>
    </h1>
    <span data-dojo-type="dojo.data.ItemFileReadStore" id="ItemFileReadStore_1" 
            jsId="ItemFileReadStore_1" url="weights.json"></span>
    <ul data-dojo-type="dojox.mobile.EdgeToEdgeDataList" store="ItemFileReadStore_1" 
            query="{"label":"*"}"></ul>
</div>

Другие элементы (Листинг 1) должны иметь знакомые атрибуты. Например, внутри элемента <div> для mainView мы имеем:

  • элемент <h1> типа Dojo dojox/mobile/Heading с меткой Weight Tracker. Внутри этого элемента находится элемент <div> типа Dojo dojox/mobile/ToolBarButton с меткой «+» и IDaddWeightButton. Мы напишем код JavaScript, который отвечает на событие нажатия этой кнопки добавлением новой записи в список значений веса;
  • элемент <span> типа Dojo dojo/data/ItemFileReadStore, указывающий URL weights.json;
  • элемент <ul> типа Dojo dojox/mobile/EdgeToEdgeDataList с IDweightList и атрибутом store. Обратите внимание, что атрибут store указывает на объект ItemFileReadStore, созданный в предыдущей строке. Мы напишем код JavaScript, который получает ссылку на этот список и изменяет его хранилище данных.

Обновление weights.json

Прежде чем приступить к написанию специального кода JavaScript, нужно добавить в weights.json некоторые новые и отредактированные поля. Откройте weights.json, дважды щелкнув на палитре Files в левом нижнем углу рабочей области Maqetta. Затем замените содержимое тем, что вы видите в Листинг 2, и сохраните файл.

Листинг 2. Редактирование weights.json
{
    "identifier": "id",
    "items": [
        {id: "weight_0", label: "149", moveTo: "#", rightText: "2012-10-01", 
		notes: "Starting to track my weight."},

        {id: "weight_1", label: "150", moveTo: "#", rightText: "2012-10-09", 
		notes: "Ran 5 miles and ate lots of broccoli!"},

        {id: "weight_2", label: "151", moveTo: "#", rightText: "2012-10-15", 
		notes: "Oops, going in wrong direction."},

        {id: "weight_3", label: "148", moveTo: "#", rightText: "2012-10-23", 
		notes: "Wow, lost 3 pounds!"},

        {id: "weight_4", label: "146", moveTo: "#", rightText: "2012-11-01", 
			notes: "Feeling good!"}
    ]
}

Каждая строка в этом листинге (Листинг 2) содержит элемент, соответствующий записи веса. Теперь каждый элемент содержит некоторые новые и измененные атрибуты:

  • ID — это новое поле, которое действует как уникальный идентификатор данного значения веса (обратите внимание на добавленную строку "identifier": "ID"). Это поле облегчит нам поиск и изменение отдельных элементов JavaScript;
  • label ― это существующее поле, содержащее значение веса для данного элемента;
  • moveTo ― существующее поле, которое я заменил на "#", что позволит нам обработать переход к detailsView в JavaScript;
  • rightText ― это существующее поле, содержащее значение даты для данного элемента;
  • notes ― новое поле, содержащее текстовое примечание к данному элементу.

Так как эта статья посвящена JavaScript, я должен отметить, что weights.json соответствует структуре элементаItemFileReadStore (см. раздел Ресурсы). Глядя на исходный код HTML, вспомните, что это род хранилища данных, используемый объектом EdgeToEdgeDataList.


JavaScript в Maqetta

В оставшейся части этой статьи мы добавим специальный код JavaScript в файл app.js нашего приложения для отслеживания веса. Отметим, что файл app.js предоставляется по умолчанию для всех проектов Maqetta. Если вы посмотрите на исходный код index.html (или любого сгенерированного Maqetta HTML-файла), вы увидите, что он загружает app.js с помощью следующей строки:

<script type="text/javascript" src="app.js"></script>

Сначала откройте файл app.js в своем проекте, что можно сделать, дважды щелкнув на палитре файлов. Файл должен выглядеть как в листинге ().

Листинг 3. Файл app.js по умолчанию

Обратите внимание, что в файле app.js используется функция dojo/ready. Любой код, помещенный в функцию ready, будет гарантированно выполняется только после полной загрузки всех необходимых ресурсов Dojo, синтаксического анализа страницы и создания указанных виджетов Dojo. (Подробнее о dojo/ready см. в разделе Ресурсы).

Простая демонстрация app.js

Чтобы попробовать app.js, давайте добавим в файл предупредительное сообщение, которой будет отображаться при загрузке приложения в браузере:

  1. Добавьте в app.js простой оператор alert (Листинг 4):
    Листинг 4. Оператор alert, добавленный в app.js
    * Этот файл предназначен для специальной логики JavaScript, которая может 
    * потребоваться для ваших HTML-файлов. Этот файл JavaScript входит в 
    * Maqetta по умолчанию в составе HTML-страниц, созданных в Maqetta.
     */
    require(["dojo/ready"], function(ready){
         ready(function(){
    
             // здесь должна быть логика, которая требует полной инициализации Dojo
    
             //Добавление временного уведомления ― просто чтобы убедиться, 
             // что alert работает («Код из app.js работает!»);
         });
    });
  2. Сохраните app.js.
  3. Просмотрите файл index.html с помощью кнопки Preview in Browser и обратите внимание, что после загрузки приложения для отслеживания веса браузер выдает предупредительное сообщение.

Добавление специального кода JavaScript

Большая часть нашей работы по совершенствованию приложения для отслеживания веса будет происходить в app.js. Мы добавим в этот файл специальный код JavaScript для вызова различных виджетов Dojo и манипулирования ими. Вы можете следовать за нами, копируя и вставляя фрагменты кода JavaScript в свой файл app.js.

Многие из фрагментов кода JavaScript представляют собой усовершенствования приложения, пригодные для тестирования. При добавлении кода для тестирования новых функций можно использовать функцию предварительного просмотра Maqetta. Если же вы предпочитаете перескочить вперед, просто загрузите окончательную версиюapp.js и замените ею свою текущую версию.

Необходимые модули

По умолчанию Файл app.js использует функцию require, определенную загрузчиком Dojo, для загрузки одного модуля Dojo (вышеупомянутого dojo/ready). Для наших целей нам понадобятся некоторые другие модули, в том числе следующие (см. "Справочное руководство по Dojo Toolkit" в разделе Ресурсов):

  • dojo/dom
  • dojo/dom-style
  • dijit/registry
  • dojo/on
  • dojo/date/stamp
  • dojo/data/ItemFileWriteStore

Чтобы добавить эти модули в свой файл app.js, просто замените его кодом (Листинг 5), содержащим дополнительные модули.

Листинг 5. Дополнительные модули Dojo для app.js
/*
* Этот файл предназначен для специальной логики JavaScript, которая может 
* потребоваться для ваших HTML-файлов. Этот файл JavaScript входит в 
* Maqetta по умолчанию в составе HTML-страниц, созданных в Maqetta.
 */
require(["dojo/ready", 
        "dojo/dom", 
        "dojo/dom-style", 
        "dijit/registry", 
        "dojo/on", 
        "dojo/date/stamp",
        "dojo/data/ItemFileWriteStore"], 
function(ready, 
         dom, 
         domStyle, 
         registry,
         on, 
         stamp,
         ItemFileWriteStore){

    ready(function(){
         // здесь должна быть логика, которая требует полной инициализации Dojo

    });
});

Ссылки на виджеты

Следующая задача заключается в использовании JavaScript для получения ссылки на каждый виджет Dojo, с которым мы хотим взаимодействовать. Глядя на созданный HTML-код, вспомните, что можно получить ссылку на любой виджет Dojo, чей ID нам известен. Это особенно легко сделать с помощью функции byId, которая находится в модуле dijit/registry. Например, к виджету списка значений веса можно обратиться следующим образом: registry.byId("weightList").

Листинг 6 вызывает registry.byId определенное количество раз, каждый раз находя нужный виджет и сохраняя его ссылку в переменной, которую мы впоследствии будем использовать. На всякий случай я добавил оператор if, чтобы гарантировать, что все переменные будут определены (ведь легко допустить опечатку или забыть ввести ID). Если переменная отсутствует, появится сообщение об ошибке, которое поможет нам выявить причину проблемы.

Листинг 6. Получение ссылок на виджеты
        /* *******************************************
         * Получение ссылок на все необходимые виджеты
         *********************************************/
        var weightList = registry.byId("weightList");
        var mainView = registry.byId("mainView");
        var detailsView = registry.byId("detailsView");
        var detailsView_Date = registry.byId("detailsView_Date");
        var detailsView_Notes = registry.byId("detailsView_Notes"); 
        var weightSpinWheel = registry.byId("weightSpinWheel");
        var dateListItem = registry.byId("dateListItem");
        var notesListItem = registry.byId("notesListItem");
        var dateSpinWheel = registry.byId("dateSpinWheel");
        var notesTextArea = registry.byId("notesTextArea");
        var addWeightButton = registry.byId("addWeightButton");

        // Проверка того, что мы нашли все виджеты
        if (!weightList || 
            !mainView || 
            !detailsView || 
            !detailsView_Date || 
            !detailsView_Notes || 
            !weightSpinWheel || 
            !dateListItem || 
            !notesListItem || 
            !dateSpinWheel || 
            !notesTextArea || 
            !addWeightButton) {

            // вывод сообщения об ошибке для определения того,
            // какие виджеты не удалось найти
            alert("could not find at least one of the widgets:\n" + 
                "\t weightList = " +  weightList + ",\n" + 
                "\t mainView = " +  mainView + ",\n" +
                "\t detailsView = " +  detailsView + ",\n" +
                "\t detailsView_Date = " +  detailsView_Date + ",\n" +
                "\t detailsView_Notes = " +  detailsView_Notes + ",\n" +
                "\t weightSpinWheel = " +  weightSpinWheel + ",\n" +
                "\t dateListItem  = " +  dateListItem + ",\n" +
                "\t notesListItem = " +  notesListItem + ",\n" +
                "\t dateSpinWheel = " +  dateSpinWheel + ",\n" + 
                "\t notesTextArea = " +  notesTextArea + ",\n" +
                "\t addWeightButton = " +  addWeightButton);

            // возврат, чтобы не запускать другие модули JavaScript
            return;
        }

Пойдем дальше и скопируем этот код в функцию ready в файле app.js. Затем сохраним файл и выполним предварительный просмотр приложения, чтобы убедиться, что все виджеты на месте.


Изменение хранилища данных

Выше в исходном коде HTML мы видели, что созданное Maqetta хранилище данных ItemFileReadStore предназначено для использования нашего EdgeToEdgeDataList в mainView. Хотя ItemFileReadStore хорош для статических данных, теперь мы хотим изменять данные приложения для отслеживания веса во время выполнения, поэтому нам понадобится наш EdgeToEdgeDataList для использования ItemFileWriteStore.

У ItemFileWriteStore есть все функции ItemFileReadStore, но добавлены дополнительные, необходимые для API dojo/data/api/Write и dojo/data/api/Notification. Благодаря этим дополнениям мы сможем изменить данные в этом хранилище данных, добавив некоторые специфические функции. Листинг 7 содержит код для обновления хранилища данных, используемого списком значений веса.

Листинг 7. Задание другого хранилища данных
        /* *******************************************
         * Замена ItemFileReadStore, сгенерированного
         * Maqetta, на ItemFileWriteStore  
         *********************************************/
        var weightWriteStore = new ItemFileWriteStore  ({
            url:"weights.json"
        });
        weightList.setStore (weightWriteStore);

Скопируйте этот код (Листинг 7) и вставьте его в файл app.js после оператора if, который мы добавили выше (Листинг 6). Сохранив файл app.js, попробуйте выполнить предварительный просмотр приложения для отслеживания веса. Вы должны увидеть, что список значений веса из файла weights.json по-прежнему отображается в EdgeToEdgeDataList.

Добавление заполнителя для выбранных данных

Наша общая стратегия заключается в том, чтобы хранить данные текущей выбранной записи веса в переменной selectedWeightData. Эту переменную определяет следующий код (Листинг 8). Добавьте его в свой файл app.js. Заметим, что если после внесения изменений выполнить предварительный просмотр приложения для отслеживания веса, никакой разницы в функциональности заметно не будет.

Листинг 8. Добавление заполнителя для выбранных данных
        /* *******************************************
         * Добавление заполнителя для данных веса,
         * редактируемых в настоящий момент.
         ********************************************/
        var selectedWeightData = null;

Добавление обработчика действия click

Далее, определим функцию с именем listItemClick, которая впоследствии будет вызывается при каждом нажатии на элемент EdgeToEdgeDataList в представлении mainView. Функция listItemClick ожидает параметр dojoListItem типа dojox/mobile/ListItem. Мы будем использовать ссылку на объект ListItem для заполнения переменной selectedWeightData (Листинг 8).

После заполнения selectedWeightData мы инициируем переход к detailsView. (Напомним, что выше (Листинг 2) мы заменили moveTo на "#", чтобы можно было вручную обработать этот переход.)

Следующий код JavaScript определяет listItemClick (Листинг 9).

Листинг 9. Обработчик события click
        /* *******************************************
         * Функция, вызываемая при нажатии на элемент 
         * EdgeToEdgeDataList.
         ********************************************/
        var listItemClick = function(dojoListItem) {
            // Заполнение выбранных данных веса на основе выбранного элемента
            selectedWeightData = {
                id: dojoListItem.params.id,
                label: dojoListItem.params.label,
                rightText: dojoListItem.params.rightText,
                notes: dojoListItem.params.notes,
            };

            //Выполнение перехода
            dojoListItem.transitionTo("detailsView");                    
        };

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

Прослушивание события click

Теперь нужно, чтобы всякий раз при выборе элемента из списка весов вызывалась функция listItemClick, но прямого способа добавить обработчика события click к отдельным элементам списка не существует. Но вы должны помнить, что в исходном HTML-коде EdgeToEdgeDataList был представлен элементом <ul>. Во время выполнения EdgeToEdgeDataList будет создавать отдельные элементы <li> для каждого элемента списка. Мы воспользуемся этим для создания нужной функциональности списка весов.

Мы используем dojo/on для регистрации функции, которая будет вызываться всякий раз при нажатии на EdgeToEdgeDataList (Листинг 10). Затем определим цель события, чтобы узнать, какой элемент списка находился под указателем мыши, когда произошло нажатие. Этот элемент будет потомком элемента <li>, который нас интересует. Определив его происхождение, мы найдем <li>. Тогда мы можем использовать registry.byId, чтобы получить ссылку на ListItem Dojo и вызвать listItemClick.

Листинг 10. Обработчик событий click для EdgeToEdgeDataList
       /* *******************************************
         * При нажатии на список весов нам нужно 
         * найти целевой ListItem Dojo,
         * выбранный пользователем, и обработать событие click.
         ********************************************/
        on(weightList, "click", 
            function(event) {
                // "Целью" события будет элемент списка, на который нажали.
                 // (Currenttarget события будет сам список).
                var subElement = event.target;

                // Элементом списка может быть LI или дочерний элемент LI.
                // В противном случае нужно искать родословную элемента, чтобы найти LI.
                // 
                // 
                var parent = subElement.parentNode;
                while (parent != null && parent.nodeName != "LI") {
                    parent = parent.parentNode;
                }

                if (parent) {
                    // Если родитель установлен, мы нашли LI. 
                    // Тогда можно использовать идентификатор для получения Dojo ListItem.
                    var dojoListItem = registry.byId(parent.id);

                    // Обработка события click
                    listItemClick(dojoListItem);
                }
        });

Дополнив app.js этим кодом и сохранив его, выполните предварительный просмотр приложения для отслеживания веса. Вызывает ли щелчок на элементе списка EdgeToEdgeDataList переход к представлению detailsView? Напомним, что теперь этот переход инициируется функцией listItemClick. Так что если функция listItemClick выполняется, должен происходить и переход. А если переход происходит, то мы можем быть уверены, что selectedWeightData заполнится данными из выбранного элемента списка.


Контроль событий перехода

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

Детали переходов представлений

Непосредственно перед тем как представление становится видимым (как показывает событие onBeforeTransitionIn), нужно задать значения в виджетах этого представления на основе данных из selectedWeightData. Аналогично, перед тем как представление будет скрыто (как показывает событие onBeforeTransitionOut), нужно обновить данные в переменной selectedWeightData. Обновление отражает любые изменения, внесенные пользователем во время взаимодействия с виджетами представления. После того как мы обновим selectedWeightData, любые изменения будут доступны для обработчика перехода, чтобы отобразилось следующее представление.

В следующем коде (Листинг 11) мы начинаем с рассмотрения перехода к представлению detailsView. Используя модуль dojo/on, мы указываем функцию, вызываемую в случае события beforeTransitionIn для detailsView. В этой функции мы обновляем weightSpinWheel, dateListItem и notesListItem в соответствии со значениями, обнаруженными в selectedWeightData. Обратите внимание на следующее (Листинг 11):

  • для обновления weightSpinWheel требуется некоторое маневрирование, потому что нужно задать значение каждого слота шкалы в соответствии с цифрами веса (которые находятся в selectedWeightData.label). Получается, что weightSpinWheel представляет собой экземпляр dojox/mobile/SpinWheel, поэтому можно получить слоты путем вызова функции getSlots. Каждый слот является экземпляром dojox/mobile/SpinWheelSlot. Как и для большинства виджетов Dojo Mobile, значение атрибута можно установить посредством функции set;
  • атрибут rightText элемента dateListItem обновляется путем вызова его функции set. set также используется для обновления атрибута rightText элемента notesListItem;
  • после обновления мы воспользуемся методом domStyle.get, чтобы получить ширину узла DOM, где содержится текст элемента dateListItem. Затем воспользуемся методом domStyle.set, чтобы получить ширину узла DOM, где содержится текст элемента notesListItem. Когда ширина должным образом установлена, можно использовать другие атрибуты CSS (а именно, white-space, overflow и text-overflow), чтобы при необходимости обрезать rightText (добавив многоточие).
Листинг 11. Перед переходом к представлению Details
        /* *******************************************
         * Переходы detailsView
         *********************************************/	 
        on(detailsView, "beforeTransitionIn", 
            function(){
              if (selectedWeightData) {
                // Получение значений слотов шкалы
                var weightSpinWheelSlots = weightSpinWheel.getSlots();

                // Перебор цифр веса для задания значения каждого слота шкалы.
                // Для простоты (и, в конце концов, это только прототип)
                // предполагается, что все метки веса трехзначные
                // (то есть вес > 100)
                for (var i = 0; i < 3; i++) {
                   var char = selectedWeightData.label.charAt(i);
                   weightSpinWheelSlots[i].set("value", char);
                }

                // Обновление элемента даты списка
                dateListItem.set("rightText", selectedWeightData.rightText);

                // Обновление элемента примечаний списка
                notesListItem.set("rightText", selectedWeightData.notes);

                // Обновление стиля в rightTextNode элемента notesListItem, 
                // так чтобы он имел ту же ширину, что и метка даты, и автоматически
                // добавлял многоточие. WhiteSpace, overflow 
                // и textOverflow ― это статические параметры, так что они технически
                // должны поступать в app.css и переопределять класс стиля 
                // mblListItemRightText.
                var width = Math.round(domStyle.get(dateListItem.rightTextNode, "width"));
                domStyle.set(notesListItem.rightTextNode, "width", width + "px");
                domStyle.set(notesListItem.rightTextNode, "whiteSpace", "nowrap");
                domStyle.set(notesListItem.rightTextNode, "overflow", "hidden");
                domStyle.set(notesListItem.rightTextNode, "textOverflow", "ellipsis");
            }
       });

После добавления приведенного выше фрагмента кода в app.js поведение нашего приложения должно стать гораздо ближе к желаемому. Если щелкнуть на элементе в mainView, то виджеты в detailsView должны фактически отражать состояние нажатого элемента. Например, Рисунок 5 показывает результат нажатия на первый элемент в EdgeToEdgeDataList в представлении detailsView. Обратите внимание, что значения отличаются от значений по умолчанию, которые присутствуют в редакторе страниц Maqetta. Отметим также, что если вернуться в mainView (нажав кнопку Home в заголовке) и выбрать другой элемент из списка весов, то в detailsView снова появятся другие значения!

Рисунок 5. Детальное представление со значениями не по умолчанию
Детальное представление со значениями не по умолчанию

Переход из detailsView

Теперь посмотрим, что происходит, когда пользователь покидает представление detailsView. Напомним, что при выходе из представления мы хотим обновить selectedWeightData с учетом всех изменений, внесенных пользователем, пока тот находился в этом представлении. Мы используем dojo/on для регистрации функции, вызываемой в случае события beforeTransitionOut в detailsView (Листинг 12).

Единственный виджет, который пользователь может редактировать в этом представлении, ― это weightSpinWheel, поэтому weight (хранящийся в selectedWeightData.label)- это единственное значение, которое изменяется при выходе из представления. Мы снова используем функцию getSlots для получения цифр из отдельных слотов, но на этот раз перебираем слоты, извлекая из каждого атрибут value и соединяя результаты в строку, соответствующую весу. Затем мы помещаем это значение обратно в selectedWeightData.

Листинг 12. Перед выходом из представления Details
        on(detailsView, "beforeTransitionOut", 
            function(){
                if (selectedWeightData) {
                    // Получение значений слотов шкалы
                    var weightSpinWheelSlots = weightSpinWheel.getSlots();

                    // Создание новой метки для веса из цифр слотов шкалы
                    var newLabel = "";
                    for (var i = 0; i < weightSpinWheelSlots.length; i++) {
                        newLabel += weightSpinWheelSlots[i].get("value");
                    }

                    // Обновление выбранных данных веса
                    selectedWeightData.label = newLabel;
                }
        });

После добавления в app.js приведенного выше фрагмента вы не заметите никакой разницы в поведении приложения. Однако вскоре мы реализуем обработчик событий beforeTransitionIn для mainView, который для элемента списка будет использовать обновленное значение из selectedWeightData.label. После этого станут заметны некоторые изменения!

Переходы для detailsView_Date

При текущем состоянии app.js виджеты detailsView отображают значения элемента веса, выбранного в mainView. Но если нажать на dateListItem в представлении detailsView, чтобы перейти в detailsView_Date, то окажется, что шкала данных по-прежнему заполнена статическим значением, установленным в редакторе страниц Maqetta, а не тем, что выбрано во время выполнения. Поэтому нужно реализовать еще кое-какие обработчики перехода.

Зарегистрируем функцию, которая будет выполняться при каждом событии beforeTransitionIn для detailsView_Date (Листинг 13). В данном случае реализовать ее очень легко. Достаточно присвоить атрибуту value dateSpinWheel (экземпляр dojox/mobile/SpinWheelDatePicker) значение selectedWeightData.rightText. Отметим, что это работает, потому что мы представили свои даты в формате ISO-8601, совместимом с SpinWheelDatePicker.

Листинг 13. Перед переходом в detailsView_Date
        /* *******************************************
         * Переходы для detailsView
         *********************************************/
        on(detailsView_Date, "beforeTransitionIn", 
            function(){
                if (selectedWeightData) {
                    // ПРИМЕЧАНИЕ. Дата шкалы должна иметь формат ISO 
                    // (который мы заложили в rightText)
                    dateSpinWheel.set("value", selectedWeightData.rightText);
                }
        });

Если после добавления в файл app.js приведенного выше кода снова выполнить функцию Preview, то вы должны увидеть представление detailsView_Date, работающее должным образом. После выбора элемента веса в mainView значение даты шкалы будет соответствовать значениям rightText как элемента EdgeToEdgeDataList, так и элемента date-list из detailsView.

Единственное, что осталось сделать с detailsView_Date, ― это обновление selectedWeightData значением даты шкалы в том случае, если пользователь изменил его. Как показывает Листинг 14, при событии beforeTransitionOut для detailsView_Date мы просто получаем значение из dateSpinWheel и устанавливаем selectedWeightData.rightText.

Листинг 14. Перед переходом в detailsView_Date
        on(detailsView_Date, "beforeTransitionOut", 
            function(){
                if (selectedWeightData) {
                    // Получение значения шкалы
                    var value = dateSpinWheel.get("value");

                    // Обновление выбранных данных веса
                    selectedWeightData.rightText = value;
                }
        });

Обновите app.js и выполните предварительный просмотр. Если теперь изменить значение даты шкалы в detailsView_Date, то когда вы вернетесь в detailsView, значение rightText элемента dateListItem будет новым.

Переходы для detailsView_Notes

Будем следовать тому же основному процессу при переходе из detailsView_Date для обновления кода detailsView_Notes. Как показывает Листинг 15, обработчики beforeTransitionIn и beforeTransitionOut для detailsView_Notes очень похожи на соответствующие обработчики detailsView_Date. Разница заключается в том, что вместо dateSpinWheel и selectedWeightData.rightText используются notesTextArea и selectedWeightData.notes.

Листинг 15. Переходы для detailsView_Notes
        /* *******************************************
         * Переходы для detailsView
         *********************************************/
        on(detailsView_Notes, "beforeTransitionIn", 
            function(){
                if (selectedWeightData) { 
                    notesTextArea.set("value", selectedWeightData.notes);
                }
        });

        on(detailsView_Notes, "beforeTransitionOut", 
            function(){
                if (selectedWeightData) {
                    // Получение значения из области текста
                    var value = notesTextArea.get("value");

                    // Обновление выбранных данных веса
                    selectedWeightData.notes = value;
                }
        });

Повторите предварительный просмотр после добавления в app.js приведенного выше кода. Вы должны обнаружить, что notesTextArea в detailsView_Notes отражает значение атрибута notes элемента, выбранного в mainView. А если вы внести изменения в начале строки в области notesTextArea и вернуться к представлению detailsView, то rightText элемента notesListItem также должен обновиться.

Переходы для основного представления

Теперь весь код переходов для detailsView, detailsView_Date и detailsView_Notes на месте. Далее, мы хотим сделать так, чтобы наш EdgeToEdgeDataList показывал текущие данные, когда пользователь возвращается в mainView из detailsView. Для этого мы будем взаимодействовать главным образом с weightWriteStore. Напомним, что в начале наших усовершенствований app.js мы создали weightWriteStore (см. Листинг 7). Это экземпляр ItemFileWriteStore, задача которого заключается в предоставлении данных для weightList.

Теперь нужно изменить значения элемента данных в соответствии с элементом списка, который был выбран первоначально. Начнем с вызова fetchItemByIdentity из weightWriteStore с интересующим нас ID (Листинг 16). Функция fetchItemByIdentity работает асинхронно, поэтому нам нужно передать ей функцию, которая будет вызываться при выборе элемента (через аргумент onItem).

После вызова нашей функции onItem процесс упрощается. Мы отслеживаем изменения данных, трижды вызывая метод setValue для weightWriteStore, чтобы обновить label, rightText и notes выбранного элемента значениями, хранящимися в selectedWeightData. Затем вызываем setStore из weightList с помощью weightWriteStore, чтобы EdgeToEdgeDataList обновился данными из хранилища. Наконец, мы обнуляем selectedWeightData, так как активного выбора больше нет.

Листинг 16. Перед переходом в mainView
        /* *******************************************
         * Переход в mainView 
         *********************************************/
        on(mainView, "beforeTransitionIn",
            function(){
                if (selectedWeightData) {
                    weightWriteStore.fetchItemByIdentity({
                        identity: selectedWeightData.id,
                        onItem: function(item) {
                            // Мы извлекли элемент, который хотим изменить, 
                            // чтобы обновить его в хранилище данных списка значений веса
                            weightWriteStore.setValue(item, "label", 
                                selectedWeightData.label);
                            weightWriteStore.setValue(item, "rightText", 
                                selectedWeightData.rightText);
                            weightWriteStore.setValue(item, "notes", 
                                selectedWeightData.notes);

                            // Принудительная перезагрузка списка значений веса в 
                            // хранилище данных
                            weightList.setStore(null); 
                            weightList.setStore(weightWriteStore);

                            //Очистка выбранных данных веса
                            selectedWeightData = null;
                        },
                        onError: function(error) {
                            // НА БУДУЩЕЕ: в производственной среде 
                            // нужно что-то делать с ошибкой
                            console.error("fetchItemByIdentity failed!");
                        }
                    });
                }
        });

После обновления app.js и просмотра приложения вы должны увидеть, что мы решили большую часть своих задач. В частности, изменения, внесенные в detailsView и detailsView_Date, отражаются в EdgeToEdgeDataList после возврата в mainView. Изменения, внесенные в detailsView_Notes, менее очевидны, но в приведенном выше коде мы видели, что изменения, внесенные в notes, сохраняются в weightWriteStore. Чтобы проверить это, повторно выберите ту же запись веса в weightList и перейдите к detailsView_Notes. Если текст в notesTextArea был изменен, то вы должны увидеть обновленное значение.


Добавление записей веса

Осталось написать последний фрагмент JavaScript для нашего динамического прототипа UI. Напомним, что мы хотели, чтобы кнопка плюс (+) в заголовке mainView добавляла новый элемент в список весов с последующим переходом к представлению detailsView, где пользователь сможет отредактировать новую запись.

Для этого сначала воспользуемся dojo/on для регистрации функции, вызываемой при нажатии addWeightButton (Листинг 17). В этой функции сделаем следующее:

  1. Создадим уникальный ID нового элемента с помощью нашего счетчика addWeightCounter.
  2. Используем уникальный ID для создания данных по умолчанию для нового элемента.
  3. Сделаем так, как будто новый элемент был выбран и поместил свои данные в selectedWeightData. При переходе в detailsView его обработчик beforeTransitionIn сможет использовать эти данные.
  4. Вызовем newItem из weightWriteStore с данными для нового элемента. Помимо добавления в хранилище данных это запускает соответствующие события, так что EdgeToEdgeDataList автоматически создаст новый виджет элемента списка, представляющий новый вес.

Листинг 17 демонстрирует код JavaScript для кнопки «плюс».

Листинг 17. Обработка нажатия кнопки addWeightButton
        /* *******************************************
         * Обработка addWeightButton
         ********************************************/
         var addWeightCounter = 0;
         on(addWeightButton, "click", function() {
             // Создание уникального идентификатора нового элемента
             var newWeightId = "newWeight_" + addWeightCounter++;

             // Заполнение некоторых данных по умолчанию для нового элемента
             var newWeightData = {
                 id: newWeightId,
                 moveTo: "detailsView",
                 //По умолчанию устанавливается 150, но в промышленном коде нужно 
                 // использовать последнее значение веса
                 label: "150", 
                 //rightText по умолчанию заполняется сегодняшней датой
                 rightText: stamp.toISOString(new Date(), {selector: 'date'}), 
                 //Примечание по умолчанию ― пустая строка
                 notes: ""
             };

             // Установка выбранных данных веса для нового элемента
             selectedWeightData = newWeightData;

             // Добавление нового элемента в хранилище данных. ПРИМЕЧАНИЕ. 
             // Для упрощения прототипа мы просто всегда добавляем в хранилище данных  
             // новый элемент.То есть мы не рассматриваем возможность отмены 
             //операции пользователем.
             weightWriteStore.newItem(newWeightData);
         });

После обновления app.js при предварительном просмотре приложения кнопка "плюс" должна выполнять переход к detailsView с отображением данных нового элемента. Если немедленно вернуться в mainView, вы увидите новую запись со значением веса 150 и сегодняшней датой. В качестве последнего теста попробуйте вернуться в mainView и изменить вес и/или дату. По возвращении в mainView, благодаря всем нашим обработчикам перехода, эти обновленные значения должны отразиться в новой записи.


Заключение

Прототип UI Maqetta, который мы разработали в первой части, было легко построить, не написав ни строки кода, и он уже был довольно мощным, но в этой статье мы значительно усовершенствовали его. Добавив специальный код JavaScript и используя интерактивные функции Dojo и Dojo Mobile, мы сделали алгоритм приложения для отслеживания веса более реалистичным. Приложение еще далеко не закончено (например, добавленные или отредактированные веса не сохраняются между сеансами, и пользователь не может удалить запись веса), но для прототипа это нормально. Добавленные функции наглядно продемонстрируют руководителю или потенциальному инвестору, как пользовательский интерфейс должен вести себя на практике.

После выполнения упражнений первой и второй статей вы накопили достаточный опыт работы с Maqetta, чтобы приступить к созданию своих собственных прототипов Maqetta и даже написанию необходимого специального кода JavaScript. В третьей, заключительной статье этого цикла мы продолжим опираться на идеи, с которыми работали до сих пор. Сначала мы построим простой прототип приложения GPS-локатора, а затем воспользуемся PhoneGap, чтобы превратить его в "родное" приложение для установки на реальных мобильных устройствах. Пока же просмотрите раздел Ресурсы, чтобы больше узнать о Maqetta.


Загрузка

ОписаниеИмяРазмер
Окончательный исходный код специального приложенияmaqetta_part2.zip5 КБ

Ресурсы

Научиться

Получить продукты и технологии

  • Для многофункционального пользовательского интерфейса мобильного приложения, разработанного во второй статье, используются функциональность и виджеты из пакетов Dojo, Dijit и Dojo Mobile набора Dojo Toolkit.
  • Maqetta.org: запуск Maqetta в облаке.
  • Загрузить Maqetta: установите среду Maqetta на сервере в своей интрасети, загрузив ее со страницы загрузок.

Обсудить

  • Присоединяйтесь к группе пользователей Maqetta: общайтесь с другими дизайнерами и разработчиками, использующими Maqetta для создания настольных и мобильных UI.
  • Примите участие в деятельности сообщества developerWorks. Общайтесь с другими пользователями developerWorks, читая блоги разработчиков, форумы, группы и вики.

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Мобильные приложения, Open source, Web-архитектура
ArticleID=940535
ArticleTitle=Система макетирования Maqetta: Часть 2. Написание специального кода JavaScript для мобильного пользовательского интерфейса, созданного в Maqetta
publish-date=08122013