Skip to content

Instantly share code, notes, and snippets.

@Guria Guria/_noinit.js
Last active Aug 29, 2015

Embed
What would you like to do?
/**
* Modules
*
* Copyright (c) 2013 Filatov Dmitry (dfilatov@yandex-team.ru)
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* @version 0.1.0
*/
(function(global) {
var undef,
DECL_STATES = {
NOT_RESOLVED : 'NOT_RESOLVED',
IN_RESOLVING : 'IN_RESOLVING',
RESOLVED : 'RESOLVED'
},
/**
* Creates a new instance of modular system
* @returns {Object}
*/
create = function() {
var curOptions = {
trackCircularDependencies : true,
allowMultipleDeclarations : true
},
modulesStorage = {},
waitForNextTick = false,
pendingRequires = [],
/**
* Defines module
* @param {String} name
* @param {String[]} [deps]
* @param {Function} declFn
*/
define = function(name, deps, declFn) {
if(!declFn) {
declFn = deps;
deps = [];
}
var module = modulesStorage[name];
if(!module) {
module = modulesStorage[name] = {
name : name,
decl : undef
};
}
module.decl = {
name : name,
prev : module.decl,
fn : declFn,
state : DECL_STATES.NOT_RESOLVED,
deps : deps,
dependents : [],
exports : undef
};
},
/**
* Requires modules
* @param {String|String[]} modules
* @param {Function} cb
* @param {Function} [errorCb]
*/
require = function(modules, cb, errorCb) {
if(typeof modules === 'string') {
modules = [modules];
}
if(!waitForNextTick) {
waitForNextTick = true;
nextTick(onNextTick);
}
pendingRequires.push({
deps : modules,
cb : function(exports, error) {
error?
(errorCb || onError)(error) :
cb.apply(global, exports);
}
});
},
/**
* Returns state of module
* @param {String} name
* @returns {String} state, possible values are NOT_DEFINED, NOT_RESOLVED, IN_RESOLVING, RESOLVED
*/
getState = function(name) {
var module = modulesStorage[name];
return module?
DECL_STATES[module.decl.state] :
'NOT_DEFINED';
},
/**
* Returns whether the module is defined
* @param {String} name
* @returns {Boolean}
*/
isDefined = function(name) {
return !!modulesStorage[name];
},
/**
* Sets options
* @param {Object} options
*/
setOptions = function(options) {
for(var name in options) {
if(options.hasOwnProperty(name)) {
curOptions[name] = options[name];
}
}
},
onNextTick = function() {
waitForNextTick = false;
applyRequires();
},
applyRequires = function() {
var requiresToProcess = pendingRequires,
i = 0, require;
pendingRequires = [];
while(require = requiresToProcess[i++]) {
requireDeps(null, require.deps, [], require.cb);
}
},
requireDeps = function(fromDecl, deps, path, cb) {
var unresolvedDepsCnt = deps.length;
if(!unresolvedDepsCnt) {
cb([]);
}
var decls = [],
i = 0, len = unresolvedDepsCnt,
dep, decl;
while(i < len) {
dep = deps[i++];
if(typeof dep === 'string') {
if(!modulesStorage[dep]) {
cb(null, buildModuleNotFoundError(dep, fromDecl));
return;
}
decl = modulesStorage[dep].decl;
}
else {
decl = dep;
}
if(decl.state === DECL_STATES.IN_RESOLVING &&
curOptions.trackCircularDependencies &&
isDependenceCircular(decl, path)) {
cb(null, buildCircularDependenceError(decl, path));
return;
}
decls.push(decl);
startDeclResolving(
decl,
path,
function(_, error) {
if(error) {
cb(null, error);
return;
}
if(!--unresolvedDepsCnt) {
var exports = [],
i = 0, decl;
while(decl = decls[i++]) {
exports.push(decl.exports);
}
cb(exports);
}
});
}
},
startDeclResolving = function(decl, path, cb) {
if(decl.state === DECL_STATES.RESOLVED) {
cb(decl.exports);
return;
}
else {
decl.dependents.push(cb);
}
if(decl.state === DECL_STATES.IN_RESOLVING) {
return;
}
if(decl.prev && !curOptions.allowMultipleDeclarations) {
provideError(decl, buildMultipleDeclarationError(decl));
return;
}
curOptions.trackCircularDependencies && (path = path.slice()).push(decl);
var isProvided = false,
deps = decl.prev? decl.deps.concat([decl.prev]) : decl.deps;
decl.state = DECL_STATES.IN_RESOLVING;
requireDeps(
decl,
deps,
path,
function(depDeclsExports, error) {
if(error) {
provideError(decl, error);
return;
}
depDeclsExports.unshift(function(exports, error) {
if(isProvided) {
cb(null, buildDeclAreadyProvidedError(decl));
return;
}
isProvided = true;
error?
provideError(decl, error) :
provideDecl(decl, exports);
});
decl.fn.apply(
{
name : decl.name,
deps : decl.deps,
global : global
},
depDeclsExports);
});
},
provideDecl = function(decl, exports) {
decl.exports = exports;
decl.state = DECL_STATES.RESOLVED;
var i = 0, dependent;
while(dependent = decl.dependents[i++]) {
dependent(exports);
}
decl.dependents = undef;
},
provideError = function(decl, error) {
decl.state = DECL_STATES.NOT_RESOLVED;
var i = 0, dependent;
while(dependent = decl.dependents[i++]) {
dependent(null, error);
}
decl.dependents = [];
};
return {
create : create,
define : define,
require : require,
getState : getState,
isDefined : isDefined,
setOptions : setOptions
};
},
onError = function(e) {
nextTick(function() {
throw e;
});
},
buildModuleNotFoundError = function(name, decl) {
return Error(decl?
'Module "' + decl.name + '": can\'t resolve dependence "' + name + '"' :
'Required module "' + name + '" can\'t be resolved');
},
buildCircularDependenceError = function(decl, path) {
var strPath = [],
i = 0, pathDecl;
while(pathDecl = path[i++]) {
strPath.push(pathDecl.name);
}
strPath.push(decl.name);
return Error('Circular dependence has been detected: "' + strPath.join(' -> ') + '"');
},
buildDeclAreadyProvidedError = function(decl) {
return Error('Declaration of module "' + decl.name + '" has already been provided');
},
buildMultipleDeclarationError = function(decl) {
return Error('Multiple declarations of module "' + decl.name + '" have been detected');
},
isDependenceCircular = function(decl, path) {
var i = 0, pathDecl;
while(pathDecl = path[i++]) {
if(decl === pathDecl) {
return true;
}
}
return false;
},
nextTick = (function() {
var fns = [],
enqueueFn = function(fn) {
return fns.push(fn) === 1;
},
callFns = function() {
var fnsToCall = fns, i = 0, len = fns.length;
fns = [];
while(i < len) {
fnsToCall[i++]();
}
};
if(typeof process === 'object' && process.nextTick) { // nodejs
return function(fn) {
enqueueFn(fn) && process.nextTick(callFns);
};
}
if(global.setImmediate) { // ie10
return function(fn) {
enqueueFn(fn) && global.setImmediate(callFns);
};
}
if(global.postMessage && !global.opera) { // modern browsers
var isPostMessageAsync = true;
if(global.attachEvent) {
var checkAsync = function() {
isPostMessageAsync = false;
};
global.attachEvent('onmessage', checkAsync);
global.postMessage('__checkAsync', '*');
global.detachEvent('onmessage', checkAsync);
}
if(isPostMessageAsync) {
var msg = '__modules' + (+new Date()),
onMessage = function(e) {
if(e.data === msg) {
e.stopPropagation && e.stopPropagation();
callFns();
}
};
global.addEventListener?
global.addEventListener('message', onMessage, true) :
global.attachEvent('onmessage', onMessage);
return function(fn) {
enqueueFn(fn) && global.postMessage(msg, '*');
};
}
}
var doc = global.document;
if('onreadystatechange' in doc.createElement('script')) { // ie6-ie8
var head = doc.getElementsByTagName('head')[0],
createScript = function() {
var script = doc.createElement('script');
script.onreadystatechange = function() {
script.parentNode.removeChild(script);
script = script.onreadystatechange = null;
callFns();
};
head.appendChild(script);
};
return function(fn) {
enqueueFn(fn) && createScript();
};
}
return function(fn) { // old browsers
enqueueFn(fn) && setTimeout(callFns, 0);
};
})();
if(typeof exports === 'object') {
module.exports = create();
}
else {
global.modules = create();
}
})(this);
if(typeof module !== 'undefined') {modules = module.exports;}
(function () {
var BH = (function() {
var lastGenId = 0;
/**
* BH: BEMJSON -> HTML процессор.
* @constructor
*/
function BH() {
/**
* Используется для идентификации шаблонов.
* Каждому шаблону дается уникальный id для того, чтобы избежать повторного применения
* шаблона к одному и тому же узлу BEMJSON-дерева.
* @type {Number}
* @private
*/
this._lastMatchId = 0;
/**
* Плоский массив для хранения матчеров.
* Каждый элемент — массив с двумя элементами: [{String} выражение, {Function} шаблон}]
* @type {Array}
* @private
*/
this._matchers = [];
/**
* Флаг, включающий автоматическую систему поиска зацикливаний. Следует использовать в development-режиме,
* чтобы определять причины зацикливания.
* @type {Boolean}
* @private
*/
this._infiniteLoopDetection = false;
/**
* Неймспейс для библиотек. Сюда можно писать различный функционал для дальнейшего использования в шаблонах.
* ```javascript
* bh.lib.objects = bh.lib.objects || {};
* bh.lib.objects.inverse = bh.lib.objects.inverse || function(obj) { ... };
* ```
* @type {Object}
*/
this.lib = {};
this._inited = false;
/**
* Опции BH. Задаются через setOptions.
* @type {Object}
*/
this._options = {};
this._optJsAttrName = 'onclick';
this._optJsAttrIsJs = true;
this._optEscapeContent = false;
this.utils = {
_expandoId: new Date().getTime(),
bh: this,
/**
* Проверяет, что объект является примитивом.
* ```javascript
* bh.match('link', function(ctx) {
* ctx.tag(ctx.isSimple(ctx.content()) ? 'span' : 'div');
* });
* ```
* @param {*} obj
* @returns {Boolean}
*/
isSimple: function(obj) {
if (!obj || obj === true) return true;
var t = typeof obj;
return t === 'string' || t === 'number';
},
/**
* Расширяет один объект свойствами другого (других).
* Аналог jQuery.extend.
* ```javascript
* obj = ctx.extend(obj, {a: 1});
* ```
* @param {Object} target
* @returns {Object}
*/
extend: function(target) {
if (!target || typeof target !== 'object') {
target = {};
}
for (var i = 1, len = arguments.length; i < len; i++) {
var obj = arguments[i],
key;
if (obj) {
for (key in obj) {
target[key] = obj[key];
}
}
}
return target;
},
/**
* Возвращает позицию элемента в рамках родителя.
* Отсчет производится с 1 (единицы).
* ```javascript
* bh.match('list__item', function(ctx) {
* ctx.mod('pos', ctx.position());
* });
* ```
* @returns {Number}
*/
position: function() {
var node = this.node;
return node.index === 'content' ? 1 : node.position;
},
/**
* Возвращает true, если текущий BEMJSON-элемент первый в рамках родительского BEMJSON-элемента.
* ```javascript
* bh.match('list__item', function(ctx) {
* if (ctx.isFirst()) {
* ctx.mod('first', 'yes');
* }
* });
* ```
* @returns {Boolean}
*/
isFirst: function() {
var node = this.node;
return node.index === 'content' || node.position === 1;
},
/**
* Возвращает true, если текущий BEMJSON-элемент последний в рамках родительского BEMJSON-элемента.
* ```javascript
* bh.match('list__item', function(ctx) {
* if (ctx.isLast()) {
* ctx.mod('last', 'yes');
* }
* });
* ```
* @returns {Boolean}
*/
isLast: function() {
var node = this.node;
return node.index === 'content' || node.position === node.arr._listLength;
},
/**
* Передает параметр вглубь BEMJSON-дерева.
* **force** — задать значение параметра даже если оно было задано ранее.
* ```javascript
* bh.match('input', function(ctx) {
* ctx.content({ elem: 'control' });
* ctx.tParam('value', ctx.param('value'));
* });
* bh.match('input__control', function(ctx) {
* ctx.attr('value', ctx.tParam('value'));
* });
* ```
* @param {String} key
* @param {*} value
* @param {Boolean} [force]
* @returns {*|Ctx}
*/
tParam: function(key, value, force) {
var keyName = '__tp_' + key;
var node = this.node;
if (arguments.length > 1) {
if (force || !node.hasOwnProperty(keyName))
node[keyName] = value;
return this;
} else {
while (node) {
if (node.hasOwnProperty(keyName)) {
return node[keyName];
}
node = node.parentNode;
}
return undefined;
}
},
/**
* Применяет матчинг для переданного фрагмента BEMJSON.
* Возвращает результат преобразований.
* @param {BemJson} bemJson
* @returns {Object|Array}
*/
apply: function(bemJson) {
var prevCtx = this.ctx,
prevNode = this.node;
var res = this.bh.processBemJson(bemJson, prevCtx.block);
this.ctx = prevCtx;
this.node = prevNode;
return res;
},
/**
* Выполняет преобразования данного BEMJSON-элемента остальными шаблонами.
* Может понадобиться, например, чтобы добавить элемент в самый конец содержимого, если в базовых шаблонах в конец содержимого добавляются другие элементы.
* Пример:
* ```javascript
* bh.match('header', function(ctx) {
* ctx.content([
* ctx.content(),
* { elem: 'under' }
* ], true);
* });
* bh.match('header_float_yes', function(ctx) {
* ctx.applyBase();
* ctx.content([
* ctx.content(),
* { elem: 'clear' }
* ], true);
* });
* ```
* @returns {Ctx}
*/
applyBase: function() {
var node = this.node;
var json = node.json;
if (!json.elem && json.mods) json.blockMods = json.mods;
var block = json.block;
var blockMods = json.blockMods;
var subRes = this.bh._fastMatcher(this, json);
if (subRes !== undefined) {
this.ctx = node.arr[node.index] = node.json = subRes;
node.blockName = block;
node.blockMods = blockMods;
}
return this;
},
/**
* Останавливает выполнение прочих шаблонов для данного BEMJSON-элемента.
* Пример:
* ```javascript
* bh.match('button', function(ctx) {
* ctx.tag('button', true);
* });
* bh.match('button', function(ctx) {
* ctx.tag('span');
* ctx.stop();
* });
* ```
* @returns {Ctx}
*/
stop: function() {
this.ctx._stop = true;
return this;
},
/**
* Возвращает уникальный идентификатор. Может использоваться, например,
* чтобы задать соответствие между `label` и `input`.
* @returns {String}
*/
generateId: function() {
return 'uniq' + this._expandoId + (++lastGenId);
},
/**
* Возвращает/устанавливает модификатор в зависимости от аргументов.
* **force** — задать модификатор даже если он был задан ранее.
* ```javascript
* bh.match('input', function(ctx) {
* ctx.mod('native', 'yes');
* ctx.mod('disabled', true);
* });
* bh.match('input_islands_yes', function(ctx) {
* ctx.mod('native', '', true);
* ctx.mod('disabled', false, true);
* });
* ```
* @param {String} key
* @param {String|Boolean} [value]
* @param {Boolean} [force]
* @returns {String|undefined|Ctx}
*/
mod: function(key, value, force) {
var mods;
if (arguments.length > 1) {
mods = this.ctx.mods || (this.ctx.mods = {});
mods[key] = !mods.hasOwnProperty(key) || force ? value : mods[key];
return this;
} else {
mods = this.ctx.mods;
return mods ? mods[key] : undefined;
}
},
/**
* Возвращает/устанавливает модификаторы в зависимости от аргументов.
* **force** — задать модификаторы даже если они были заданы ранее.
* ```javascript
* bh.match('paranja', function(ctx) {
* ctx.mods({
* theme: 'normal',
* disabled: true
* });
* });
* ```
* @param {Object} [values]
* @param {Boolean} [force]
* @returns {Object|Ctx}
*/
mods: function(values, force) {
var mods = this.ctx.mods || (this.ctx.mods = {});
if (values !== undefined) {
this.ctx.mods = force ? this.extend(mods, values) : this.extend(values, mods);
return this;
} else {
return mods;
}
},
/**
* Возвращает/устанавливает тег в зависимости от аргументов.
* **force** — задать значение тега даже если оно было задано ранее.
* ```javascript
* bh.match('input', function(ctx) {
* ctx.tag('input');
* });
* ```
* @param {String} [tagName]
* @param {Boolean} [force]
* @returns {String|undefined|Ctx}
*/
tag: function(tagName, force) {
if (tagName !== undefined) {
this.ctx.tag = this.ctx.tag === undefined || force ? tagName : this.ctx.tag;
return this;
} else {
return this.ctx.tag;
}
},
/**
* Возвращает/устанавливает значение mix в зависимости от аргументов.
* При установке значения, если force равен true, то переданный микс заменяет прежнее значение,
* в противном случае миксы складываются.
* ```javascript
* bh.match('button_pseudo_yes', function(ctx) {
* ctx.mix({ block: 'link', mods: { pseudo: 'yes' } });
* ctx.mix([
* { elem: 'text' },
* { block: 'ajax' }
* ]);
* });
* ```
* @param {Array|BemJson} [mix]
* @param {Boolean} [force]
* @returns {Array|undefined|Ctx}
*/
mix: function(mix, force) {
if (mix !== undefined) {
if (force) {
this.ctx.mix = mix;
} else {
if (this.ctx.mix) {
this.ctx.mix = Array.isArray(this.ctx.mix) ?
this.ctx.mix.concat(mix) :
[this.ctx.mix].concat(mix);
} else {
this.ctx.mix = mix;
}
}
return this;
} else {
return this.ctx.mix;
}
},
/**
* Возвращает/устанавливает значение атрибута в зависимости от аргументов.
* **force** — задать значение атрибута даже если оно было задано ранее.
* @param {String} key
* @param {String} [value]
* @param {Boolean} [force]
* @returns {String|undefined|Ctx}
*/
attr: function(key, value, force) {
var attrs;
if (arguments.length > 1) {
attrs = this.ctx.attrs || (this.ctx.attrs = {});
attrs[key] = !attrs.hasOwnProperty(key) || force ? value : attrs[key];
return this;
} else {
attrs = this.ctx.attrs;
return attrs ? attrs[key] : undefined;
}
},
/**
* Возвращает/устанавливает атрибуты в зависимости от аргументов.
* **force** — задать атрибуты даже если они были заданы ранее.
* ```javascript
* bh.match('input', function(ctx) {
* ctx.attrs({
* name: ctx.param('name'),
* autocomplete: 'off'
* });
* });
* ```
* @param {Object} [values]
* @param {Boolean} [force]
* @returns {Object|Ctx}
*/
attrs: function(values, force) {
var attrs = this.ctx.attrs || {};
if (values !== undefined) {
this.ctx.attrs = force ? this.extend(attrs, values) : this.extend(values, attrs);
return this;
} else {
return attrs;
}
},
/**
* Возвращает/устанавливает значение bem в зависимости от аргументов.
* **force** — задать значение bem даже если оно было задано ранее.
* Если `bem` имеет значение `false`, то для элемента не будут генерироваться BEM-классы.
* ```javascript
* bh.match('meta', function(ctx) {
* ctx.bem(false);
* });
* ```
* @param {Boolean} [bem]
* @param {Boolean} [force]
* @returns {Boolean|undefined|Ctx}
*/
bem: function(bem, force) {
if (bem !== undefined) {
this.ctx.bem = this.ctx.bem === undefined || force ? bem : this.ctx.bem;
return this;
} else {
return this.ctx.bem;
}
},
/**
* Возвращает/устанавливает значение `js` в зависимости от аргументов.
* **force** — задать значение `js` даже если оно было задано ранее.
* Значение `js` используется для инициализации блоков в браузере через `BEM.DOM.init()`.
* ```javascript
* bh.match('input', function(ctx) {
* ctx.js(true);
* });
* ```
* @param {Boolean|Object} [js]
* @param {Boolean} [force]
* @returns {Boolean|Object|Ctx}
*/
js: function(js, force) {
if (js !== undefined) {
this.ctx.js = force ?
(js === true ? {} : js) :
js ? this.extend(this.ctx.js, js) : this.ctx.js;
return this;
} else {
return this.ctx.js;
}
},
/**
* Возвращает/устанавливает значение CSS-класса в зависимости от аргументов.
* **force** — задать значение CSS-класса даже если оно было задано ранее.
* ```javascript
* bh.match('page', function(ctx) {
* ctx.cls('ua_js_no ua_css_standard');
* });
* ```
* @param {String} [cls]
* @param {Boolean} [force]
* @returns {String|Ctx}
*/
cls: function(cls, force) {
if (cls !== undefined) {
this.ctx.cls = this.ctx.cls === undefined || force ? cls : this.ctx.cls;
return this;
} else {
return this.ctx.cls;
}
},
/**
* Возвращает/устанавливает параметр текущего BEMJSON-элемента.
* **force** — задать значение параметра, даже если оно было задано ранее.
* Например:
* ```javascript
* // Пример входного BEMJSON: { block: 'search', action: '/act' }
* bh.match('search', function(ctx) {
* ctx.attr('action', ctx.param('action') || '/');
* });
* ```
* @param {String} key
* @param {*} [value]
* @param {Boolean} [force]
* @returns {*|Ctx}
*/
param: function(key, value, force) {
if (value !== undefined) {
this.ctx[key] = this.ctx[key] === undefined || force ? value : this.ctx[key];
return this;
} else {
return this.ctx[key];
}
},
/**
* Возвращает/устанавливает защищенное содержимое в зависимости от аргументов.
* **force** — задать содержимое даже если оно было задано ранее.
* ```javascript
* bh.match('input', function(ctx) {
* ctx.content({ elem: 'control' });
* });
* ```
* @param {BemJson} [value]
* @param {Boolean} [force]
* @returns {BemJson|Ctx}
*/
content: function(value, force) {
if (arguments.length > 0) {
this.ctx.content = this.ctx.content === undefined || force ? value : this.ctx.content;
return this;
} else {
return this.ctx.content;
}
},
/**
* Возвращает/устанавливает незащищенное содержимое в зависимости от аргументов.
* **force** — задать содержимое даже если оно было задано ранее.
* ```javascript
* bh.match('input', function(ctx) {
* ctx.html({ elem: 'control' });
* });
* ```
* @param {String} [value]
* @param {Boolean} [force]
* @returns {String|Ctx}
*/
html: function(value, force) {
if (arguments.length > 0) {
this.ctx.html = this.ctx.html === undefined || force ? value : this.ctx.html;
return this;
} else {
return this.ctx.html;
}
},
/**
* Возвращает текущий фрагмент BEMJSON-дерева.
* Может использоваться в связке с `return` для враппинга и подобных целей.
* ```javascript
* bh.match('input', function(ctx) {
* return {
* elem: 'wrapper',
* content: ctx.json()
* };
* });
* ```
* @returns {Object|Array}
*/
json: function() {
return this.ctx;
}
};
}
BH.prototype = {
/**
* Задает опции шаблонизации.
*
* @param {Object} options
* {String} options[jsAttrName] Атрибут, в который записывается значение поля `js`. По умолчанию, `onclick`.
* {String} options[jsAttrScheme] Схема данных для `js`-значения.
* Форматы:
* `js` — значение по умолчанию. Получаем `return { ... }`.
* `json` — JSON-формат. Получаем `{ ... }`.
* @returns {BH}
*/
setOptions: function(options) {
var i;
for (i in options) {
this._options[i] = options[i];
}
if (options.jsAttrName) {
this._optJsAttrName = options.jsAttrName;
}
if (options.jsAttrScheme) {
this._optJsAttrIsJs = options.jsAttrScheme === 'js';
}
if (options.escapeContent) {
this._optEscapeContent = options.escapeContent;
}
return this;
},
/**
* Возвращает опции шаблонизации.
*
* @returns {Object}
*/
getOptions: function() {
return this._options;
},
/**
* Включает/выключает механизм определения зацикливаний.
*
* @param {Boolean} enable
* @returns {BH}
*/
enableInfiniteLoopDetection: function(enable) {
this._infiniteLoopDetection = enable;
return this;
},
/**
* Преобразует BEMJSON в HTML-код.
* @param {BemJson} bemJson
* @returns {String}
*/
apply: function(bemJson) {
return this.toHtml(this.processBemJson(bemJson));
},
/**
* Объявляет шаблон.
* ```javascript
* bh.match('page', function(ctx) {
* ctx.mix([{ block: 'ua' }]);
* ctx.cls('ua_js_no ua_css_standard');
* });
* bh.match('block_mod_modVal', function(ctx) {
* ctx.tag('span');
* });
* bh.match('block__elem', function(ctx) {
* ctx.attr('disabled', 'disabled');
* });
* bh.match('block__elem_elemMod', function(ctx) {
* ctx.mix([{ block: 'link' }]);
* });
* bh.match('block__elem_elemMod_elemModVal', function(ctx) {
* ctx.mod('active', 'yes');
* });
* bh.match('block_blockMod__elem', function(ctx) {
* ctx.param('checked', true);
* });
* bh.match('block_blockMod_blockModVal__elem', function(ctx) {
* ctx.content({
* elem: 'wrapper',
* content: ctx
* };
* });
* ```
* @param {String|Array|Object} expr
* @param {Function} matcher
* @returns {BH}
*/
match: function(expr, matcher) {
if (!expr) return this;
if (Array.isArray(expr)) {
expr.forEach(function(match, i) {
this.match(expr[i], matcher);
}, this);
return this;
}
if (typeof expr === 'object') {
for (var i in expr) {
this.match(i, expr[i]);
}
return this;
}
matcher.__id = '__func' + (this._lastMatchId++);
this._matchers.push([expr, matcher]);
this._fastMatcher = null;
return this;
},
/**
* Вспомогательный метод для компиляции шаблонов с целью их быстрого дальнейшего исполнения.
* @returns {String}
*/
buildMatcher: function() {
/**
* Группирует селекторы матчеров по указанному ключу.
* @param {Array} data
* @param {String} key
* @returns {Object}
*/
function groupBy(data, key) {
var res = {};
for (var i = 0, l = data.length; i < l; i++) {
var item = data[i];
var value = item[key] || '__no_value__';
(res[value] || (res[value] = [])).push(item);
}
return res;
}
var i, j, l;
var res = [];
var vars = ['bh = this'];
var allMatchers = this._matchers;
var decl, expr, matcherInfo;
var declarations = [], exprBits, blockExprBits;
for (i = allMatchers.length - 1; i >= 0; i--) {
matcherInfo = allMatchers[i];
expr = matcherInfo[0];
vars.push('_m' + i + ' = ms[' + i + '][1]');
decl = { fn: matcherInfo[1], index: i };
if (~expr.indexOf('__')) {
exprBits = expr.split('__');
blockExprBits = exprBits[0].split('_');
decl.block = blockExprBits[0];
if (blockExprBits.length > 1) {
decl.blockMod = blockExprBits[1];
decl.blockModVal = blockExprBits[2] || true;
}
exprBits = exprBits[1].split('_');
decl.elem = exprBits[0];
if (exprBits.length > 1) {
decl.elemMod = exprBits[1];
decl.elemModVal = exprBits[2] || true;
}
} else {
exprBits = expr.split('_');
decl.block = exprBits[0];
if (exprBits.length > 1) {
decl.blockMod = exprBits[1];
decl.blockModVal = exprBits[2] || true;
}
}
declarations.push(decl);
}
var declByBlock = groupBy(declarations, 'block');
res.push('var ' + vars.join(', ') + ';');
res.push('function applyMatchers(ctx, json) {');
res.push('var subRes;');
res.push('switch (json.block) {');
for (var blockName in declByBlock) {
res.push('case "' + strEscape(blockName) + '":');
var declsByElem = groupBy(declByBlock[blockName], 'elem');
res.push('switch (json.elem) {');
for (var elemName in declsByElem) {
if (elemName === '__no_value__') {
res.push('case undefined:');
} else {
res.push('case "' + strEscape(elemName) + '":');
}
var decls = declsByElem[elemName];
for (j = 0, l = decls.length; j < l; j++) {
decl = decls[j];
var fn = decl.fn;
var conds = [];
conds.push('!json.' + fn.__id);
if (decl.elemMod) {
conds.push(
'json.mods && json.mods["' + strEscape(decl.elemMod) + '"] === ' +
(decl.elemModVal === true || '"' + strEscape(decl.elemModVal) + '"'));
}
if (decl.blockMod) {
conds.push(
'json.blockMods["' + strEscape(decl.blockMod) + '"] === ' +
(decl.blockModVal === true || '"' + strEscape(decl.blockModVal) + '"'));
}
res.push('if (' + conds.join(' && ') + ') {');
res.push('json.' + fn.__id + ' = true;');
res.push('subRes = _m' + decl.index + '(ctx, json);');
res.push('if (subRes !== undefined) { return (subRes || "") }');
res.push('if (json._stop) return;');
res.push('}');
}
res.push('return;');
}
res.push('}');
res.push('return;');
}
res.push('}');
res.push('};');
res.push('return applyMatchers;');
return res.join('\n');
},
/**
* Раскрывает BEMJSON, превращая его из краткого в полный.
* @param {BemJson} bemJson
* @param {String} [blockName]
* @param {Boolean} [ignoreContent]
* @returns {Object|Array}
*/
processBemJson: function(bemJson, blockName, ignoreContent) {
if (bemJson == null) return;
if (!this._inited) {
this._init();
}
var resultArr = [bemJson];
var nodes = [{ json: bemJson, arr: resultArr, index: 0, blockName: blockName, blockMods: !bemJson.elem && bemJson.mods || {} }];
var node, json, block, blockMods, i, j, l, p, child, subRes;
var compiledMatcher = (this._fastMatcher || (this._fastMatcher = Function('ms', this.buildMatcher())(this._matchers)));
var processContent = !ignoreContent;
var infiniteLoopDetection = this._infiniteLoopDetection;
/**
* Враппер для json-узла.
* @constructor
*/
function Ctx() {
this.ctx = null;
}
Ctx.prototype = this.utils;
var ctx = new Ctx();
while (node = nodes.shift()) {
json = node.json;
block = node.blockName;
blockMods = node.blockMods;
if (Array.isArray(json)) {
for (i = 0, j = 0, l = json.length; i < l; i++) {
child = json[i];
if (child !== false && child != null && typeof child === 'object') {
nodes.push({ json: child, arr: json, index: i, position: ++j, blockName: block, blockMods: blockMods, parentNode: node });
}
}
json._listLength = j;
} else {
var content, stopProcess = false;
if (json.elem) {
block = json.block = json.block || block;
blockMods = json.blockMods = json.blockMods || blockMods;
if (json.elemMods) {
json.mods = json.elemMods;
}
} else if (json.block) {
block = json.block;
blockMods = json.blockMods = json.mods || {};
}
if (json.block) {
if (infiniteLoopDetection) {
json.__processCounter = (json.__processCounter || 0) + 1;
compiledMatcher.__processCounter = (compiledMatcher.__processCounter || 0) + 1;
if (json.__processCounter > 100) {
throw new Error('Infinite json loop detected at "' + json.block + (json.elem ? '__' + json.elem : '') + '".');
}
if (compiledMatcher.__processCounter > 1000) {
throw new Error('Infinite matcher loop detected at "' + json.block + (json.elem ? '__' + json.elem : '') + '".');
}
}
subRes = undefined;
if (!json._stop) {
ctx.node = node;
ctx.ctx = json;
subRes = compiledMatcher(ctx, json);
if (subRes !== undefined) {
json = subRes;
node.json = json;
node.blockName = block;
node.blockMods = blockMods;
nodes.push(node);
stopProcess = true;
}
}
}
if (!stopProcess) {
if (processContent && (content = json.content)) {
if (Array.isArray(content)) {
var flatten;
do {
flatten = false;
for (i = 0, l = content.length; i < l; i++) {
if (Array.isArray(content[i])) {
flatten = true;
break;
}
}
if (flatten) {
json.content = content = content.concat.apply([], content);
}
} while (flatten);
for (i = 0, j = 0, l = content.length, p = l - 1; i < l; i++) {
child = content[i];
if (child !== false && child != null && typeof child === 'object') {
nodes.push({ json: child, arr: content, index: i, position: ++j, blockName: block, blockMods: blockMods, parentNode: node });
}
}
content._listLength = j;
} else {
nodes.push({ json: content, arr: json, index: 'content', blockName: block, blockMods: blockMods, parentNode: node });
}
}
}
}
node.arr[node.index] = json;
}
return resultArr[0];
},
/**
* Превращает раскрытый BEMJSON в HTML.
* @param {BemJson} json
* @returns {String}
*/
toHtml: function(json) {
var res, i, l, item;
if (json === false || json == null) return '';
if (typeof json !== 'object') {
return this._optEscapeContent ? this.xmlEscape(json) : json;
} else if (Array.isArray(json)) {
res = '';
for (i = 0, l = json.length; i < l; i++) {
item = json[i];
if (item !== false && item != null) {
res += this.toHtml(item);
}
}
return res;
} else {
var isBEM = json.bem !== false;
if (typeof json.tag !== 'undefined' && !json.tag) {
return json.html || json.content ? this.toHtml(json.content) : '';
}
if (json.mix && !Array.isArray(json.mix)) {
json.mix = [json.mix];
}
var cls = '',
jattr, jval, attrs = '', jsParams, hasMixJsParams = false;
if (jattr = json.attrs) {
for (i in jattr) {
jval = jattr[i];
if (jval !== null && jval !== undefined) {
attrs += ' ' + i + '="' + attrEscape(jval) + '"';
}
}
}
if (isBEM) {
var base = json.block + (json.elem ? '__' + json.elem : '');
if (json.block) {
cls = toBemCssClasses(json, base);
if (json.js) {
(jsParams = {})[base] = json.js === true ? {} : json.js;
}
}
var addJSInitClass = jsParams && !json.elem;
var mixes = json.mix;
if (mixes && mixes.length) {
for (i = 0, l = mixes.length; i < l; i++) {
var mix = mixes[i];
if (mix && mix.bem !== false) {
var mixBlock = mix.block || json.block || '',
mixElem = mix.elem || (mix.block ? null : json.block && json.elem),
mixBase = mixBlock + (mixElem ? '__' + mixElem : '');
if (mixBlock) {
cls += toBemCssClasses(mix, mixBase, base);
if (mix.js) {
(jsParams = jsParams || {})[mixBase] = mix.js === true ? {} : mix.js;
hasMixJsParams = true;
if (!addJSInitClass) addJSInitClass = mixBlock && !mixElem;
}
}
}
}
}
if (jsParams) {
if (addJSInitClass) cls += ' i-bem';
var jsData = (!hasMixJsParams && json.js === true ?
'{&quot;' + base + '&quot;:{}}' :
attrEscape(JSON.stringify(jsParams)));
attrs += ' ' + (json.jsAttr || this._optJsAttrName) + '="' +
(this._optJsAttrIsJs ? 'return ' + jsData : jsData) + '"';
}
}
if (json.cls) {
cls = cls ? cls + ' ' + json.cls : json.cls;
}
var content, tag = (json.tag || 'div');
res = '<' + tag + (cls ? ' class="' + attrEscape(cls) + '"' : '') + (attrs ? attrs : '');
if (selfCloseHtmlTags[tag]) {
res += '/>';
} else {
res += '>';
if (json.html) {
res += json.html;
} else if ((content = json.content) != null) {
if (Array.isArray(content)) {
for (i = 0, l = content.length; i < l; i++) {
item = content[i];
if (item !== false && item != null) {
res += this.toHtml(item);
}
}
} else {
res += this.toHtml(content);
}
}
res += '</' + tag + '>';
}
return res;
}
},
/**
* Инициализация BH.
*/
_init: function() {
this._inited = true;
/*
Копируем ссылку на BEM.I18N в bh.lib.i18n, если это возможно.
*/
if (typeof BEM !== 'undefined' && typeof BEM.I18N !== 'undefined') {
this.lib.i18n = this.lib.i18n || BEM.I18N;
}
}
};
/**
* @deprecated
*/
BH.prototype.processBemjson = BH.prototype.processBemJson;
var selfCloseHtmlTags = {
area: 1,
base: 1,
br: 1,
col: 1,
command: 1,
embed: 1,
hr: 1,
img: 1,
input: 1,
keygen: 1,
link: 1,
menuitem: 1,
meta: 1,
param: 1,
source: 1,
track: 1,
wbr: 1
};
var buildEscape = (function() {
var ts = { '"': '&quot;', '&': '&amp;', '<': '&lt;', '>': '&gt;' },
f = function(t) {
return ts[t] || t;
};
return function(r) {
r = new RegExp(r, 'g');
return function(s) {
return ('' + s).replace(r, f);
};
};
})();
var xmlEscape = BH.prototype.xmlEscape = buildEscape('[&<>]');
var attrEscape = BH.prototype.attrEscape = buildEscape('["&<>]');
var strEscape = function(str) {
str += '';
if (~str.indexOf('\\')) {
str = str.replace(/\\/g, '\\\\');
}
if (~str.indexOf('"')) {
str = str.replace(/"/g, '\\"');
}
return str;
};
var toBemCssClasses = function(json, base, parentBase) {
var mods, mod, res = '', baseName, i, l;
if (parentBase !== base) {
if (parentBase) res += ' ';
res += base;
}
if (mods = json.mods || json.elem && json.elemMods) {
for (i in mods) {
if (mod = mods[i]) {
res += ' ' + base + '_' + i + (mod === true ? '' : '_' + mod);
}
}
}
return res;
};
return BH;
})();
if (typeof module !== 'undefined') {
module.exports = BH;
}
var bh = new BH();
bh.setOptions({
jsAttrName: 'data-bem',
jsAttrScheme: 'json'
});
modules.define('bh', [], function(provide) {
provide(bh);
});
modules.define('BEMHTML', [], function(provide) {
provide(bh);
});
})()
/* begin: ../../libs/bem-core/common.blocks/i-bem/i-bem.vanilla.js */
/**
* @module i-bem
*/
modules.define(
'i-bem',
[
'i-bem__internal',
'inherit',
'identify',
'next-tick',
'objects',
'functions',
'events'
],
function(
provide,
INTERNAL,
inherit,
identify,
nextTick,
objects,
functions,
events) {
var undef,
MOD_DELIM = INTERNAL.MOD_DELIM,
ELEM_DELIM = INTERNAL.ELEM_DELIM,
/**
* Storage for block init functions
* @private
* @type Array
*/
initFns = [],
/**
* Storage for block declarations (hash by block name)
* @private
* @type Object
*/
blocks = {};
/**
* Builds the name of the handler method for setting a modifier
* @param {String} prefix
* @param {String} modName Modifier name
* @param {String} modVal Modifier value
* @param {String} [elemName] Element name
* @returns {String}
*/
function buildModFnName(prefix, modName, modVal, elemName) {
return '__' + prefix +
(elemName? '__elem_' + elemName : '') +
'__mod' +
(modName? '_' + modName : '') +
(modVal? '_' + modVal : '');
}
/**
* Transforms a hash of modifier handlers to methods
* @param {String} prefix
* @param {Object} modFns
* @param {Object} props
* @param {String} [elemName]
*/
function modFnsToProps(prefix, modFns, props, elemName) {
if(functions.isFunction(modFns)) {
props[buildModFnName(prefix, '*', '*', elemName)] = modFns;
} else {
var modName, modVal, modFn;
for(modName in modFns) {
if(modFns.hasOwnProperty(modName)) {
modFn = modFns[modName];
if(functions.isFunction(modFn)) {
props[buildModFnName(prefix, modName, '*', elemName)] = modFn;
} else {
for(modVal in modFn) {
if(modFn.hasOwnProperty(modVal)) {
props[buildModFnName(prefix, modName, modVal, elemName)] = modFn[modVal];
}
}
}
}
}
}
}
function buildCheckMod(modName, modVal) {
return modVal?
Array.isArray(modVal)?
function(block) {
var i = 0, len = modVal.length;
while(i < len)
if(block.hasMod(modName, modVal[i++]))
return true;
return false;
} :
function(block) {
return block.hasMod(modName, modVal);
} :
function(block) {
return block.hasMod(modName);
};
}
function convertModHandlersToMethods(props) {
if(props.beforeSetMod) {
modFnsToProps('before', props.beforeSetMod, props);
delete props.beforeSetMod;
}
if(props.onSetMod) {
modFnsToProps('after', props.onSetMod, props);
delete props.onSetMod;
}
var elemName;
if(props.beforeElemSetMod) {
for(elemName in props.beforeElemSetMod) {
if(props.beforeElemSetMod.hasOwnProperty(elemName)) {
modFnsToProps('before', props.beforeElemSetMod[elemName], props, elemName);
}
}
delete props.beforeElemSetMod;
}
if(props.onElemSetMod) {
for(elemName in props.onElemSetMod) {
if(props.onElemSetMod.hasOwnProperty(elemName)) {
modFnsToProps('after', props.onElemSetMod[elemName], props, elemName);
}
}
delete props.onElemSetMod;
}
}
/**
* @class BEM
* @description Base block for creating BEM blocks
* @augments events:Emitter
* @exports
*/
var BEM = inherit(events.Emitter, /** @lends BEM.prototype */ {
/**
* @constructor
* @private
* @param {Object} mods Block modifiers
* @param {Object} params Block parameters
* @param {Boolean} [initImmediately=true]
*/
__constructor : function(mods, params, initImmediately) {
/**
* Cache of block modifiers
* @member {Object}
* @private
*/
this._modCache = mods || {};
/**
* Current modifiers in the stack
* @member {Object}
* @private
*/
this._processingMods = {};
/**
* Block parameters, taking into account the defaults
* @member {Object}
* @readonly
*/
this.params = objects.extend(this.getDefaultParams(), params);
initImmediately !== false?
this._init() :
initFns.push(this._init, this);
},
/**
* Initializes the block
* @private
*/
_init : function() {
return this.setMod('js', 'inited');
},
/**
* Adds an event handler
* @param {String|Object} e Event type
* @param {Object} [data] Additional data that the handler gets as e.data
* @param {Function} fn Handler
* @param {Object} [ctx] Handler context
* @returns {BEM} this
*/
on : function(e, data, fn, ctx) {
if(typeof e === 'object' && (functions.isFunction(data) || functions.isFunction(fn))) { // mod change event
e = this.__self._buildModEventName(e);
}
return this.__base.apply(this, arguments);
},
/**
* Removes event handler or handlers
* @param {String|Object} [e] Event type
* @param {Function} [fn] Handler
* @param {Object} [ctx] Handler context
* @returns {BEM} this
*/
un : function(e, fn, ctx) {
if(typeof e === 'object' && functions.isFunction(fn)) { // mod change event
e = this.__self._buildModEventName(e);
}
return this.__base.apply(this, arguments);
},
/**
* Executes the block's event handlers and live event handlers
* @protected
* @param {String} e Event name
* @param {Object} [data] Additional information
* @returns {BEM} this
*/
emit : function(e, data) {
var isModJsEvent = false;
if(typeof e === 'object' && !(e instanceof events.Event)) {
isModJsEvent = e.modName === 'js';
e = this.__self._buildModEventName(e);
}
if(isModJsEvent || this.hasMod('js', 'inited')) {
this.__base(e = this._buildEvent(e), data);
this._ctxEmit(e, data);
}
return this;
},
_ctxEmit : function(e, data) {
this.__self.emit(e, data);
},
/**
* Builds event
* @private
* @param {String|events:Event} e
* @returns {events:Event}
*/
_buildEvent : function(e) {
typeof e === 'string'?
e = new events.Event(e, this) :
e.target || (e.target = this);
return e;
},
/**
* Checks whether a block or nested element has a modifier
* @param {Object} [elem] Nested element
* @param {String} modName Modifier name
* @param {String} [modVal] Modifier value
* @returns {Boolean}
*/
hasMod : function(elem, modName, modVal) {
var len = arguments.length,
invert = false;
if(len === 1) {
modVal = '';
modName = elem;
elem = undef;
invert = true;
} else if(len === 2) {
if(typeof elem === 'string') {
modVal = modName;
modName = elem;
elem = undef;
} else {
modVal = '';
invert = true;
}
}
var res = this.getMod(elem, modName) === modVal;
return invert? !res : res;
},
/**
* Returns the value of the modifier of the block/nested element
* @param {Object} [elem] Nested element
* @param {String} modName Modifier name
* @returns {String} Modifier value
*/
getMod : function(elem, modName) {
var type = typeof elem;
if(type === 'string' || type === 'undefined') { // elem either omitted or undefined
modName = elem || modName;
var modCache = this._modCache;
return modName in modCache?
modCache[modName] || '' :
modCache[modName] = this._extractModVal(modName);
}
return this._getElemMod(modName, elem);
},
/**
* Returns the value of the modifier of the nested element
* @private
* @param {String} modName Modifier name
* @param {Object} elem Nested element
* @param {Object} [elemName] Nested element name
* @returns {String} Modifier value
*/
_getElemMod : function(modName, elem, elemName) {
return this._extractModVal(modName, elem, elemName);
},
/**
* Returns values of modifiers of the block/nested element
* @param {Object} [elem] Nested element
* @param {String} [...modNames] Modifier names
* @returns {Object} Hash of modifier values
*/
getMods : function(elem) {
var hasElem = elem && typeof elem !== 'string',
modNames = [].slice.call(arguments, hasElem? 1 : 0),
res = this._extractMods(modNames, hasElem? elem : undef);
if(!hasElem) { // caching
modNames.length?
modNames.forEach(function(name) {
this._modCache[name] = res[name];
}, this) :
this._modCache = res;
}
return res;
},
/**
* Sets the modifier for a block/nested element
* @param {Object} [elem] Nested element
* @param {String} modName Modifier name
* @param {String} modVal Modifier value
* @returns {BEM} this
*/
setMod : function(elem, modName, modVal) {
if(typeof modVal === 'undefined') {
if(typeof elem === 'string') { // if no elem
modVal = typeof modName === 'undefined'?
true : // e.g. setMod('focused')
modName; // e.g. setMod('js', 'inited')
modName = elem;
elem = undef;
} else { // if elem
modVal = true; // e.g. setMod(elem, 'focused')
}
}
if(!elem || elem[0]) {
modVal === false && (modVal = '');
var modId = (elem && elem[0]? identify(elem[0]) : '') + '_' + modName;
if(this._processingMods[modId])
return this;
var elemName,
curModVal = elem?
this._getElemMod(modName, elem, elemName = this.__self._extractElemNameFrom(elem)) :
this.getMod(modName);
if(curModVal === modVal)
return this;
this._processingMods[modId] = true;
var needSetMod = true,
modFnParams = [modName, modVal, curModVal];
elem && modFnParams.unshift(elem);
var modVars = [['*', '*'], [modName, '*'], [modName, modVal]],
prefixes = ['before', 'after'],
i = 0, prefix, j, modVar;
while(prefix = prefixes[i++]) {
j = 0;
while(modVar = modVars[j++]) {
if(this._callModFn(prefix, elemName, modVar[0], modVar[1], modFnParams) === false) {
needSetMod = false;
break;
}
}
if(!needSetMod) break;
if(prefix === 'before') {
elem || (this._modCache[modName] = modVal); // cache only block mods
this._onSetMod(modName, modVal, curModVal, elem, elemName);
}
}
this._processingMods[modId] = null;
needSetMod && this._emitModChangeEvents(modName, modVal, curModVal, elem, elemName);
}
return this;
},
/**
* Function after successfully changing the modifier of the block/nested element
* @protected
* @param {String} modName Modifier name
* @param {String} modVal Modifier value
* @param {String} oldModVal Old modifier value
* @param {Object} [elem] Nested element
* @param {String} [elemName] Element name
*/
_onSetMod : function(modName, modVal, oldModVal, elem, elemName) {},
_emitModChangeEvents : function(modName, modVal, oldModVal, elem, elemName) {
var eventData = { modName : modName, modVal : modVal, oldModVal : oldModVal };
elem && (eventData.elem = elem);
this
.emit({ modName : modName, modVal : '*', elem : elemName }, eventData)
.emit({ modName : modName, modVal : modVal, elem : elemName }, eventData);
},
/**
* Sets a modifier for a block/nested element, depending on conditions.
* If the condition parameter is passed: when true, modVal1 is set; when false, modVal2 is set.
* If the condition parameter is not passed: modVal1 is set if modVal2 was set, or vice versa.
* @param {Object} [elem] Nested element
* @param {String} modName Modifier name
* @param {String} modVal1 First modifier value
* @param {String} [modVal2] Second modifier value
* @param {Boolean} [condition] Condition
* @returns {BEM} this
*/
toggleMod : function(elem, modName, modVal1, modVal2, condition) {
if(typeof elem === 'string') { // if this is a block
condition = modVal2;
modVal2 = modVal1;
modVal1 = modName;
modName = elem;
elem = undef;
}
if(typeof modVal1 === 'undefined') { // boolean mod
modVal1 = true;
}
if(typeof modVal2 === 'undefined') {
modVal2 = '';
} else if(typeof modVal2 === 'boolean') {
condition = modVal2;
modVal2 = '';
}
var modVal = this.getMod(elem, modName);
(modVal === modVal1 || modVal === modVal2) &&
this.setMod(
elem,
modName,
typeof condition === 'boolean'?
(condition? modVal1 : modVal2) :
this.hasMod(elem, modName, modVal1)? modVal2 : modVal1);
return this;
},
/**
* Removes a modifier from a block/nested element
* @protected
* @param {Object} [elem] Nested element
* @param {String} modName Modifier name
* @returns {BEM} this
*/
delMod : function(elem, modName) {
if(!modName) {
modName = elem;
elem = undef;
}
return this.setMod(elem, modName, '');
},
/**
* Executes handlers for setting modifiers
* @private
* @param {String} prefix
* @param {String} elemName Element name
* @param {String} modName Modifier name
* @param {String} modVal Modifier value
* @param {Array} modFnParams Handler parameters
*/
_callModFn : function(prefix, elemName, modName, modVal, modFnParams) {
var modFnName = buildModFnName(prefix, modName, modVal, elemName);
return this[modFnName]?
this[modFnName].apply(this, modFnParams) :
undef;
},
/**
* Retrieves the value of the modifier
* @private
* @param {String} modName Modifier name
* @param {Object} [elem] Element
* @returns {String} Modifier value
*/
_extractModVal : function(modName, elem) {
return '';
},
/**
* Retrieves name/value for a list of modifiers
* @private
* @param {Array} modNames Names of modifiers
* @param {Object} [elem] Element
* @returns {Object} Hash of modifier values by name
*/
_extractMods : function(modNames, elem) {
return {};
},
/**
* Returns a block's default parameters
* @protected
* @returns {Object}
*/
getDefaultParams : function() {
return {};
},
/**
* Deletes a block
* @private
*/
_destruct : function() {
this.delMod('js');
},
/**
* Executes given callback on next turn eventloop in block's context
* @protected
* @param {Function} fn callback
* @returns {BEM} this
*/
nextTick : function(fn) {
var _this = this;
nextTick(function() {
_this.hasMod('js', 'inited') && fn.call(_this);
});
return this;
}
}, /** @lends BEM */{
_name : 'i-bem',
/**
* Storage for block declarations (hash by block name)
* @type Object
*/
blocks : blocks,
/**
* Declares blocks and creates a block class
* @param {String|Object} decl Block name (simple syntax) or description
* @param {String} decl.block|decl.name Block name
* @param {String} [decl.baseBlock] Name of the parent block
* @param {Array} [decl.baseMix] Mixed block names
* @param {String} [decl.modName] Modifier name
* @param {String|Array} [decl.modVal] Modifier value
* @param {Object} [props] Methods
* @param {Object} [staticProps] Static methods
* @returns {Function}
*/
decl : function(decl, props, staticProps) {
// string as block
typeof decl === 'string' && (decl = { block : decl });
// inherit from itself
if(arguments.length <= 2 &&
typeof decl === 'object' &&
(!decl || (typeof decl.block !== 'string' && typeof decl.modName !== 'string'))) {
staticProps = props;
props = decl;
decl = {};
}
typeof decl.block === 'undefined' && (decl.block = this.getName());
var baseBlock;
if(typeof decl.baseBlock === 'undefined') {
baseBlock = blocks[decl.block] || this;
} else if(typeof decl.baseBlock === 'string') {
baseBlock = blocks[decl.baseBlock];
if(!baseBlock)
throw('baseBlock "' + decl.baseBlock + '" for "' + decl.block + '" is undefined');
} else {
baseBlock = decl.baseBlock;
}
convertModHandlersToMethods(props || (props = {}));
if(decl.modName) {
var checkMod = buildCheckMod(decl.modName, decl.modVal);
objects.each(props, function(prop, name) {
functions.isFunction(prop) &&
(props[name] = function() {
var method;
if(checkMod(this)) {
method = prop;
} else {
var baseMethod = baseBlock.prototype[name];
baseMethod && baseMethod !== prop &&
(method = this.__base);
}
return method?
method.apply(this, arguments) :
undef;
});
});
}
if(staticProps && typeof staticProps.live === 'boolean') {
var live = staticProps.live;
staticProps.live = function() {
return live;
};
}
var block, baseBlocks = baseBlock;
if(decl.baseMix) {
baseBlocks = [baseBlocks];
decl.baseMix.forEach(function(mixedBlock) {
if(!blocks[mixedBlock]) {
throw('mix block "' + mixedBlock + '" for "' + decl.block + '" is undefined');
}
baseBlocks.push(blocks[mixedBlock]);
});
}
if(decl.block === baseBlock.getName()) {
// makes a new "live" if the old one was already executed
(block = inherit.self(baseBlocks, props, staticProps))._processLive(true);
} else {
(block = blocks[decl.block] = inherit(baseBlocks, props, staticProps))._name = decl.block;
delete block._liveInitable;
}
return block;
},
declMix : function(block, props, staticProps) {
convertModHandlersToMethods(props || (props = {}));
return blocks[block] = inherit(props, staticProps);
},
/**
* Processes a block's live properties
* @private
* @param {Boolean} [heedLive=false] Whether to take into account that the block already processed its live properties
* @returns {Boolean} Whether the block is a live block
*/
_processLive : function(heedLive) {
return false;
},
/**
* Factory method for creating an instance of the block named
* @param {String|Object} block Block name or description
* @param {Object} [params] Block parameters
* @returns {BEM}
*/
create : function(block, params) {
typeof block === 'string' && (block = { block : block });
return new blocks[block.block](block.mods, params);
},
/**
* Returns the name of the current block
* @returns {String}
*/
getName : function() {
return this._name;
},
/**
* Adds an event handler
* @param {String|Object} e Event type
* @param {Object} [data] Additional data that the handler gets as e.data
* @param {Function} fn Handler
* @param {Object} [ctx] Handler context
* @returns {Function} this
*/
on : function(e, data, fn, ctx) {
if(typeof e === 'object' && (functions.isFunction(data) || functions.isFunction(fn))) { // mod change event
e = this._buildModEventName(e);
}
return this.__base.apply(this, arguments);
},
/**
* Removes event handler or handlers
* @param {String|Object} [e] Event type
* @param {Function} [fn] Handler
* @param {Object} [ctx] Handler context
* @returns {Function} this
*/
un : function(e, fn, ctx) {
if(typeof e === 'object' && functions.isFunction(fn)) { // mod change event
e = this._buildModEventName(e);
}
return this.__base.apply(this, arguments);
},
_buildModEventName : function(modEvent) {
var res = MOD_DELIM + modEvent.modName + MOD_DELIM + (modEvent.modVal === false? '' : modEvent.modVal);
modEvent.elem && (res = ELEM_DELIM + modEvent.elem + res);
return res;
},
/**
* Retrieves the name of an element nested in a block
* @private
* @param {Object} elem Nested element
* @returns {String|undefined}
*/
_extractElemNameFrom : function(elem) {},
/**
* Executes the block init functions
* @private
*/
_runInitFns : function() {
if(initFns.length) {
var fns = initFns,
fn, i = 0;
initFns = [];
while(fn = fns[i]) {
fn.call(fns[i + 1]);
i += 2;
}
}
}
});
provide(BEM);
});
/* end: ../../libs/bem-core/common.blocks/i-bem/i-bem.vanilla.js */
/* begin: ../../libs/bem-core/common.blocks/i-bem/__internal/i-bem__internal.vanilla.js */
/**
* @module i-bem__internal
*/
modules.define('i-bem__internal', function(provide) {
var undef,
/**
* Separator for modifiers and their values
* @const
* @type String
*/
MOD_DELIM = '_',
/**
* Separator between names of a block and a nested element
* @const
* @type String
*/
ELEM_DELIM = '__',
/**
* Pattern for acceptable element and modifier names
* @const
* @type String
*/
NAME_PATTERN = '[a-zA-Z0-9-]+';
function isSimple(obj) {
var typeOf = typeof obj;
return typeOf === 'string' || typeOf === 'number' || typeOf === 'boolean';
}
function buildModPostfix(modName, modVal) {
var res = '';
/* jshint eqnull: true */
if(modVal != null && modVal !== false) {
res += MOD_DELIM + modName;
modVal !== true && (res += MOD_DELIM + modVal);
}
return res;
}
function buildBlockClass(name, modName, modVal) {
return name + buildModPostfix(modName, modVal);
}
function buildElemClass(block, name, modName, modVal) {
return buildBlockClass(block, undef, undef) +
ELEM_DELIM + name +
buildModPostfix(modName, modVal);
}
provide(/** @exports */{
NAME_PATTERN : NAME_PATTERN,
MOD_DELIM : MOD_DELIM,
ELEM_DELIM : ELEM_DELIM,
buildModPostfix : buildModPostfix,
/**
* Builds the class of a block or element with a modifier
* @param {String} block Block name
* @param {String} [elem] Element name
* @param {String} [modName] Modifier name
* @param {String|Number} [modVal] Modifier value
* @returns {String} Class
*/
buildClass : function(block, elem, modName, modVal) {
if(isSimple(modName)) {
if(!isSimple(modVal)) {
modVal = modName;
modName = elem;
elem = undef;
}
} else if(typeof modName !== 'undefined') {
modName = undef;
} else if(elem && typeof elem !== 'string') {
elem = undef;
}
if(!(elem || modName)) { // optimization for simple case
return block;
}
return elem?
buildElemClass(block, elem, modName, modVal) :
buildBlockClass(block, modName, modVal);
},
/**
* Builds full classes for a buffer or element with modifiers
* @param {String} block Block name
* @param {String} [elem] Element name
* @param {Object} [mods] Modifiers
* @returns {String} Class
*/
buildClasses : function(block, elem, mods) {
if(elem && typeof elem !== 'string') {
mods = elem;
elem = undef;
}
var res = elem?
buildElemClass(block, elem, undef, undef) :
buildBlockClass(block, undef, undef);
if(mods) {
for(var modName in mods) {
if(mods.hasOwnProperty(modName) && mods[modName]) {
res += ' ' + (elem?
buildElemClass(block, elem, modName, mods[modName]) :
buildBlockClass(block, modName, mods[modName]));
}
}
}
return res;
}
});
});
/* end: ../../libs/bem-core/common.blocks/i-bem/__internal/i-bem__internal.vanilla.js */
/* begin: ../../libs/bem-core/common.blocks/inherit/inherit.vanilla.js */
/**
* @module inherit
* @version 2.2.1
* @author Filatov Dmitry <dfilatov@yandex-team.ru>
* @description This module provides some syntax sugar for "class" declarations, constructors, mixins, "super" calls and static members.
*/
(function(global) {
var hasIntrospection = (function(){'_';}).toString().indexOf('_') > -1,
emptyBase = function() {},
hasOwnProperty = Object.prototype.hasOwnProperty,
objCreate = Object.create || function(ptp) {
var inheritance = function() {};
inheritance.prototype = ptp;
return new inheritance();
},
objKeys = Object.keys || function(obj) {
var res = [];
for(var i in obj) {
hasOwnProperty.call(obj, i) && res.push(i);
}
return res;
},
extend = function(o1, o2) {
for(var i in o2) {
hasOwnProperty.call(o2, i) && (o1[i] = o2[i]);
}
return o1;
},
toStr = Object.prototype.toString,
isArray = Array.isArray || function(obj) {
return toStr.call(obj) === '[object Array]';
},
isFunction = function(obj) {
return toStr.call(obj) === '[object Function]';
},
noOp = function() {},
needCheckProps = true,
testPropObj = { toString : '' };
for(var i in testPropObj) { // fucking ie hasn't toString, valueOf in for
testPropObj.hasOwnProperty(i) && (needCheckProps = false);
}
var specProps = needCheckProps? ['toString', 'valueOf'] : null;
function getPropList(obj) {
var res = objKeys(obj);
if(needCheckProps) {
var specProp, i = 0;
while(specProp = specProps[i++]) {
obj.hasOwnProperty(specProp) && res.push(specProp);
}
}
return res;
}
function override(base, res, add) {
var addList = getPropList(add),
j = 0, len = addList.length,
name, prop;
while(j < len) {
if((name = addList[j++]) === '__self') {
continue;
}
prop = add[name];
if(isFunction(prop) &&
(!hasIntrospection || prop.toString().indexOf('.__base') > -1)) {
res[name] = (function(name, prop) {
var baseMethod = base[name]?
base[name] :
name === '__constructor'? // case of inheritance from plane function
res.__self.__parent :
noOp;
return function() {
var baseSaved = this.__base;
this.__base = baseMethod;
var res = prop.apply(this, arguments);
this.__base = baseSaved;
return res;
};
})(name, prop);
} else {
res[name] = prop;
}
}
}
function applyMixins(mixins, res) {
var i = 1, mixin;
while(mixin = mixins[i++]) {
res?
isFunction(mixin)?
inherit.self(res, mixin.prototype, mixin) :
inherit.self(res, mixin) :
res = isFunction(mixin)?
inherit(mixins[0], mixin.prototype, mixin) :
inherit(mixins[0], mixin);
}
return res || mixins[0];
}
/**
* Creates class
* @exports
* @param {Function|Array} [baseClass|baseClassAndMixins] class (or class and mixins) to inherit from
* @param {Object} prototypeFields
* @param {Object} [staticFields]
* @returns {Function} class
*/
function inherit() {
var args = arguments,
withMixins = isArray(args[0]),
hasBase = withMixins || isFunction(args[0]),
base = hasBase? withMixins? applyMixins(args[0]) : args[0] : emptyBase,
props = args[hasBase? 1 : 0] || {},
staticProps = args[hasBase? 2 : 1],
res = props.__constructor || (hasBase && base.prototype.__constructor)?
function() {
return this.__constructor.apply(this, arguments);
} :
hasBase?
function() {
return base.apply(this, arguments);
} :
function() {};
if(!hasBase) {
res.prototype = props;
res.prototype.__self = res.prototype.constructor = res;
return extend(res, staticProps);
}
extend(res, base);
res.__parent = base;
var basePtp = base.prototype,
resPtp = res.prototype = objCreate(basePtp);
resPtp.__self = resPtp.constructor = res;
props && override(basePtp, resPtp, props);
staticProps && override(base, res, staticProps);
return res;
}
inherit.self = function() {
var args = arguments,
withMixins = isArray(args[0]),
base = withMixins? applyMixins(args[0], args[0][0]) : args[0],
props = args[1],
staticProps = args[2],
basePtp = base.prototype;
props && override(basePtp, basePtp, props);
staticProps && override(base, base, staticProps);
return base;
};
var defineAsGlobal = true;
if(typeof exports === 'object') {
module.exports = inherit;
defineAsGlobal = false;
}
if(typeof modules === 'object') {
modules.define('inherit', function(provide) {
provide(inherit);
});
defineAsGlobal = false;
}
if(typeof define === 'function') {
define(function(require, exports, module) {
module.exports = inherit;
});
defineAsGlobal = false;
}
defineAsGlobal && (global.inherit = inherit);
})(this);
/* end: ../../libs/bem-core/common.blocks/inherit/inherit.vanilla.js */
/* begin: ../../libs/bem-core/common.blocks/identify/identify.vanilla.js */
/**
* @module identify
*/
modules.define('identify', function(provide) {
var counter = 0,
expando = '__' + (+new Date),
get = function() {
return 'uniq' + (++counter);
};
provide(
/**
* Makes unique ID
* @exports
* @param {Object} obj Object that needs to be identified
* @param {Boolean} [onlyGet=false] Return a unique value only if it had already been assigned before
* @returns {String} ID
*/
function(obj, onlyGet) {
if(!obj) return get();
var key = 'uniqueID' in obj? 'uniqueID' : expando; // Use when possible native uniqueID for elements in IE
return onlyGet || key in obj?
obj[key] :
obj[key] = get();
}
);
});
/* end: ../../libs/bem-core/common.blocks/identify/identify.vanilla.js */
/* begin: ../../libs/bem-core/common.blocks/next-tick/next-tick.vanilla.js */
/**
* @module next-tick
*/
modules.define('next-tick', function(provide) {
/**
* Executes given function on next tick.
* @exports
* @type Function
* @param {Function} fn
*/
var global = this.global,
fns = [],
enqueueFn = function(fn) {
return fns.push(fn) === 1;
},
callFns = function() {
var fnsToCall = fns, i = 0, len = fns.length;
fns = [];
while(i < len) {
fnsToCall[i++]();
}
};
/* global process */
if(typeof process === 'object' && process.nextTick) { // nodejs
return provide(function(fn) {
enqueueFn(fn) && process.nextTick(callFns);
});
}
if(global.setImmediate) { // ie10
return provide(function(fn) {
enqueueFn(fn) && global.setImmediate(callFns);
});
}
if(global.postMessage) { // modern browsers
var isPostMessageAsync = true;
if(global.attachEvent) {
var checkAsync = function() {
isPostMessageAsync = false;
};
global.attachEvent('onmessage', checkAsync);
global.postMessage('__checkAsync', '*');
global.detachEvent('onmessage', checkAsync);
}
if(isPostMessageAsync) {
var msg = '__nextTick' + (+new Date),
onMessage = function(e) {
if(e.data === msg) {
e.stopPropagation && e.stopPropagation();
callFns();
}
};
global.addEventListener?
global.addEventListener('message', onMessage, true) :
global.attachEvent('onmessage', onMessage);
return provide(function(fn) {
enqueueFn(fn) && global.postMessage(msg, '*');
});
}
}
var doc = global.document;
if('onreadystatechange' in doc.createElement('script')) { // ie6-ie8
var head = doc.getElementsByTagName('head')[0],
createScript = function() {
var script = doc.createElement('script');
script.onreadystatechange = function() {
script.parentNode.removeChild(script);
script = script.onreadystatechange = null;
callFns();
};
head.appendChild(script);
};
return provide(function(fn) {
enqueueFn(fn) && createScript();
});
}
provide(function(fn) { // old browsers
enqueueFn(fn) && global.setTimeout(callFns, 0);
});
});
/* end: ../../libs/bem-core/common.blocks/next-tick/next-tick.vanilla.js */
/* begin: ../../libs/bem-core/common.blocks/objects/objects.vanilla.js */
/**
* @module objects
* @description A set of helpers to work with JavaScript objects
*/
modules.define('objects', function(provide) {
var hasOwnProp = Object.prototype.hasOwnProperty;
provide(/** @exports */{
/**
* Extends a given target by
* @param {Object} target object to extend
* @param {Object} source
* @returns {Object}
*/
extend : function(target, source) {
typeof target !== 'object' && (target = {});
for(var i = 1, len = arguments.length; i < len; i++) {
var obj = arguments[i];
if(obj) {
for(var key in obj) {
hasOwnProp.call(obj, key) && (target[key] = obj[key]);
}
}
}
return target;
},
/**
* Check whether a given object is empty (contains no enumerable properties)
* @param {Object} obj
* @returns {Boolean}
*/
isEmpty : function(obj) {
for(var key in obj) {
if(hasOwnProp.call(obj, key)) {
return false;
}
}
return true;
},
/**
* Generic iterator function over object
* @param {Object} obj object to iterate
* @param {Function} fn callback
* @param {Object} [ctx] callbacks's context
*/
each : function(obj, fn, ctx) {
for(var key in obj) {
if(hasOwnProp.call(obj, key)) {
ctx? fn.call(ctx, obj[key], key) : fn(obj[key], key);
}
}
}
});
});
/* end: ../../libs/bem-core/common.blocks/objects/objects.vanilla.js */
/* begin: ../../libs/bem-core/common.blocks/functions/functions.vanilla.js */
/**
* @module functions
* @description A set of helpers to work with JavaScript functions
*/
modules.define('functions', function(provide) {
var toStr = Object.prototype.toString;
provide(/** @exports */{
/**
* Checks whether a given object is function
* @param {*} obj
* @returns {Boolean}
*/
isFunction : function(obj) {
return toStr.call(obj) === '[object Function]';
},
/**
* Empty function
*/
noop : function() {}
});
});
/* end: ../../libs/bem-core/common.blocks/functions/functions.vanilla.js */
/* begin: ../../libs/bem-core/common.blocks/events/events.vanilla.js */
/**
* @module events
*/
modules.define(
'events',
['identify', 'inherit', 'functions'],
function(provide, identify, inherit, functions) {
var undef,
storageExpando = '__' + (+new<