nanoblocks
-- nano framework для написания блоков.
Блок состоит из двух частей: визуальное представление (html/css
) и поведение (js
).
Вообще говоря, эти части независимы друг от друга. Одно и тоже поведение можно навешивать
на визуально разные блоки.
Внешний вид блоков задается html-разметкой и набором css-классов.
Поведение задается через специальные data
-атрибуты:
<div class="popup" data-nb="popup">
...
</div>
При этом название блока в css не обязано совпадать с названием блока в js:
<!-- Внешний вид другой, а поведение такое же. -->
<div class="dialog" data-nb="popup">
...
</div>
Блоки определяются примерно так:
nb.define('popup', {
// События, на которые подписан блок.
'events': {
'click .close': 'onclick',
...
},
// Методы блока (включая и обработчики событий).
'onclick': function(e, node) {
...
this.close();
},
'close': function() {
$(this.node).hide();
return false;
}
...
});
Первым параметром в nb.define
передается имя блока, вторым объект, описывающий методы и свойства блока.
По сути это прототип. Свойство events
имеет особое значение, оно описывает то, на какие
события реагирует блок.
Блок может реагировать на два типа событий:
-
DOM-события (
click
,dblclick
,keypress
, ...). Этот тип событий может иметь уточняющий селектор, например,click .foo
. -
Кастомные события.
events: {
'click': function(e, node) {
...
},
'click .foo': 'onClickFoo',
'open': function(e, params) {
...
}
}
В качестве обработчика события можно указать либо функцию, либо название метода блока.
В обработчик передаются два параметра:
-
Для DOM-событий первый параметр -- это jQuery.Event, а второй -- html-нода, на которой случилось событие. В случае, когда задан селектор, это будет нода, соответствующая селектору. Если селектора нет, то это будет нода всего блока.
-
Для кастомных событий первый параметр -- это название события, второй -- дополнительный параметр, который можно передать в метод
trigger()
.
Внутри обработчика this
указывает на текущий блок.
Функция nb.define
определяет конструкторы соответствующих классов блоков.
Экземпляры же создаются по мере необходимости и кэшируются.
В общем случае, схема такая:
-
При инициализации библиотеки на документ вешаются обработчики для всех DOM-событий (
click
, ...). -
Когда пользователь кликает (например) куда-нибудь, этот обработчик пытается найти ближайший блок, внутри которого произошел клик. Для этого он проходит по всем нодам от ноды, на которой произошло событие, до самого верха (
document
). Для каждой ноды он смотрит, есть ли у нее атрибутdata-nb
. -
Если это блок (есть атрибут
data-nb
), либо из кэша достается раннее созданный блок, либо же создается экземпляр блока с классом, указанным вdata-nb
, в конструктор передается та самая нода. -
Сразу после создания блока, на нем генерится событие
init
. -
Ключом для кэширования блоков служит атрибут
id
. Если такого атрибута на ноде блока нет, генерится уникальный id, ноде выставляется соответствующий атрибут. -
После чего проверяется, подписан ли блок на DOM-событие
click
, если да, вызывается этот обработчик. Если нет или же обработчик вернул неfalse
, то берется родительская нода и процесс продолжается.
Т.е. блоки создаются тогда, когда на них, возможно, случилось DOM-событие, на которое блок может быть подписан.
В случае, когда блок нужно создать сразу же после загрузки страницы,
ему нужно задать специальный класс _init
:
<div class="popup _init" data-nb="popup">
...
В момент инициализации библиотеки находятся все блоки на странице с классом _init
и для
всех них сразу создаются экземпляры блоков. Если блок в events
указал событие init
, то
он сможет сразу же выполнить какое-то действие:
nb.define('popup', {
events: {
'init': function() {
// do something
},
...
},
...
}
Есть несколько вариантов, как из существующих блоков сделать какой-то другой:
- Миксины.
- Расширение.
- Замена.
На одной html-ноде можно задать несколько js-блоков:
nb.define('foo', {
events: {
click: function() {
console.log('click foo');
return false;
}
}
});
nb.define('bar', {
events: {
click: function() {
console.log('click bar');
return false;
}
}
});
<div data-nb="foo bar">foobar</div>
В этом примере, при клике в этот div
будут срабатывать оба обработчика в том порядке,
в котором они заданы в атрибуте data-nb
.
Это вариант применим тогда, когда нужно слегка подкорректировать поведение блока. Или же добавить новый функционал.
nb.define('foo', {
events: {
click: function() {
console.log('click foo');
return false;
}
}
});
nb.define('bar', {
events: {
// Если у ноды есть класс _disabled, то ничего не делаем.
// Иначе вызывает родительский обработчик.
'click': function(e, node) {
if ( $(node).hasClass('_disabled') ) {
console.log('disabled!');
return false;
}
},
// Новая функциональность.
dblclick: 'onDoubleClick'
},
'onDoubleClick': function() {
...
}
// Последним параметром указываем базовый класс.
}, 'foo');
<div data-nb="bar">foobar</div>
<div class="_disabled" data-nb="bar">disabled foobar</div>
Пока не реализовано. Полностью заменяет реакцию на событие. Нужно ли это вообще?
Непонятно, каким образом задавать этот вариант. Вариант:
nb.define('bar', {
events: {
// Даже если этот обработчик не возвращает false,
// родительский обработчик не вызывается.
'! click': function() {
...
}
}
}, 'foo');
Функция nb.block(node)
принимает html-ноду и возвращает блок, созданный на этой ноде.
var block = nb.block( document.getElementsByClassName('.popup')[0] );
var block = nb.block( document.getElementById('my-block') );
var block = nb.find('my-block'); // Тоже самое.
Функция nb.find(id)
сперва ищет в документе ноду с заданным id
и создает на ней блок.
Лучше ей пока не пользоваться, видимо, т.к. я планирую ее расширить, чтобы она принимала
селектор, а не id
.
Все блоки имеют методы on
, off
и trigger
:
var block = nb.block(...);
var handler = block.on('foo', function(e, params) {
console.log(e, params);
});
block.trigger('foo', 42);
block.off('foo', handler);
Сейчас есть методы getMod
, setMod
, delMod
.
Но они, видимо, будут переделаны в отдельные функции, работающие с html-нодами,
а не с блоками.
Свойство node
:
var node = block.node; // html-нода, на которой инициализирован блок.
Метод nbdata
:
var foo = block.nbdata('foo'); // тоже самое, что и block.node.getAttribute('data-nb-foo').
block.nbdata('foo', 42); // тоже самое, что и block.node.setAttribute('data-nb-foo', 42).