Коллекции и элементы управления для вывода коллекций
Файлы к данной лекции Вы можете скачать здесь.
Можно с уверенностью говорить о том, что везде, где бы вы ни были, вы окружены множеством коллекций. Книги, которые вы обычно читаете - это коллекция глав, а глава - это коллекция страниц. Страницы, в свою очередь, являются коллекциями абзацев, абзацы - коллекции слов, слова - коллекции букв, а буквы (если вы читаете это в электронном виде) - это коллекции пикселей. Всё дальше и дальше…
Ваше тело, так же, содержит коллекции разных уровней, которых очень много, как можно узнать из курса анатомии. Оглядывая мой офис и мой дом, я вижу еще больше коллекций: книжная полка с книгами; альбом с листами, и листы с фотографиями; шкафы с консервными банками, коробками и ящиками с едой; неисчислимые игрушки моего сына; коробки с DVD… даже лес за окном - это коллекция деревьев и кустов, у которых есть ветки, на которых есть листья. Всё дальше и дальше…
Мы воспринимаем всё это как коллекции, так как мы знаем, как обобщить отдельные объекты - как листья или страницы, или игрушки - в категории или группы. Это даёт нам мощный инструмент для организации этих вещей и управления ими (за исключением одежды в моём шкафу, моя жена может это подтвердить). И так же, как физический мир вокруг нас во многом состоит из коллекций, цифровой мир, который мы используем для того, чтобы представить объекты реального мира, так же полон коллекций. Языки программирования, наподобие JavaScript, имеют конструкции, такие, как массивы, для организации коллекций данных и управления ими, и окружение, наподобие Windows 8, обеспечивает элементы управления для коллекций, с помощью которых мы можем визуализировать данные и управлять ими.
В данной лекции мы обратим наше внимание на два элемента управления для коллекций, которые предоставлены WinJS. Это FlipView (представление отражения), который отображает один элемент коллекции, и ListView (представление списка), который отображает множество элементов, организуя их по-разному. Как вы можете ожидать, ListView обладает большей функциональностью. Так как он является одной из центральных деталей во многих подходах к проектированию приложений, мы потратим основное количество времени на то, чтобы глубоко его изучить, а так же изучим концепцию и реализацию семантического масштабирования (semantic zoom) (на самом деле, другого элемента управления).
Оба элемента управления для коллекций могут обрабатывать элементы произвольной сложности (и в смысле данных, и в смысле их представления, в отличие от простых HTML-элементов управления, таких, как списки и выпадающие списки), так же это касается и количества элементов, эти элементы управления построены на основе механизмов привязки данных, как мы видели в конце "Элементы управления, их стилизация и привязка данных" . Кроме того, они имеют тесную взаимосвязь с источниками-коллекциями данных, которые мы так же рассмотрим. К ним применимы собственные правила стилизации, они имеют особое поведение.
Но не будем перегружать наши мозги здесь, в начале лекции, теорией или архитектурными сложностями! Рассмотрим код, который позволит нам понять основные аспекты обоих элементов управления.
Основы элементов управления для коллекций
Для рассмотрения основ элементов управления для коллекций, посмотрим сначала на FlipWiew, который позволит нам начать разговор о шаблонах элементов и источниках данных. Затем мы увидим, как это можно применить к ListView, затем разберем группировку элементов в ListView.
Быстрый старт №1: пример использования элемента управления FlipWiew
Как показано на Рис. 5.1, пример "Элемент управления FlipView" (http://code.msdn.microsoft.com/windowsapps/FlipView-control-sample-18e434b4) содержит неплохой код для этого элемента управления и его визуальное представление, позволяющее исследовать этот элемент управления. (Я испытываю особую благодарность за то, что мне не пришлось писать подобные примеры для этого курса!). Для целей быстрого старта, посмотрим на первый сценарий, касающийся заполнения элемента управления из простого источника данных и использования шаблона для рендеринга этого элемента, так как такие же механизмы применяются в ListView. Позже мы вернемся к другим сценариям использования FlipView.
увеличить изображение
Рис. 5.1. Пример использования элемента управления FlipView, где элемент управления отображает картинку
Так как FlipView - это элемент управления WinJS, с конструктором WinJS.UI.FlipView, мы объявляем его в разметке с атрибутами data-win-control и data-win-options (смотрите html/simpleFlipView.html):
<div id="simple_FlipView" class="flipView" data-win-control="WinJS.UI.FlipView" data-win-options="{ itemDataSource: DefaultData.bindingList.dataSource, itemTemplate: simple_ItemTemplate }"> </div>
И, конечно, в процессе загрузки сраницы вызывается WinJS.UI.processAll для создания экземпляра элемента управления. В параметрах FlipView мы можем сразу же увидеть два критически важных участка, благодаря которым он работает: это источник данных, который предоставляет содержимое, нужное каждому элементу, и шаблон для вывода элемента.
Если вы обратили внимание на конец "Элементы управления, их стилизация и привязка данных" , вы, возможно, догадываетесь, что шаблон - это экземпляр WinJS.Binding.Template. И вы правы! Эта часть разметки, на самом деле, расположена перед объявлением элемента управления в html/simpleFlipView.html.
<div id="simple_ItemTemplate" data-win-control="WinJS.Binding.Template" style="display: none"> <div class="overlaidItemTemplate"> <img class="image" data-win-bind="src: picture; alt: title" /> <div class="overlay"> <h2 class="ItemTitle" data-win-bind="innerText: title"></h2> </div> </div> </div>
Обратите внимание на то, что шаблон должен быть всегда объявлен в разметке до элемента управления, который на него ссылается: WinJS.UI.processAll должен создать экземпляр шаблона прежде чем элемент управления запросит шаблон для вывода своего содержимого для каждого элемента источника данных. Так же вспомните, из "Элементы управления, их стилизация и привязка данных" , что создание экземпляра шаблона убирает его содержимое из DOM, и, таким образом, оно не может быть изменено во время выполнения программы. Вы можете видеть это, исполняя пример: разверните узлы в Проводнике DOM (DOM Explorer) в Visual Studio, или в панели Динамическая DOM (Live DOM) в Blend, и вы увидите лишь корневой элемент шаблона div, который не имеет элементов-потомков.
В примере, прозаически названный ItemTemplate, шаблон создан из элемента img и еще одного div, который содержит h2. Класс overlay в этом div, если вы присмотритесь к Рис. 5.1, стилизован с использованием частично прозрачного фонового цвета (смотрите css/default.css на предмет селектора .overlaidItemTemplate.overlay). Это свидетельствует о том, что вы можете использовать в шаблоне любые элементы, в том числе - другие элементы управления WinJS. В последнем случае, они собираются, когда WinJS.UI.process/ processAll выполняется над шаблоном1Обратите внимание на то, что для того, чтобы подобные элементы управления были полностью интерактивными, присвойте им класс win-intaractive, иначе окружающий их элемент управления (это применимо и для ListView) поглотит события ввода до того, как они достигнут данных элементов управления..
Кроме того, вы увидите, что шаблон использует атрибуты привязки данных WinJS, где свойства img.src, img.alt и h2.innerText привязаны к свойствам источника данных, которые называются picture и title. Это показывает, как свойства двух целевых элементов могут быть привязаны к одному источнику. (Помните, что если вы осуществляете привязку к свойствам элемента управления WinJS, а не к его элементам-потомкам, эти свойства должны начинаться с winControl).
Что касается источника данных, то параметру itemDataSource элемента управления FlipView присвоено значение DefaultData.bindingList.dataSource, описание источника вы можете найти в js/DefaultData.js:
var array = [ { type: "item", title: "Cliff", picture: "images/Cliff.jpg" }, { type: "item", title: "Grapes", picture: "images/Grapes.jpg" }, { type: "item", title: "Rainier", picture: "images/Rainier.jpg" }, { type: "item", title: "Sunset", picture: "images/Sunset.jpg" }, { type: "item", title: "Valley", picture: "images/Valley.jpg" } ]; var bindingList = new WinJS.Binding.List(array); WinJS.Namespace.define("DefaultData", { bindingList: bindingList, array: array });
Мы кратко ознакомились с WinJS.Binding.List в конце "Элементы управления, их стилизация и привязка данных" . Его цель заключается в том, чтобы превратить массив, хранящийся в памяти, в наблюдаемый источник данных для односторонней привязки данных. Контейнер WinJS.Binding.List, кроме того, необходим, так как FlipView и ListView не могут работать напрямую с простыми массивами, даже при единовременной привязке данных. Они ожидают от источников данных наличия у них методов интерфейса WinJS.UI.IListDataSource. Свойство dataSource WinJS.Binding.List, как в bindingList.dataSource, предоставляет то же самое, и вы всегда будете использовать это свойство вместе с FlipView и ListView (Оно, на самом деле, существует лишь для этого). Если вы об этом забудете и попытаетесь осуществить прямую привязку к WinJS.Binding.List, вы увидите исключение, в котором говорится о том, что "Объект не поддерживает свойство или метод 'createListBinding'" ("Object doesn't support property or method 'createListBinding'.")
Достаточно сказать, что WinJS.Binding.List станет вашим близким другом для работы с источниками данных в памяти. Конечно, вы не будете всегда использовать данные, заданные в коде, как в примере. Вместо этого вы будете загружать массивы данных из файла, или получать из веб-сервисов, а WinJS.Binding.List сделает эти данные доступными для элементов управления, работающих с коллекциями.
Отметим, что WinJS.Binding.List полностью поддерживает динамические данные. Если вы посмотрите на его описание (http://msdn.microsoft.com/library/windows/apps/hh700774.aspx) в документации, вы увидите, что он выглядит практически так же, как JavaScript-массив, со свойством length и полным набором методов массивов - от concat и indexOf до push, pop и unshift. Они устроенны именно так, как можно этого ожидать: от вас не требуется повторное изучение основ.
Кроме того, важно отметить, что и для FlipView, и для ListView, подобная установка свойства элемента управления itemDataSource автоматически создаёт одностороннюю привязку данных, в итоге, любое изменение в объекте-списке, или даже в массиве, на основании которого он построен, запустит автоматическое обновление связанного элемента управления.
Быстрый старт №2a: основные возможности HTML ListView
Как я уже говорил раньше, основные механизмы, касающиеся источников данных и шаблонов, применимые к ListView, это те же, что применимы к FlipView. Вы можете увидеть всё это в примере "Основные возможности HTML ListView" (http://code.msdn.microsoft.com/windowsapps/ListView-basic-usage-sample-fcc451db), Рис. 5.2. Обратите внимание на первых два сценария, которые касаются создания элемента управления и ответа на события отдельных элементов, которые он отображает.
Так как ListView способен одновременно отображать множество элементов, ему нужно еще кое-что, в дополнение к источнику данных и шаблону. Что-то, что описывало бы, как эти элементы визуально соотносятся друг с другом. Это - свойство layout элемента управления ListView, которое мы увидим в разметке, в Сценарии 1 данного примера, вместе с некоторыми другими параметрами поведения (html/scenario1.html).
<div id="listView" data-win-control="WinJS.UI.ListView" data-win-options="{ itemDataSource: myData.dataSource, itemTemplate: select('#smallListIconTextTemplate'), selectionMode: 'none', tapBehavior: 'none', swipeBehavior: 'none', layout: { type: WinJS.UI.GridLayout } }"> </div>
Конструктор ListView, WinJS.UI.ListView, конечно, вызывается вездесущим WinJS.UI.processAll, когда загружен элемент управления страницей. Источник данных для данного списка установлен в myData.dataSource, где myData - это, опять же, WinJS.Binding.List (определенный в js/data.js, на основе обычного массива) и его свойство dataSource обеспечивает необходимый интерфейс.
Шаблон отдельного элемента определен ранее в default.html, с параметром id, равным smallListIconTextTemplate, и это, в основном, то же самое, что мы видим в FlipView (img и текстовые элементы), поэтому я не привожу здесь их описание.
В параметрах элемента управления мы видим три свойства, влияющих на поведение элемента: selectionMode, tapBehavior и swipeBegavior. Все они в этом примере установлены в 'none' для того, чтобы полностью отключить возможность выделения элементов и щелчков мышью по ним, превращая ListView в средство пассивного отображения объектов. Содержимое в нём можно перематывать, но элементы не отвечают на ввод. (Смотрите, кроме того, врезку "Стилизация элементов при зависании над ними указателя мыши")
Что касается свойства layout, то это объект, свойство type которого показывает, какой макет использовать. WinJS.UI.GridLayout, который используется здесь, отображает элементы сверху вниз и слева направо, что подходит для горизонтальной прокрутки. WinJS предоставляет и другой тип макета, который называется WinJS.UI.ListLayout. Это одномерный список, размещающий элементы сверху вниз, который подходит для вертикальной прокрутки, особенно - в прикрепленном режиме просмотра. (Мы скоро увидим это при рассмотрении шаблона Приложение таблицы. Пример ListView, который мы здесь рассматриваем, не имеет хорошего варианта для прикрепленного режима просмотра).
Сейчас элемент управления ListView в Сценарии 1 лишь отображает элементы, часто нужно, чтобы они реагировали на щелчок мыши или прикосновения. Сценарий 2 показывает это, здесь свойство tapBehavior установлено в 'invoke' (смотрите html/scenario2.htm). Это равносильно использованию WinJS.UI.tapBehaviortoggleSelect, как показано в справке по перечислению tapBehavior (http://msdn.microsoft.com/library/windows/apps/hh701303.aspx) для "invoke". Данный вариант поведения позволяет выделить элемент или снять с него выделение в зависимости от его состояния, и затем активирует его. Другой вариант - это directSelect, где элемент всегда выделен, и затем активируется, при этом invokeOnly исполняется лишь тогда, когда элемент активирован без изменения состояния выделения. Кроме того, вы можете установить параметры поведения элемента в none, в итоге прикосновение или щелчок мышью будут проигнорированы.
Когда отображаемый элемент активируется, элемент управления ListView запускает событие itemInvoked. Вы можете подключить обработчик события, используя либо addEventListener, либо свойство oniteminvoked элемента ListView. Вот как это выполняется в Сценарии 2 (слегка реорганизованный код из js/scenario2.js):
var listView = element.querySelector('#listView').winControl; listView.addEventListener("iteminvoked", itemInvokedHandler, false); }
Обратите внимание на то, что мы прослушиваем событие элемента управления WinJS, однако, это позволяет нам прослушивать и событие элемента, содержащегося в нём, благодаря механизму восходящей маршрутизации событий. Это может оказать помощь в том случае, если вам нужно добавить прослушиватели к элементу управления, экземпляр которого пока не создан, в то время, как содержащий его элемент уже находится в DOM.
В вышеприведенном коде, вы можете так же назначить обработчик, используя напрямую свойство listView.oniteminvoked , или вы можете задать обработчик в свойстве itemInvoked в data-win-options (в таком случае функция должна быть отмечена безопасной для обработки). Объект события, который вы затем получите в обработчике, содержит promise-объект для активированного элемента, а не сам элемент, поэтому вам нужно вызвать его метод done или then для того, чтобы получить данные элемента. Кроме того, полезно знать, что вам никогда не следует менять свойство источника данных ListView напрямую, внутри обработчика itemInvoked, так как это, возможно, вызовет исключение. Если вам нужно это сделать, заключите код, вносящий изменения, в setImmediate, так вы сможете вернуться в поток пользовательского интерфейса.
Врезка: стилизация элементов при зависании над ними указателя мыши
В то время, как отключение возможности выделения и реакции на прикосновения в ListView приводит к созданию пассивного элемента управления, зависание над элементом указателя мыши (или соответствующее событие на подходящем для этого сенсорном оборудовании), приводит к подсветке каждого элемента. Вернитесь к Рис. 5.2. Вы можете управлять этим, используя псевдо-селектор .win-container:hover для элементов управления. Например. следующее правило стиля полностью убирает эффект, проявляющийся при зависании указателя мыши:
#myListView .win-container:hover { background-color: transparent; outline: 0px; }