definer('Node', /** @exports Node */ function(Tag, Selector, object) {
/**
* Модуль работы с БЭМ-узлом.
*
* @constructor
* @param {object} bemjson BEMJSON узла
*/
function Node(bemjson) {
/**
* BEMJSON узла.
*
* @private
* @type {object}
*/
this._bemjson = bemjson;
this._setInfo(bemjson);
}
/**
* Имя класса для js-инициализации.
*
* @type {string}
*/
Node.bemClass = 'i-bem';
/**
* Имя атрибута для хранения параметров инициализации.
*
* @type {string}
*/
Node.bemAttr = 'data-bem';
Node.prototype = {
/**
* Получить/установить BEMJSON узла.
*
* @param {object} [bemjson] BEMJSON
* @returns {object|Node}
*/
bemjson: function(bemjson) {
if(bemjson === undefined) {
return this._bemjson;
}
this._bemjson = bemjson;
this._setInfo(bemjson);
return this;
},
/**
* Проверить узел на блок.
*
* @returns {boolean}
*/
isBlock: function() {
return !!this._bemjson.block && !this.isElem();
},
/**
* Проверить узел на элемент.
*
* @returns {boolean}
*/
isElem: function() {
return !!this._bemjson.elem;
},
/**
* Получить экземпляр имени базовой БЭМ-сущности.
*
* Это может быть блок или элемент блока.
*
* @returns {Selector}
*/
getName: function() {
var name = new Selector(this._bemjson.block);
if(this.isElem()) {
name.elem(this._bemjson.elem);
}
return name;
},
/**
* Получить параметры узла.
*
* @returns {object}
*/
getParams: function() {
var params = {};
if(this._bemjson.js) {
params[this._name.toString()] = this._bemjson.js === true ? {} : this._bemjson.js;
}
return this._mix.reduce(function(params, mixNode) {
return object.extend(params, mixNode.params);
}, params);
},
/**
* Получить информацию о примиксованных сущностях.
*
* @returns {array}
*/
getMix: function() {
if(!this._bemjson.mix) return [];
return this._bemjson.mix.reduce(function(mix, mixNode) {
if(!mixNode) return mix;
var node = new Node(mixNode);
if(node.isElem() && !node.bemjson().block) {
var nodeBemjson = node.bemjson();
nodeBemjson.block = this._bemjson.block;
node.bemjson(nodeBemjson);
}
mix.push({
name: node.getName().toString(),
params: node.getParams(),
classes: node.getClass()
});
return mix;
}.bind(this), []);
},
/**
* Получить список классов узла.
*
* @returns {string[]}
*/
getClass: function() {
var node = this._bemjson;
if(node.cls) {
this._tag.addClass(node.cls.split(' ').filter(function(cls) { return cls; }));
}
if(node.bem === false) return this._tag.getClass();
if(this.isBlock() || this.isElem()) {
this._tag.addClass(this._name.toString());
}
if(!object.isEmpty(this._params)) {
this._tag.addClass(Node.bemClass);
}
if(!object.isEmpty(node.mods)) {
var mods = this._getModsClasses('mod');
this._tag.addClass(mods.length ? mods : this._name.toString());
}
if(this.isElem() && !object.isEmpty(node.elemMods)) {
this._tag.addClass(this._getElemModsClasses());
}
this._tag.addClass(this._mix.reduce(function(mixClasses, mixNode) {
return mixClasses.concat(mixNode.classes);
}, []));
return this._tag.getClass();
},
/**
* Получить/установить содержимое узла.
*
* @param {*} [content] Содержимое
* @returns {*|Node}
*/
content: function(content) {
if(content === undefined) return this._bemjson.content || '';
this._bemjson.content = content;
return this;
},
/**
* Получить строковое представление узла.
*
* @returns {string}
*/
toString: function() {
this.getClass();
if(!object.isEmpty(this._params)) {
this._tag.attr(Node.bemAttr, this._params);
}
if(this._bemjson.content) {
this._tag.addContent(this._bemjson.content);
}
var escape = Node.resolveOptionEscape(this._options.escape);
return this._tag.toString({
escapeContent: escape.content,
escapeAttr: escape.attrs
});
},
/**
* Сохранить информацию об узле.
*
* @private
* @param {object} bemjson BEMJSON узла
*/
_setInfo: function(bemjson) {
/**
* Экземпляр тега.
*
* @private
* @type {Tag}
*/
this._tag = new Tag(bemjson.tag).attr(bemjson.attrs || {});
if(bemjson.single !== undefined) {
this._tag.single(bemjson.single);
}
/**
* Экземпляр имени БЭМ-сущности.
*
* @private
* @type {Selector}
*/
this._name = this.getName();
/**
* Список информационных объектов о примиксованных сущностях.
*
* @private
* @type {array}
*/
this._mix = this.getMix();
/**
* Параметры узла.
*
* @private
* @type {object}
*/
this._params = this.getParams();
/**
* Опции преобразования узла.
*
* @private
* @type {object}
*/
this._options = bemjson.options || {};
},
/**
* Получить список классов модификаторов узла.
*
* @private
* @param {string} method Имя метода для установки модификаторов
* @returns {string[]}
*/
_getModsClasses: function(method) {
var mods = this._bemjson[method + 's'];
return Object.keys(mods).reduce(function(classes, key) {
if(mods[key] !== false && mods[key] !== undefined) {
classes.push(this._name[method](key, mods[key]).toString());
}
return classes;
}.bind(this), []);
},
/**
* Получить список классов модификаторов элемента.
*
* @private
* @returns {string[]}
*/
_getElemModsClasses: function() {
if(!object.isEmpty(this._bemjson.mods)) {
return Object.keys(this._bemjson.mods).reduce(function(classes, key) {
this._name.mod(key, this._bemjson.mods[key]);
return classes.concat(this._getModsClasses('elemMod'));
}.bind(this), []);
}
return this._getModsClasses('elemMod');
}
};
/**
* Получить/установить опции экранирования.
*
* @param {boolean|object} escape Флаг экранирования спецсимволов
* @param {boolean} [escape.content] Флаг экранирования содержимого
* @param {boolean} [escape.attrs] Флаг экранирования значений атрибутов
* @returns {object}
*/
Node.resolveOptionEscape = function(escape) {
var content = Tag.escapeContent,
attrs = Tag.escapeAttr;
if(escape !== undefined) {
if(is.boolean(escape)) {
content = escape;
attrs = escape;
} else {
if(is.boolean(escape.content)) {
content = escape.content;
}
if(is.boolean(escape.attrs)) {
attrs = escape.attrs;
}
}
}
return {
content: content,
attrs: attrs
};
};
return Node;
});