Создаем директиву AngularJS из существующих плагинов/скриптов

Концепции и практическое применение

| Категории: Javascript, AngularJS
Анна Аминева

Иллюстрация блокнота

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

Никакие DOM манипуляции не должны осуществляться внутри Контроллера, а Контроллер - это та часть приложения, в котором случается большая часть “магии” ангуляра, канал коммуникации между данными Моделей и браузером. Может быть привлекательно просто засунуть ваш скрипт внутрь (и он будет отлично работать), но это против принципов Ангуляра.

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

Директивы - это ответ AngularJS “Теневому DOM” Веб Компонентов, который совместим со всеми браузерами (а не только с передовыми, в которых есть HTML5), приносящий вам силу будущей технологии сегодня. Shadow DOM внедряет новый контент в зависимости от элемента к которому он прикручен, обладает собственной областью видимости и стилями CSS. Добавляем неописуемо крутые механизмы поведения и это ровно то, что Директивы повторяют уже сейчас, чтобы сделать эту технологию доступной сегодня.

Demo jsFiddle
Определяем директиву:
Директивы очень просты в использовании после того, как вы их единожды настроите. Для целей этой демонстрации я собираюсь перенести fluidvids.js в Директиву AngularJS.

####Существующий код

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
window.fluidvids = (function (window, document, undefined) {
'use strict';
/*
* Constructor function
*/
var Fluidvids = function (elem) {
this.elem = elem;
};
/*
* Prototypal setup
*/
Fluidvids.prototype = {
init : function () {
var videoRatio = (this.elem.height / this.elem.width) * 100;
this.elem.style.position = 'absolute';
this.elem.style.top = '0';
this.elem.style.left = '0';
this.elem.width = '100%';
this.elem.height = '100%';
var wrap = document.createElement('div');
wrap.className = 'fluidvids';
wrap.style.width = '100%';
wrap.style.position = 'relative';
wrap.style.paddingTop = videoRatio + '%';
var thisParent = this.elem.parentNode;
thisParent.insertBefore(wrap, this.elem);
wrap.appendChild(this.elem);
}
};
/*
* Initiate the plugin
*/
var iframes = document.getElementsByTagName( 'iframe' );
for (var i = 0; i < iframes.length; i++) {
var players = /www.youtube.com|player.vimeo.com/;
if (iframes[i].src.search(players) > 0) {
new Fluidvids(iframes[i]).init();
}
}
})(window, document);

###Код директивы

Для начала вам нужно понять как переструктурировать ваш код вместо небольшого API для добавления скрипта к каждому элементу в вашем селекторе/плагине. Не забудьте применять ваши изменения к единственному элементу в области видимости, так как Директивы используются для повторяющихся компонентов и, следовательно, могут часто встречаться в коде, соответственно, они ссылаются сами на себя, а не на список элементов (NodeList) для итерации.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Module
var myApp = angular.module('myApp', []);
// FluidVids Directive
myApp.directive('fluidvids', function () {
return {
restrict: 'EA',
replace: true,
scope: {
video: '@'
},
template: '<div class="fluidvids">' +
'<iframe ng-src="{{ video }}"></iframe>' +
'</div>',
link: function (scope, element, attrs) {
var ratio = (attrs.height / attrs.width) * 100;
element[0].style.paddingTop = ratio + '%';
}
};
});

Я разберу написанное выше, для тех, кто заинтересован в этом. Директива возвращает Объект, в котором находится вся конфигурация, относительно данной конкретной директивы. Я использовал restrict со значением EA, то есть Элемент или Атрибут. Далее я использую replace, таким образом разметка, находящаяся в DOM заменяется кодом из директивы. Я использую scope как третье свойство, которое вы можете взять как имя вашего элемента. @ означает то, что я использую ее как строку, которую, как вы видите, я использовал в моем маленьком шаблоне, встраиваемом в DOM с меняющимся src тега video. Я так же применяю ng-src, который Angular рекомендует для правильной обработки динамически созданных src атрибутов (в основном для старых браузеров, конечно). После этого я создаю маленькую функцию link, которая определяет любые манипуляции с DOM, после того как шаблон был создан. Вы так же можете привязать любые события именно здесь.

###Перемещение стилей JavaScript в CSS

Применение Angular директивы позволяет мне меньше использовать индивидуальные DOM манипуляции, так как вместо объектов JS со стилями я определяю CSS:

1
2
3
4
5
6
7
8
9
10
11
12
.fluidvids {
width: 100%;
position: relative;
}
.fluidvids iframe {
border: 0;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}

Здесь нет соотношения сторон видео, так как этот параметр рассчитывается JS и добавляется в каждый индивидуальный элемент, что гарантирует каждому видео правильный показатель соотношения сторон, как и предполагается с любым контентом, внедряемым через iframe.

###Ошибки предрелизной версии при работе с кросс-доменным медиа контентом

Во время портирования FluidVids на AngularJS все очень просто тестировалось, наши руки не дошли до работы с RC версией angular.js (версия 1.2.0-rc.2). На самом деле они сделали это, чтобы помочь вам, но вам нужен внутренний белый список для работы с внешними доменами, с которых вы будете получать медиа контент. Мы получили следующую ошибку при смене версии с 1.0.8 на 1.2.0-rc.2

1
Error: [$interpolate:interr] http://errors.angularjs.org/undefined/$interpolate/interr?p0=%7B%7B%20src%2…%24sce%2Finsecurl%3Fp0%3D%252F%252Fplayer.vimeo.com%252Fvideo%252F23919731

После быстрого поиска в Google, мне понадобилось занести в белый список домены, которые я использую внутри AngularJS, в какой-то мере умный защитный ход (даже несмотря на слегка раздражающую ошибку) команд��. После некоторых исследований, я нашел, что кто-то написал следующий комментарий, в котором описан код добавления всех доменов в белый список:

1
2
3
myApp.config(function ($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist(['.*']);
});

###Пользовательские элементы или атрибуты (E или A, или оба!)

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

Пользовательский элемент:
<fluidvids video="//player.vimeo.com/video/23919731" height="281" width="500"></fluidvids>
As an attribute:

Как атрибут:
<div fluidvids video="//player.vimeo.com/video/23919731" height="281" width="500"></div>

В зависимости от вашей команды или ее композиции, может быть проще использование одного метода или другого. Они не так различны, но мне кажется, что Веб Компоненты предлагают на удивление наиболее понятную семантику, несмотря на их свободную разметку, которая позволяет разработчику решать что и как писать. Веб Компоненты так же вводят и пользовательские атрибуты- используйте их, но не злоупотребляйте! ;)