Создание настраиваемого элемента управления с помощью библиотеки Windows для JavaScript (WinJS)
Если вы разрабатывали приложения для Магазина Windows с использованием JavaScript, скорее всего вы познакомились с библиотекой Windows для JavaScript (WinJS). Данная библиотека предоставляет набор стилей CSS, элементов управления JavaScript и вспомогательных программ для быстрого создания приложений, соответствующих рекомендациям по взаимодействию с пользователем для Магазина Windows. WinJS также предоставляет набор функций, которые можно использовать для создания настраиваемых элементов управления в приложении.
Вы можете создавать элементы управления JavaScript с использованием любых шаблонов или библиотек. Функции в библиотеке WinJS — всего лишь один из вариантов. Основным преимуществом использования WinJS для создания элементов управления является то, что эта библиотека позволяет создавать собственные элементы управления, согласованно работающие с другими элементами управления в библиотеке. Принципы разработки собственного элемента управления и работы с ним — такие же, как и для любого другого элемента управления в пространстве имен WinJS.UI.
В этой записи я покажу, как создавать собственные элементы управления с поддержкой настраиваемых параметров, событий и общих методов. Если кого-то из вас интересует то же самое, но для XAML, вскоре выйдет запись блога, посвященная этому вопросу.
Добавление элемента управления на основе JavaScript на HTML-страницу
Сначала вспомним, как добавить элемент управления WinJS на страницу. Для этого существует два различных способа: императивный (используя только JavaScript естественным образом) или декларативный (включая элементы управления на HTML-страницу с использованием дополнительных атрибутов в HTML-элементах). Последний способ позволяет средствам предоставлять функции, доступные во время разработки, такие как перетаскивание элементов управления из набора инструментов. Подробную информацию можно найти в кратком руководстве по добавлению элементов управления и стилей WinJS в MSDN.
В этой статье я покажу, как создать элемент управления JavaScript с использованием преимуществ декларативной модели обработки в WinJS. Для декларативного добавления элемента управления на страницу, выполните следующие действия.
Добавьте ссылки на WinJS на HTML-страницу, так как ваш элемент управления будет использовать API-интерфейсы из этих файлов.
<script src="//Microsoft.WinJS.1.0/js/base.js"></script> <script src="//Microsoft.WinJS.1.0/js/ui.js"></script>
После ссылок тега скрипта (приведенных выше) добавьте ссылку на файл скрипта с вашим элементом управления.
<script src="js/hello-world-control.js"></script>
Вызовите функцию WinJS.UI.processAll() в коде JavaScript приложения. Эта функция обрабатывает HTML и создает экземпляры всех найденных декларативных элементов управления. Если вы используете шаблоны приложений в Visual Studio, WinJS.UI.processAll() вызывается в файле default.js автоматически.
Добавьте элемент управления на страницу декларативно.
<div data-win-control="Contoso.UI.HelloWorld" data-win-options="{blink: true}"></div>
Простой элемент управления JavaScript
Теперь мы создадим очень простой элемент управления: что-то вроде "Здравствуй, мир!" среди элементов управления. Вот код JavaScript, используемый для определения этого элемента управления. Создайте файл в проекте с именем hello-world-control.js с помощью следующего кода:
function HelloWorld(element) { if (element) { element.textContent = "Hello, World!"; } }; WinJS.Utilities.markSupportedForProcessing(HelloWorld);
Затем в теле страницы добавьте элемент управления с помощью следующей разметки:
<div data-win-control="HelloWorld"></div>
После запуска приложения вы увидите, как загрузится элемент управления и в теле страницы появится текст "Hello, World!" (Здравствуй, мир!).
Единственная часть кода, относящаяся к WinJS — это вызов функции WinJS.Utilities.markSupportedForProcessing, отмечающей код как совместимый для использования с декларативной обработкой. Таким образом вы сообщаете библиотеке WinJS, что вы разрешаете этому коду вставить контент на страницу. Дополнительную информацию об этом можно найти в документации MSDN для функции WinJS.Utilities.markSupportedForProcessing.
Зачем использовать вспомогательные программы WinJS или другую библиотеку для создания элемента управления?
Я только что показал вам, как создать декларативный элемент управления без WinJS. Теперь рассмотрим следующий фрагмент кода, который также не использует WinJS в большей части своей реализации. Это более сложный элемент управления с событиями, настраиваемыми параметрами и общими методами:
(function (Contoso) { Contoso.UI = Contoso.UI || {}; Contoso.UI.HelloWorld = function (element, options) { this.element = element; this.element.winControl = this; this.blink = (options && options.blink) ? true : false; this._onblink = null; this._blinking = 0; element.textContent = "Hello, World!"; }; var proto = Contoso.UI.HelloWorld.prototype; proto.doBlink = function () { var customEvent = document.createEvent("Event"); customEvent.initEvent("blink", false, false); if (this.element.style.display === "none") { this.element.style.display = "block"; } else { this.element.style.display = "none"; } this.element.dispatchEvent(customEvent); }; proto.addEventListener = function (type, listener, useCapture) { this.element.addEventListener(type, listener, useCapture); }; proto.removeEventListener = function (type, listener, useCapture) { this.element.removeEventListener(type, listener, useCapture); }; Object.defineProperties(proto, { blink: { get: function () { return this._blink; }, set: function (value) { if (this._blinking) { clearInterval(this._blinking); this._blinking = 0; } this._blink = value; if (this._blink) { this._blinking = setInterval(this.doBlink.bind(this), 500); } }, enumerable: true, configurable: true }, onblink: { get: function () { return this._onblink; }, set: function (eventHandler) { if (this._onblink) { this.removeEventListener("blink", this._onblink); this._onblink = null; } this._onblink = eventHandler; this.addEventListener("blink", this._onblink); } } }); WinJS.Utilities.markSupportedForProcessing(Contoso.UI.HelloWorld); })(window.Contoso = window.Contoso || {});
Многие разработчики создают элементы управления таким образом (с использованием анонимных функций, функций конструктора, свойств и настраиваемых событий), и если это подходит вам и вашей группе, то используйте этот способ. Но для многих разработчиков этот код может казаться немного запутанным. Многие веб-разработчики не знакомы с использованными здесь способами. В этом случае могут пригодиться библиотеки — они помогают избавиться от путаницы, связанной с написанием этого кода.
Помимо улучшения читаемости, WinJS и другие библиотеки решают многие другие небольшие проблемы, чтобы вам не нужно было о них волноваться (эффективное использование прототипов, свойств и настраиваемых событий). Они оптимизируют использование памяти и помогают устранить распространенные ошибки. WinJS — всего лишь один пример, выбор за вами. В качестве конкретного примера получения пользы от библиотеки я рекомендую еще раз изучить код из этого раздела после завершения чтения статьи и сравнить предыдущую реализацию с тем же элементом управления, реализованным в конце статьи с помощью WinJS.
Базовый шаблон для элементов управления JavaScript в WinJS
Далее представлен минимальный рекомендуемый шаблон для создания элемента управления JavaScript с помощью WinJS.
(function () { "use strict"; var controlClass = WinJS.Class.define( function Control_ctor(element) { this.element = element || document.createElement("div"); this.element.winControl = this; this.element.textContent = "Hello, World!" }); WinJS.Namespace.define("Contoso.UI", { HelloWorld: controlClass }); })();
Добавьте элемент управления на страницу декларативно:
<div data-win-control="Contoso.UI.HelloWorld"></div>
Часть кода может быть вам не знакома, особенно если вы в первый раз работаете с WinJS, поэтому рассмотрим этот пример более подробно.
Код в этом примере заключен в популярный шаблон JavaScript, известный как немедленно выполняемая функция
(function () { … })();
Это необходимо для того, чтобы код был автономным и не оставлял после себя ненужных переменных и глобальных назначений. Следует отметить, что это рекомендуемый шаблон, представляющий собой изменение, которое мы бы хотели внести в источник.
Режим Strict ECMAScript 5 включается при использовании оператора "use strict" в начале функции. Это является рекомендацией для шаблонов приложений Магазина Windows для улучшения проверки ошибок и совместимости с будущими версиями JavaScript. Опять же, это рекомендуемый способ, и мы бы хотели внести это изменение в источник.
Теперь займемся кодом, связанным с WinJS. Функция WinJS.Class.define() вызывается для создания класса для элемента управления, который, помимо прочего, обрабатывает вызов markSupportedForProcessing() и упрощает будущее создание элемента управления. На самом деле это простая вспомогательная функция на основе стандартной функции Object.defineProperties.
Определяется конструктор с именем Control_ctor. При вызове функции WinJS.UI.processAll() из файла default.js она проверяет разметку страницы на наличие элементов управления с помощью атрибута data-win-control, находит наш элемент управления и вызывает этот конструктор.
В конструкторе хранится ссылка на элемент на странице с объектом элемента управления, вместе с этим элементом хранится ссылка на объект элемента управления.
- Если вы гадаете, для чего нужна строка element || document.createElement("div") — она используется для поддержки императивной модели. Это позволяет пользователю подключить элемент управления к элементу на странице в дальнейшем.
- Это хорошая идея — хранить ссылку на элемент на странице таким образом, а также хранить в этом элементе ссылку на объект элемента управления, задав element.winControl. Например, при добавлении событий это позволяет некоторым функциям библиотеки просто работать. Не волнуйтесь об утечках памяти в результате циклической ссылки на объект или элемент DOM, Internet Explorer 10 позаботится о них.
- Конструктор изменяет текст элемента управления на "Hello, World!" (Здравствуй, мир!), который вы видите на экране.
Наконец, функция WinJS.Namespace.define() используется для публикации класса элемента управления и предоставления доступа к нему любому коду в приложении. Без этого нам пришлось бы искать другое решение для предоставления доступа к нашему элементу управления с использованием глобального пространства имен для кода за пределами встроенной функции, в которой мы работаем.
Определение параметров элемента управления
Чтобы сделать пример более интересным, добавим поддержку настраиваемых параметров в наш элемент управления. В этом случае мы добавим для пользователя возможность включения мигания содержимого.
var controlClass = WinJS.Class.define( function Control_ctor(element, options) { this.element = element || document.createElement("div"); this.element.winControl = this; // Set option defaults this._blink = false; // Set user-defined options WinJS.UI.setOptions(this, options); element.textContent = "Hello, World!" }, { _blinking: 0, blink: { get: function () { return this._blink; }, set: function (value) { if (this._blinking) { clearInterval(this._blinking); this._blinking = 0; } this._blink = value; if (this._blink) { this._blinking = setInterval(this._doBlink.bind(this), 500); } } }, _doBlink: function () { if (this.element.style.display === "none") { this.element.style.display = "block"; } else { this.element.style.display = "none"; } }, }); WinJS.Namespace.define("Contoso.UI", { HelloWorld: controlClass });
На этот раз при добавлении элемента управления на страницу вы можете настроить параметр мигания с помощью атрибута data-win-options:
<div data-win-control="Contoso.UI.HelloWorld" data-win-options="{blink: true}"> </div>
Чтобы добавить поддержку параметров, мы внесли следующие изменения в код:
- Параметры передаются элементу управления через параметр (options) в функции конструктора.
- Параметры, используемые по умолчанию, настраиваются с помощью закрытых свойств класса.
- Вызывается функция WinJS.UI.setOptions() , в нее передается ваш объект элемента управления. Этот вызов переопределяет значения по умолчанию для настраиваемых параметров элемента управления.
- Добавляется открытое свойство (blink) для нового параметра.
- Мы добавили возможность мигания текста на экране (на практике лучше не кодировать стили таким образом, а использовать свойства класса CSS).
В этом примере часть кода, выполняющая сложные операции, — это вызов функции WinJS.UI.setOptions(). Служебная функция setOptions проходит по каждому полю в объекте options и назначает его значение полю с таким же именем в целевом объекте — первом параметре setOptions.
В нашем примере мы настраиваем объект options с помощью аргумента data-win-options для элемента управления win-control, передавая значение true для поля "blink". Теперь функция setOptions() в конструкторе увидит поле с именем "blink" и скопирует его значение в поле с таким же именем в объекте control. Мы определили свойство с именем blink, предоставляющее функцию настройки. Наша функция настройки — та, которую вызывает функция setOptions(), после чего устанавливается член _blink нашего элемента управления.
Добавление поддержки событий
После реализации параметра blink добавим поддержку события, чтобы мы могли реагировать на любое мигание:
var controlClass = WinJS.Class.define( function Control_ctor(element, options) { this.element = element || document.createElement("div"); this.element.winControl = this; // Set option defaults this._blink = false; // Set user-defined options WinJS.UI.setOptions(this, options); element.textContent = "Hello, World!" }, { _blinking: 0, _blinkCount: 0, blink: { get: function () { return this._blink; }, set: function (value) { if (this._blinking) { clearInterval(this._blinking); this._blinking = 0; } this._blink = value; if (this._blink) { this._blinking = setInterval(this._doBlink.bind(this), 500); } } }, _doBlink: function () { if (this.element.style.display === "none") { this.element.style.display = "block"; } else { this.element.style.display = "none"; } this._blinkCount++; this.dispatchEvent("blink", { count: this._blinkCount }); }, }); WinJS.Namespace.define("Contoso.UI", { HelloWorld: controlClass }); // Set up event handlers for the control WinJS.Class.mix(Contoso.UI.HelloWorld, WinJS.Utilities.createEventProperties("blink"), WinJS.UI.DOMEventMixin);
Добавьте элемент управления на страницу, как и ранее. Обратите внимание, что мы добавили идентификатор в элемент, чтобы мы могли получить этот элемент позднее:
<div id="hello-world-with-events" data-win-control="Contoso.UI.HelloWorld" data-win-options="{blink: true}"></div>
После этих изменений мы можем подключить прослушиватель события "blink". (Примечание. В этом примере я использовал оператор $ в качестве псевдонима для document.getElementById.)
$("hello-world-with-events").addEventListener("blink", function (event) { console.log("blinked element this many times: " + event.count); });
При выполнении этого кода вы увидите сообщение, которое каждые 500 мс записывается в окно консоли JS в Visual Studio.
Чтобы добавить поддержку этого поведения, мы внесли следующие изменения в элемент управления:
- Вызывается функция WinJS.Class.mix(Contoso.UI.HelloWorld, WinJS.Utilities.createEventProperties("blink")), создающая свойство "onblink", которое пользователи могут устанавливать программными средствами или с которым они могут декларативно связать HTML-страницу.
- Вызывается функция WinJS.Class.mix(Contoso.UI.HelloWorld, WinJS.UI.DOMEventMixin) , добавляющая функции addEventListener, removeEventListener и dispatchEvent в элемент управления.
- Инициируется событие мигания посредством вызова that.dispatchEvent("blink", {element: that.element}), после этого в поле element создается объект настраиваемого события.
- Подключается обработчик события blink. В ответ на событие он обращается к полю element объекта настраиваемого события.
Здесь я подчеркну, что вызовы dispatchEvent() работают, только если вы установили this.element в конструкторе элемента управления. Для события mix-in требуется доступ к данному элементу в DOM. Это один из тех случаев, о которых я упомянул ранее и в которых требуется член element для объекта элемента управления. Это позволяет передавать события родительским элементам на странице в шаблоне события DOM 3 уровня.
Предоставление доступа к общим методам
В качестве последнего изменения нашего элемента управления добавим общую функцию doBlink(), которую можно вызывать в любой момент для отображения мигания.
var controlClass = WinJS.Class.define( function Control_ctor(element, options) { this.element = element || document.createElement("div"); this.element.winControl = this; // Set option defaults this._blink = false; // Set user-defined options WinJS.UI.setOptions(this, options); element.textContent = "Hello, World!" }, { _blinking: 0, _blinkCount: 0, blink: { get: function () { return this._blink; }, set: function (value) { if (this._blinking) { clearInterval(this._blinking); this._blinking = 0; } this._blink = value; if (this._blink) { this._blinking = setInterval(this.doBlink.bind(this), 500); } } }, doBlink: function () { if (this.element.style.display === "none") { this.element.style.display = "block"; } else { this.element.style.display = "none"; } this._blinkCount++; this.dispatchEvent("blink", { count: this._blinkCount }); }, }); WinJS.Namespace.define("Contoso.UI", { HelloWorld: controlClass }); // Set up event handlers for the control WinJS.Class.mix(Contoso.UI.HelloWorld, WinJS.Utilities.createEventProperties("blink"), WinJS.UI.DOMEventMixin);
Это всего лишь изменение соглашения — мы можем изменить имя нашей функции _doBlink на doBlink.
Чтобы вызвать doBlink() с помощью JavaScript, вам потребуется ссылка на объект вашего элемента управления. Если вы создаете элемент управления императивно, у вас уже есть ссылка. Если вы используете декларативную обработку, вы можете получить доступ к объекту элемента управления с помощью свойства winControl в HTML-элементе вашего элемента управления. Например, с использованием той же разметки, что и ранее, вы можете получить доступ к элементу управления следующим образом:
$("hello-world-with-events").winControl.doBlink();
Объединение кода
Только что мы проработали основные аспекты элемента управления, которые вам потребуется реализовать:
- добавление элемента управления на страницу;
- передача параметров конфигурации;
- передача событий и реагирование на них;
- предоставление функциональной возможности через общие методы.
Надеюсь, что это руководство помогло вам понять, как создать простой элемент управления на основе JavaScript. Если у вас есть вопросы по работе с собственными элементами управления, посетите Центр разработки для Windows и задайте вопросы на форумах. Кроме того, для разработчиков на XAML вскоре будет опубликована запись с подробным описанием того же процесса разработки элемента управления на XAML.
Джордан Маттьесен (Jordan Matthiesen)
Руководитель программы, Microsoft Visual Studio