- PVSM.RU - https://www.pvsm.ru -

CornerJS, или директивы «как в AngularJS», только лучше

image

Постоянно создавая сложные веб-проекты с нуля, начинаешь замечать, что примерно треть — а в некоторых случаях половина и больше — кода в действительности автономна, и привязана только к определенному DOM-элементу.

В рабочих проектах это может сводиться к чему-то вроде

function pageChange(){
    if ($(‘.element-carousel’).length>0) {$(‘.element-carousel’).initCarousel()}
    if ($(‘.element-scrollbox’).length>0) {$(‘.element-scrollbox’).initScrollbox()}
…

А может и не сводиться, и в каждом условном контроллере (колбэке на смену определенной страницы) мы вызываем код, связанный с определенными элементами.

Знакомо? Думаю, да. Считаете ли вы этот подход неправильным? Если первый ответ – да, то уверен, что и второй тоже да.

Хотите узнать, как можно сделать правильно, аккуратно и красиво?

Директивы

В AngularJS есть, как они пишут, уникальная фича – директивы. Но теперь она уже на самом деле не уникальна.
Неплохая статья про них уже была на хабре [1].
Не стану пересказывать все содержимое статьи, повторю всего одну фразу:

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

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

Как это работает?

Реализация работает на MutationObserver (для тех, кто не в курсе — родные события браузера на изменение DOM-дерева) с полифиллом, работающим на mutation events (DOMSubtreeModified и ему подобные). Полифилл реально нужен только для IE, так как все остальные десктопные браузеры уже поддерживают родной MutationObserver.
К сожалению, даже с полифиллом не поддерживается родной браузер android 2.3, что действительно печально, однако в 4.0 стабильно проходятся все тесты.
Теоретически «ручная» — с вызовом по необходимости проверки на обновление директив – поддержка директив возможна в практически любом мобильном и десктопном браузере, начиная с IE6.

Начало

Первоначально был определен синтаксис:

directive(‘name’, function(node){
    alert(«i’m alive!»)
}) 

Вначале предполагалось сделать целевой элемент this, но из-за большого количества колбэков (перед которыми каждый раз приходилось писать var _this = this) решено было вынести его в первый аргумент.
Дальше синтаксис был расширен до еще одного варианта:

directive(‘name’, {
    load: function(node){},
    unload: function(node){}
})

Событие load связано с появлением директивы(не элемента), unload соответствует ее удалению.
Почему именно с появлением директивы? Потому что их можно как прописывать изначально, так и добавлять и удалять в процессе работы с элементом. Простой пример — эта директива будет вызвана, если элемент изменится с

<div class="container something">

на

<div class="container test something">
Полезный this

Чтобы колбэки могли передавать друг другу значения – они получили общий this. Так что вполне возможно делать так:

directive(‘name’, {
    load: function(){
         this.interval = setInterval(function(){}, 1000)
    },
    unload: function(){
        clearInterval(this.interval)
    }
})

В некоторых случаях удобно создавать внешние методы для взаимодействия с содержимым: например, презентация с возможностью перехода. Для этого доступен сам скоп – он является одним из атрибутов ноды — так, для директивы test это будет node.directiveTest.
В итоге создание публичных методов для директивы становится простым и удобным:

directive(‘name’, function(){
    this.publicMethod = function(a){alert(a)}
})
Синтаксис в HTML

В качестве целей для директив были заданы классы, атрибуты и тэги, и с самого начала предполагалась возможность использования префикса ‘data-‘(на самом деле в настоящий момент конфигураторе по умолчанию задан еще один префикс — ‘directive-‘. Это сделано для читаемости директивных классов: div class=«directive-scrollbox» куда понятнее чем div class=«scrollbox»).
Соответственно такая директива будет выполнена при следующих сценариях:

<div class=«name»/>
<div class=«data-name»/>
<div name/>
<div data-name=«john»/>
<name/>
<data-name first-name=«john» last-name=«doe»>
Работа с атрибутами

Решение передавать данные из атрибутов просилось само собой. Для директив в атрибутах был задан сценарий передачи значения атрибута, для директив-тэгов — формируется объект из всех атрибутов. В итоге в примерах выше в первом случае будет передано ‘john’, во втором — {‘first-name’: ‘john’, ‘last-name’: ‘doe’}.
Для более «умной» передачи данных появилась поддержка «краткого» синтаксиса объекта: можно писать name=«first: ‘john’, jast: ‘doe’». в действительности внутри происходит что-то вроде

try {value = eval(  ‘{‘ + value + ‘}’ )}
try {value = eval(   value  )}
return value

Атрибуты могут меняться, и для их изменения тоже есть отдельный колбэк:

directive(‘name’, {
alter: function(){}
}

В некоторых случаях — например, для директивы вроде

directive(‘include’, function(node, path){
$.get(path, function(data){node.innerHTML = data})
})

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

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

Примеры

Из больших директив, которые уже используются в некоторых проектах, одна из самых приятных — это scrollbox, автоматическая «обертка» для любого элемента, которая навешивает на элемент кастомный скролл.

Пример на jsFiddle [2]

А вот простой и удобный способ работать с drag-n-drop-ом файлов. Просто перетащите файл на серый квадрат.
Пример на jsFiddle [3]
да, код немного объемен, но если добавить jQuery — он будет примерно в 2 раза короче.

А вот и повторение опубликованного пару месяцев назад на хабре решения для разработчиков. Для того, чтобы почувствовать всю увлекательность решения — нужно будет залезть в панель веб-разработки и вручную поменять значение repeat у любого из клонированных элементов. Если удалить атрибут — элемент станет одиночным, после чего можно будет добавить атрибут еще раз.
Пример на jsFiddle [4]

Для тех, кто хочет попробовать cornerJS в своих проектах — минифицированная и обычная версии находятся тут:
Репозиторий cornerJS на GitHub [5]

Автор: Jabher

Источник [6]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/news/45223

Ссылки в тексте:

[1] уже была на хабре: http://habrahabr.ru/post/164493/

[2] Пример на jsFiddle: http://jsfiddle.net/8ygdQ/

[3] Пример на jsFiddle: http://jsfiddle.net/HhXPt/

[4] Пример на jsFiddle: http://jsfiddle.net/V3DLa/

[5] Репозиторий cornerJS на GitHub: https://github.com/Jabher/cornerjs

[6] Источник: http://habrahabr.ru/post/192740/