Опубликован: 02.04.2013 | Доступ: свободный | Студентов: 987 / 20 | Длительность: 20:18:00
Специальности: Программист
Лекция 5:

Элементы управления, их стилизация и привязка данных

Односторонняя привязка

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

В коде, который мы видели выше, если мы меняем login.name после вызова WinJS.Binding.processAll, с элементом управления, выводящим данные, ничего не происходит. Как же нам автоматически обновить выводимую информацию?

Вообще говоря, это требует, чтобы источник данных поддерживал список привязок (bindings), где каждая привязка может описывать свойство источника, свойство целевого объекта и функцию-конвертер. Источник данных, кроме того, нуждается в методах для управления этим списком, наподобие addBinding, removeBinding и так далее. И третье, если привязываемое (или наблюдаемое (observable)) свойство меняется, нужно пройтись по списку привязок и обновить соответствующим образом любые связанные свойства целевых объектов.

Эти требования выглядят достаточно обобщенными. Вы можете представить, что их реализация будет в значительной мере походить на классический шаблонный код. Поэтому, конечно, WinJS предоставляет такую реализацию! В этом контексте источники называются наблюдаемыми объектами (observable objects) и функция WinJS.Binding.as заключает любые объекты в объекты подобной структуры (она возвращает пустой оператор для необъектных сущностей). Наоборот, функция WinJS.Binding.unwrap удаляет эти структуры, если в том есть необходимость. Более того, функция WinJS.Binding.define создаёт конструктор для наблюдаемых объектов вокруг набора свойств (описанных с помощью чего-то вроде пустого объекта, содержащего лишь имена свойств). Подобный конструктор позволяет вам создать экземпляр объекта-источника динамически, как при обработке данных, полученных от онлайнового сервиса.

Давайте посмотрим на код. Вернемся к последнему примеру выше (Test 3), в любое время до или после WinJS.Binding.processAll мы можем взять объект LoginData.Login и сделать его наблюдаемым, как показано ниже:

var loginObservable = WinJS.Binding.as(LoginData.login)

На самом деле, это всё, что нам надо сделать - всё остальное будет таким же, как раньше. Мы можем сейчас изменить связанное значение в объекте loginObservable:

loginObservable.name = "liambro";

Эта команда обновит целевое свойство:

Вот как мы затем создаём и используем многократно используемый класс для наблюдаемого объекта (Test 4 в упражнении BindingTest). Обратите внимание на то, что объект, который мы передаем WinJS.Binding.define содержит имена свойств, но не значения (они будут проигнорированы):

WinJS.Namespace.define("LoginData", {
//...
//LoginClass становится конструктором для прикрепляемого объекта с заданными свойствами
LoginClass: WinJS.Binding.define({name: "", id: "", photoURL: "", userType: "" }),
});

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

var login4 = new LoginData.LoginClass({ name: "liamb",
photoURL: "http://www.kraigbrockschmidt.com/images/Liam08.jpg" });

Осуществляя привязку к этому объекту login, мы видим, что имя пользователя первоначально имеет черный цвет:

//Выполняем связывание 
(первоначально имя пользователя выведено черным цветом) 
WinJS.Binding.processAll(document.getElementById("loginDisplay"), login4);

Обновление свойства userType в источнике (как показано ниже), приведет к обновлению цвета целевого свойства, что, посредством конвертера, происходит автоматически:

login4.userType = "kid";

Реализация двусторонней привязки

Процесс реализации двусторонней привязки достаточно очевиден:

  1. Добавить прослушиватели к подходящим событиям элемента пользовательского интерфейса, которые имеют отношение к привязанным свойствам источника данных.
  2. В обработчиках событий обновить свойства источника данных.

Источник данных должен быть достаточно интеллектуальным для того, чтобы знать, когда новое значение свойства уже является тем же, что и в целевом свойстве, в таком случае он не попытается обновить целевое свойство. Иначе вы попадёте в бесконечный цикл. Код наблюдаемого объекта, который предоставляет WinJS, уже выполняет такую проверку.

Для того, чтобы увидеть пример реализации подобного механизма, обратитесь к примеру "Декларативная привязка" (http://code.msdn.microsoft.com/windowsapps/DeclarativeBinding-bfcb42a5) в SDK, где показано прослушивание события change в текстовых полях и соответствующее обновление свойств в их источниках данных.

Дополнительные возможности привязки

Если вы посмотрите на описание WinJS.Binding (http://msdn.microsoft.com/library/windows/apps/br229775.aspx) в документации, вы увидите в этом пространстве имен множество других полезных вещей. Позвольте мне кратко описать их особенности. (Кроме того, обратитесь к примеру "Программная привязка данных" (http://code.msdn.microsoft.com/windowsapps/ProgrammaticBinding-de038b64) для демонстрации некоторых возможностей).

Если вы уже определили класс (из WinJS.Class.define) и хотите сделать его наблюдаемым, используйте WinJS.Class.mix, как показано ниже:

var MyObservableClass = WinJS.Class.mix(MyClass, WinJS.Binding.mixin, WinJS.Binding.expandProperties(MyClass));

WinJS.Binding.mixin содержит стандартную реализацию функций связывания, которые ожидает WinJS. WinJS.Binding.expandProperties создаёт объект, свойства которого соответствуют свойствам данного объекта (те же самые имена), каждое из которых заключено в подходящую для связывания структуру. На самом деле, этот тип операции полезен только тогда, когда выполняют смешивание, и это то, что WinJS.Binding.define делает с теми самыми странными, не имеющими значений объектами, которые мы передаем ему.

Если вы помните из предыдущего материала, одно из требований для наблюдаемых объектов заключается в том, чтобы они содержали методы для управления списком привязок. Реализация подобных методов содержится в объекте WinJS.Binding.observableMixin. Вот эти методы:

  • bind Сохраняет привязку (имя свойства и функцию для запуска при изменении).
  • unbind Удаляет привязку, созданную методом bind.
  • notify Просматривает привязку на предмет свойства и запускает функции, связанные с ним. Это то место, где WinJS проверяет, различаются ли старое и новое значения и где он обрабатывает случаи, когда обновление для того же самого целевого объекта уже выполняется.

На это опирается еще один mixin-объект, WinJS.Binding.dynamicObservableMixin (то же, что и WinJS.Binding.mixin), который добавляет методы для управления свойствами объекта-источника:

  • setProperty Обновляет значение свойства и оповещает прослушивателей о том, что значение изменилось.
  • updateProperty Похож на setProperty, но возвращает promise-объект, который возвращает результат, когда все прослушиватели будут оповещены (ожидаемый результат - новое значение свойства).
  • getProperty Возвращает значение свойства в качестве наблюдаемого объекта, что делает возможным осуществлять привязку внутри вложенных объектных структур (obj1.obj2.prop3 и так далее).
  • addProperty Добавляет новое свойство к объекту, которое автоматически подготовлено к привязке данных.
  • removeProperty Удаляет свойство из объекта.

Зачем нам всё это? Это открывает простор для творчества. Вы можете вызвать WinJS.Binding.bind, например, напрямую для любого наблюдаемого источника, когда вы хотите использовать другую функцию для свойства источника. Это похоже на добавление прослушивателей событий для изменения свойства источника, и у вас может быть столько прослушивателей, сколько хотите. Это полезно для настройки двунаправленной привязки данных, и эта возможность не связана с манипуляцией пользовательским интерфейсом. Функция просто вызывается при изменении свойства. Это можно использовать для автоматической синхронизации серверной службы с объектом данных.

Пример "Декларативная привязка данных" (http://code.msdn.microsoft.com/windowsapps/DeclarativeBinding-bfcb42a5), кроме того, показывает вызов bind с объектом в качестве второго параметра, форма, которая позволяет осуществлять привязку к вложенным членам источника данных. Синтаксис похож на этот bind(rootObject, { property: { sub-property: function(value) { ... } } } - что соответствует объекту-источнику. С подобным объектом в качестве второго параметра, bind убеждается в том, что активированы все функции, назначенные вложенным свойствам. В подобном случае значение, возвращаемое bind - это объект с методом cancel, который очистит эту сложную привязку данных.

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

WinJS.Binding, кроме того, имеет несколько интеллектуальных обработчиков множественных изменений одного и того же свойства объекта-источника. После первоначального связывания, дальнейшие уведомления об изменениях асинхронны, и множественные запросы на изменение того же самого свойства объединяются. В итоге, если в нашем примере мы выполним несколько изменений свойства name в быстрой последовательности:

login.name = "Kenichiro"; 
login.name = "Josh"; 
login.name = "Chris";

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

И, наконец, имеется еще несколько функций, находящихся в WinJS.Binding:

  • onetime Функция, которая проходится по заданным целевым (принимающим) свойствам и устанавливает их в значение связанного свойства источника. Эту функцию можно использовать для настоящей единовременной привязки данных, как необходимо при привязке к объектам WinRT. Она так же может быть использован напрямую, как инициализатор, внутри data-win-bind, если источником данных является WinRT-объекта.
  • defaultBind Функция, которая делает то же самое, что и onetime, но устанавливает одностороннюю привязку между всеми заданными свойствами. Она так же служит как инициализатор по умолчанию для всех взаимодействий в data-win-control, когда специальный инициализатор не задан.
  • declarativeBind Реальная реализация processAll. (Они идентичны). В дополнение к обычным параметрам (корневой целевой элемент и контекст данных), она так же принимает параметр skipRoot (если он установлен в true, в процессе обработки не производится привязки к корневому элементу, только к его элементам-потомкам, что полезно для объектов шаблонов) и bindingCache (оптимизированное хранение результатов обработки выражения data-win-bind при обработке шаблонов объектов).

Инициализаторы привязки

В наших первых упражнениях мы видели использование функций-конвертеров, которые превращают некоторое количество данных из источника в формат, который ожидает получить свойство-приемник. Но функции, которые вы задаёте в data-win-bind правильнее будет называть инициализаторами (initializer), так как, на самом деле, они вызываются лишь один раз.

Интересно, почему? Разве конвертеры не используются всякий раз, когда связанное свойство источника копируется в целевое свойство? Хорошо, да, но мы, на самом деле, говорим здесь о двух разных функциях. Посмотрите вниманительно на структуру кода для функции userTypeColor, которую мы использовали ранее:

userTypeToColor: WinJS.Binding.converter(function (type) {
return type == "kid" ? "Orange" : "Black";
})

Функция userTypeColor - это инициализатор. Когда она вызывается - однажды и только однажды - она возвращает значение из WinJS.Binding.converter - конвертер, который затем будет использоваться для каждого обновления свойства. Это и есть настоящая функция-конвертер, а не userTypeColor - на самом деле - это структура, которая упаковывает анонимную функцию, заданную в WinJS.Binding.converter.

Если смотреть глубже, WinJS.Binding.converter, на самом деле, использует bind для того, чтобы установить взаимоотношения между свойством-источником и целевым свойством и вставляет анонимную функцию-конвертер в эти взаимоотношения. К счастью, вам не придётся иметь дело с подобными сложными механизмами и вы можете просто предоставить функцию-конвертер, как показано выше.

Тем не менее, если вы хотите увидеть пример подобного низкоуровневого кода, обратитесь снова к примеру "Декларативная привязка данных" (http://code.msdn.microsoft.com/windowsapps/DeclarativeBinding-bfcb42a5), так как он показывает как создать конвертер для сложных объектов напрямую в коде, без использования WinJS.Binding.converter. В данномм случае функция должна быть маркирована как безопасная для обработки, если на неё ссылаются в разметке. Другая функция, WinJS.Binding.initializer, существует для тех же целей; значение, возвращаемое WinJS.Binding.converter проходит через тот же самый метод, прежде чем оно вернется обратно в ваше приложение.

Шаблоны привязки данных и списки

Вы думаете, что мы уже завершили разговор о WinJS.Bindint? Не вполне, друзья! Есть две части этого обширного API, которые ведут нас прямо в следующую лекцию. (И сейчас вы узнаете настоящую причину, по которой я разместил весь этот раздел там, где разместил!) Первая - это WinJS.Binding.List -источник данных-коллекция, подходящий для привязки, который - не удивительно - весьма полезен при работе с элементами управления-коллекциями.

WinJS.Binding.Template, так же, уникальный вид пользовательского элемента управления. При использовании, как вы могли видеть в примере "Декларативная привязка", вы объявляете элемент (обычно div) с data-win-control = "WinJS.Binding.Template". В той же самой разметке вы задаёте содержимое шаблона в качестве элементов-потомков, каждый из которых может иметь собственные атрибуты data-win-bind. Что в этом уникально, так это то, что когда WinJS.UI.process или processAll находят эту разметку, они создают экземпляр шаблона и убирают всё, кроме корневого элемента, из DOM. Так что же в этом хорошего?

Итак, так как шаблон существует, кто угодно может вызвать его метод render для того, чтобы создать копию этого шаблона внутри какого-нибудь другого элемента, используя какой-нибудь контекст данных для обработки любых атрибутов data-win-bind в нём (обычно пропуская корневой элемент, отсюда и параметр skipRoot в методе WinJS.Binding.declarativeBind). Кроме того, вывод шаблона несколко раз в том же самом элементе позволяет создать множество элементов одного уровня, каждый из которых может иметь различные источники данных.

Вот оно что! Теперь мы можем начать рассматривать, какой всё это имеет смысл для элементов управления для вывода коллекций и источников данных-коллекций. Имея источник данных-коллекцию и шаблон, вы можете обойти этот источник и вывести копию шаблона для каждого отдельного элемента в источнике в его собственном элементе. Добавьте немного возможностей навигации или макет, внутри которого содержится элемент, и вуаля! Вы у истоков того, что, как мы узнаем, является элементами управления WinJS.UI.FlipView и WinJS.UI.ListView, когда разберемся со следующей лекцией.

Что мы только что изучили

  • Общая модель управления для элементов управления HTML и WinJS, в которой каждый элемент управления состоит из декларативной разметки, применимого CSS и методов, свойств и событий, доступных из JavaScript.
  • Стандартные элементы управления HTML имеют выделенную разметку; элементы управления WinJS используют атрибуты data-win-control, которые обрабатываются с использованием WinJS.UI.process или WinJS.UI.processAll.
  • Экземпляры обоих типов элементов управления могут быть созданы программно, с использованием ключевого слова new и подходящих конструкторов, таких, как Button или WinJS.UI.Rating.
  • Все элементы управления имеют различные параметры, которые могут быть использованы для их инициализации. Они представлены как специфические атрибуты в HTML-элементах управления и внутри атрибута data-win-options для элементов управления WinJS.
  • Все элементы управления имеют стандартную стилизацию, определенную в таблицах стилей WinJS: ui-light.css и ui-dark.css. Эти стили могут быть переопределены, по желанию, и некоторые классы стилей, как win-backbutton, которые используются для стилизации стандартных HTML-элементов управления для того, чтобы они выглядели как элементы управления, специфичные для Windows.
  • Приложения для Windows 8 имеют богатые возможности по стилизации, и для HTML и для WinJS элементов управления. В случае с HTML-элементами управления, псевдо-селекторы с префиксом -ms-* позволяют вам адресовать специфические части этих элементов управления. В случае с элементами управления WinJS, отдельные части стилизуют с использованием классов win-*, которые можно переопределить.
  • Пользовательские элементы управления реалзованы так же, как элементы управления WinJS, и WinJS предоставляет стандартные реализации методов наподобие addEventListener. Пользовательские элементы управления, кроме того, могут быть показаны в панели Активы (Assets) в Blend либо для отдельного проекта, либо для всех проектов.
  • WinJS предоставляет декларативные возможности по привязке данных для единовременной и односторонней привязки, которые могут применять функции преобразования данных. Она так же предоставляет возможности по созданию наблюдаемых (подходящих для односторонней привязки) источников данных из любых других объектов.
  • WinJS, кроме того, предоставляет поддержку для коллекций, поддерживающих привязку данных и шаблонов, которые могут быть многократно выведены для различных объектов-источников в один и тот же элемент-контейнер, что является основой элементов управления для вывода коллекций.
Вадик Елетин
Вадик Елетин
Россия, г. Санкт-Петербург
Андрей Шевчук
Андрей Шевчук
Украина, Ильичевск