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).