Опрос
Ждете ли вы выхода привычных ноутбуков на новой Windows 10X?

Начала Metro-программирования: создание компонентов WinRT (ч.3)

Напечатать страницу
28.12.2012 13:11 | dronov_va

5.5. Императивное создание компонента и обеспечение его поддержки

В параграфе 4.5, тестируя компонент, мы создавали его, объявив в HTML-коде. Вот так:

<div data-win-control="TheVista.UI.Spoiler">
  <div>Заголовок</div>
  <div>Содержимое</div>
</div>

div>[/code]
При обработке вызова метода processAll объекта WinJS.UI (он всегда ставится самым первым выражением в коде инициализации), встретив это объявление, WinRT сама создаст данный компонент и поместит его в указанном нами месте. Фактически мы сказали WinRT: "Создай мне в этом месте такой-то компонент". Подобный способ создания компонентов называется декларативным.

Однако мы имеем возможность создать компонент императивно, явно записав в коде логики команды по его созданию и выводу на экран. Например:

[code]var divSpoiler = document.getElementById("divSpoiler");
var splSpoiler = new TheVista.UI.Spoiler(divSpoiler);[/code]
Первые выражение получит блок divSpoiler, который станет элементом-основой для создаваемого компонента. (Этот блок нам таки придётся создать самостоятельно.) А второе выражение создаст на основе этого элемента компонент TheVista.UI.Spoiler.

В этом случае компонент будет успешно создан.

Однако разработчик может сначала создать компонент, не привязанный к какому-либо элементу-основе, и уже потом вывести его на экран:

[code]var splSpoiler = new TheVista.UI.Spoiler();
document.body.appendChild(splSpoiler.element);[/code]
Поскольку элемент-основа для создаваемого компонента отсутствует, его конструктор вызывается вообще без параметров (первое выражение). А второе выражение добавляет элемент-основу созданного компонента на экран.

Переменная document хранит экземпляр объекта HTMLDocument, представляющий интерфейс приложения. Свойство body этого объекта хранит экземпляр объекта HTMLBodyElement, представляющий видимую часть интерфейса приложения (содержимое тега <body>).

Все объекты, представляющие элементы интерфейса, в том числе и объекта HTMLBodyElement, поддерживают метод appendChild. Этот метод добавляет элемент интерфейса, переданный ему в качестве единственного параметра, в конец содержимое элемента, у которого был вызван.

Отметим, что метод appendChild требует передачи ему элемента интерфейса, а не компонента WinRT. Поэтому в качестве его параметра мы указали значение поля element компонента, которое, как мы давно знаем, хранит его элемент-основу...

Стоп! Но ведь у созданного таким образом компонента нет элемента-основы! Ведь при создании компонента мы не передали конструктору ни единого параметра.

Специально для таких случаев в коде конструктора компонента следует предусмотреть код, который будет создавать элемент-основу, если таковой не был передан конструктору. Давайте так и сделаем.

Найдём в теле конструктора вот это выражение (оно находится в самом его начале):

[code]this.element = element;[/code]
Заменим его таким выражением:

[code]this.element = element || document.createElement("div");[/code]
Если конструктору в качестве первого параметра (element) был передан элемент-основа, он будет присвоен полю element компонента. В противном случае этому полю будет присвоен вновь созданный блок.

Для создания блока мы используем метод createElement объекта HTMLDocument. Этот метод принимает в качестве единственного параметра строку с именем тега и возвращает элемент интерфейса, созданный на основе этого тега.

Теперь наш компонент можно будет создать программно.


5.6. Тестирование компонента
Настала пора проверить новую версию компонента раскрывающейся панели в деле.

Откроем файл default.html, где описывается интерфейс приложения, и немного изменим код внешнего блока. После этого он будет выглядеть так:

[code]<div id="spl1" data-win-control="TheVista.UI.Spoiler"
data-win-options="{isOpened: true, headerHTML: 'Свернуть'}">
. . .
</div>[/code]
Мы дали этому компоненту имя, для чего использовали атрибут тега id. Это нам понадобится, чтобы впоследствии привязать к его событиям обработчики. Ещё мы указали параметры компонента, а именно, его изначальное состояние (развёрнутое) и содержимое блока заголовка ("Свернуть").

Далее откроем файл default.js с логикой компонента. Сразу же объявим необходимые переменные:

[code]var spl1, spl2;[/code]
В этих переменных будут храниться оба компонента - и созданный декларативно, в HTML-коде, и тот, что мы создадим императивно, в коде логики.

Изменим код инициализации, чтобы он выглядел следующим образом:

[code]document.addEventListener("DOMContentLoaded", function() {
WinJS.UI.processAll().then(function() {
spl1 = document.getElementById("spl1").winControl;
spl1.addEventListener("aftertoggle", spl1AfterToggle);

spl2 = new TheVista.UI.Spoiler();
spl2.contentHTML = "Этот компонент был создан программно. А ещё он не сворачивается.";
spl2.addEventListener("beforetoggle", spl2BeforeToggle);
document.body.appendChild(spl2.element);
});
});[/code]
Сначала мы получаем доступ к компоненту, созданному декларативно, и привязываем к его событию aftertoggle обработчик. Далее мы императивно создаём ещё один такой же компонент, задаём для него содержимое, привязываем к его событию beforetoggle обработчик и помещаем на экран.

Напишем обработчик события aftertoggle первого компонента:

[code]function spl1AfterToggle() {
if (spl1.isOpened) {
spl1.headerHTML = "Свернуть";
} else {
spl1.headerHTML = "Развернуть";
}
}[/code]
Единственное, что он будет делать, - менять содержимое заголовка в зависимости от состояния компонента.

Осталось лишь записать обработчик события beforetoggle второго компонента:

[code]function spl2BeforeToggle(evt) {
if (!(evt.detail.isOpening)) {
evt.preventDefault();
}
}[/code]
Он будет проверять, должна ли панель в данный момент свернуться, и в таком случае отменять её поведение по умолчанию. В результате мы получим панель, которую можно развернуть, но свернуть уже не удастся.

Сохраним все исправленные файлы и запустим приложение на выполнение. И проверим оба компонента в работе.


6. Оформление компонентов
Последнее, что мы сделаем, - зададим для нашего компонента оформление. Ибо сейчас он выглядит слишком уж непрезентабельно...

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

Откроем её и введём стили, перечисленные далее.

[code].thevista-ui-spoiler {
width: 400px;
border: 3px solid white;
}[/code]
Этот стиль будет привязан к внешнему блоку. Он задаст для него ширину, равную 400 пикселам (атрибут стиля width), и сплошную белую рамку толщиной в три пиксела вокруг него (атрибут стиля border).

[code].thevista-ui-spoiler-header {
cursor: pointer;
}[/code]
Этот стиль будет привязан к блоку заголовка и укажет для него курсор мыши в форме "указующего перста" (атрибут стиля cursor).

[code].thevista-ui-spoiler-content {
border-top: 1px solid white;
}[/code]
Этот стиль будет привязан к блоку содержимого и задаст для него сплошную белую рамку толщиной в один пиксел вдоль верхней стороны (атрибут стиля border-top).

[code].thevista-ui-spoiler-hidden {
display: none;
}[/code]
А этот стиль будет привязываться к блоку содержимого, чтобы его скрыть.

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

Вставим в код конструктора следующие выражения (выделены комментариями):

[code]function (element, option) {
. . .

var cDivs = WinJS.Utilities.query("div", this.element);
this.header = cDivs.get(0);
this.content = cDivs.get(1);

//Вставленный код - начало

WinJS.Utilities.addClass(this.element, "thevista-ui-spoiler");
WinJS.Utilities.addClass(this.header, "thevista-ui-spoiler-header");
WinJS.Utilities.addClass(this.content, "thevista-ui-spoiler-content");

//Вставленный код - конец

this.headerHTML = this.header.innerHTML;
. . .
},[/code]
Для привязки стилевого класса к элементу интерфейса мы использовали метод addClass объекта WinJS.Utilities.

[code]WinJS.Utilities.addClass(
<элемент интерфейса, к которому нужно привязать стилевой класс>,
<имя привязываемого стилевого класса>
);[/code]
Первым параметром указывается сам элемент интерфейса, к которому требуется привязать стилевой класс. Имя этого стилевого класса задаётся вторым параметром в виде строки без символа точки.

И исправим код set-функции свойства isOpened, чтобы он выглядел так:

[code]set: function (value)
{
if (value != this._isOpened) {
if (!(this.dispatchEvent("beforetoggle", {isOpening: value}))) {
if (value) {
WinJS.Utilities.removeClass(this.content, "thevista-ui-spoiler-hidden");
WinJS.Utilities.setInnerHTML(this.indicator,
TheVista.UI.Spoiler.collapseIndicator);
} else {
WinJS.Utilities.addClass(this.content, "thevista-ui-spoiler-hidden");
WinJS.Utilities.setInnerHTML(this.indicator,
TheVista.UI.Spoiler.expandIndicator);
}
this._isOpened = value;
this.dispatchEvent("aftertoggle");
}
}
}[/code]
Здесь мы вместо того, чтобы указывать значение атрибута стиля display, управляющего выводом элемента на экран, непосредственно, привязываем к элементу стилевой класс thevista-ui-spoiler-hidden или, наоборот, выполняем его "отвязку". "Отвязать" стилевой класс от элемента можно с помощью метода removeClass объекта WinJS.Utilities, чей формат вызова такой же, как и у метода addClass.

Осталось только проверить компонент в работе. Откроем файл default.html и вставим тег, который выполнит привязку вновь созданной таблицы стилей (выделен комментариями):

[code]<link href="//Microsoft.WinJS.1.0.RC/css/ui-dark.css" rel="stylesheet">
<script src="//Microsoft.WinJS.1.0.RC/js/base.js"></script>
<script src="//Microsoft.WinJS.1.0.RC/js/ui.js"></script>

<!-- Вставленный код - начало -->

<link href="/css/component.css" rel="stylesheet">

<!-- Вставленный код - конец -->

<script src="/js/component.js"></script>

<link href="/css/default.css" rel="stylesheet" />
<script src="/js/default.js"></script>[/code]
Сохраним все исправленные файлы, запустим приложение и посмотрим, как теперь выглядит компонент.


Рис. 3. Окончательная версия раскрывающейся панели в свёрнутом виде


Рис. 4. Окончательная версия раскрывающейся панели в развёрнутом виде




7. Оформление кода компонентов
Код нашего совсем простого компонента хранится в двух файлах: файле сценариев и таблице стилей. Более сложные компоненты могут состоять из большего количества файлов, в состав которых входят графические файлы, файлы с какими-либо служебными данными и пр.

Как организовать эти файлы, чтобы, с одной стороны, не сваливать все в одну большую кучу, а с другой, не разбрасывать их по разным папкам?

Автор предлагает для размещения всех компонентов, что входят в состав приложения, использовать специально созданную папку с именем, скажем, components. Внутри этой папки создаются отдельные папки - каждая для отдельного компонента (папки компонентов). Имена этих папок должны совпадать с именами соответствующих им компонентов.

Все файлы сценариев с логикой компонентов помещаются внутри этих папок. Им можно дать как имена, совпадающие с именами компонентов, так и имена вида main.js, code.js и т. п.

Для размещения таблиц стилей можно использовать папку с именем css, находящуюся в папке компонента. Если компонент включает одну таблицу стилей, ей можно дать имя вида styles.css, main.css или default.css. Если же компонент включает несколько таблиц стилей (например, задающие несколько вариантов оформления), им можно дать значимые имена: dark.css, light.css и пр.

Файлы с графическими изображениями следует поместить в отдельную папку, вложенную в папку компонента; эта папка может иметь имя images или imgs. Для размещения прочих файлов можно использовать папку с именем others.

Такой подход в организации файлов имеет огромное преимущество - если мы захотим передать компонент другому разработчику, нам достаточно лишь скопировать ему папку компонента со всем её содержимым. Существенных недостатков же у него нет.

Далее приведена структура папок для размещения файлов нашего компонента. Имена папок набраны прописными буквами, имена файлов - строчными. Уровень вложенности показывается с помощью отступов.

[code]<папка проекта приложения>
. . .
COMPONENTS
THEVISTA.UI.SPOILER
code.js
CSS
default.css
wide.css[/code]
Здесь файл code.js хранит весь код логики компонента, файл default.css - таблицу стилей, задающую оформление по умолчанию, а файл wide.css - гипотетическую таблицу стилей, задающую "широкий" вариант оформления компонента.

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

[code]<link href="/components/thevista.ui.spoiler/css/default.css" rel="stylesheet">
<script src="/components/thevista.ui.spoiler/code.js"></script>[/code]
Вот теперь наш компонент не просто полностью создан, но и готов к распространению!


8. Дополнительные инструменты для создания компонентов
Здесь мы рассмотрим пару дополнительных инструментов, предлагаемых WinRT для создания компонентов. Они могут нам пригодиться.


8.1. Обеспечение поддержки событий DOM
Мы уже знаем, как дать компоненту поддержку событий. Для этого достаточно добавить к компоненту микс WinJS.Utilities.eventMixin и внутренние свойства, соответствующие создаваемым событиям, и в нужных местах кода инициировать возникновение нужного события и выполнение его обработчиков.

Однако рассмотренный нами подход не будет работать, как положено, в следующих случаях:

  • если мы захотим, чтобы компонент поддерживал события DOM (Document Object Model, объектной модели документа), такие, как click, mouseover, mousemove, mouseout и др.;
  • если мы захотим обрабатывать в элементе-основе все события DOM, что возникают в элементах, составляющих наш компонент.


Дабы обеспечить нормальную поддержку событий DOM, нам потребуется сделать две вещи.

Во-первых, мы создадим в компоненте поле _domElement и присвоим ему элемент-основу данного компонента. Далее приведён фрагмент исправленного кода конструктора нашего компонента; добавленное выражение, создающее это поле, выделено комментариями.

[code]function (element, option) {
this.element = element || document.createElement("div");
this.element.winControl = this;

//Вставленный код - начало

this._domElement = this.element;

//Вставленный код - конец

. . .
},[/code]
Во-вторых, мы добавим компоненту вместо микса WinJS.Utilities.eventMixin микс WinJS.UI.DOMEventMixin. Ниже приведён исправленный фрагмент кода нашего компонента, который это делает.

[code]WinJS.Class.mix(spoilerClass,
WinJS.UI.DOMEventMixin,
WinJS.Utilities.createEventProperties("beforetoggle", "aftertoggle")
);[/code]
Теперь мы можем привязать к компоненту обработчик любого события DOM, и оно будет успешно обработано WinRT.

[code]var divSpn1 = document.getElementById("spn1");
divSpn1.addEventListener("mouseover", function() { . . . });[/code]

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

В WinRT создать объект, наследующий от другого объекта, очень просто. Достаточно использовать метод derive объекта WinJS.Class.

[code]WinJS.Class.derive(
<объекта-родитель>,
<функция-конструктор>[,
<элементы экземпляра>[,
<элементы объекта>]]
)[/code]
Первым параметром этому методу передаётся объект, который станет родителем для создаваемого. Остальные параметры уже знакомы нам по методу define (параграф 4.1).

Метод derive также возвращает полностью сформированный объект JavaScript, который мы присвоим любой переменной.

[code]var enhancedSpoilerClass = WinJS.Class.derive(spoilerClass,
function (element, options) {
. . .
},
{
. . .
},
{
. . .
}
);[/code]
Здесь мы создаём объект раскрывающейся панели с расширенной функциональностью, порождённый от созданной нами ранее "обычной" раскрывающейся панели.

Часто возникает необходимость в коде конструктора объекта-потомка вызвать конструктор родителя. Для этого мы можем использовать метод call объекта-родителя, передав ему в качестве первого параметра переменную this, а в качестве остальных параметров - параметры вызываемого конструктора. Вот так:

[code]var enhancedSpoilerClass = WinJS.Class.derive(spoilerClass,
function (element, options) {
spoilerClass.call(this, element, options);
. . .
},
. . .
);[/code]
Также часто бывает нужно в коде метода объекта-потомка вызывать одноимённый метод объекта-родителя. Доступ к этим методам мы можем получить через свойство _super, которое автоматически создаётся самой WinRT при объявлении объекта-потомка.

[code]expand: function ()
{
this._super.expand();
. . .
},[/code]
Здесь мы в коде метода expand объекта-потомка вызываем одноимённый метод объекта-родителя.


9. Заключение
В этой статье мы изучили создание простейшего компонента WinRT. Как выяснилось, это сделать достаточно просто; и это несмотря на то, что сама Microsoft рекомендует для создания компонентов использовать технологии XAML/C# и DirectX/C++.

Дополнительная литература



dronov_va, TheVista.Ru Team
Декабрь 2012

Комментарии

Комментариев нет...
Для возможности комментировать войдите в 1 клик через

По теме

Акции MSFT
210.33 0.00
Акции торгуются с 17:30 до 00:00 по Москве
Все права принадлежат © ms insider @thevista.ru, 2020
Сайт является источником уникальной информации о семействе операционных систем Windows и других продуктах Microsoft. Перепечатка материалов возможна только с разрешения редакции.
Работает на WMS 2.34 (Страница создана за 0.048 секунд (Общее время SQL: 0.017 секунд - SQL запросов: 45 - Среднее время SQL: 0.00038 секунд))
Top.Mail.Ru