Skip to content

Instantly share code, notes, and snippets.

@Guria
Last active August 29, 2015 14:14
Show Gist options
  • Save Guria/6bb4a5dc7d97cba5fa67 to your computer and use it in GitHub Desktop.
Save Guria/6bb4a5dc7d97cba5fa67 to your computer and use it in GitHub Desktop.
/**
* Modules
*
* Copyright (c) 2013 Filatov Dmitry ([email protected])
* 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 <[email protected]>
* @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 Date) + 'storage',
getFnId = function(fn, ctx) {
return identify(fn) + (ctx? identify(ctx) : '');
},
/**
* @class Event
* @exports events:Event
*/
Event = inherit(/** @lends Event.prototype */{
/**
* @constructor
* @param {String} type
* @param {Object} target
*/
__constructor : function(type, target) {
/**
* Type
* @member {String} Event
*/
this.type = type;
/**
* Target
* @member {String} Event
*/
this.target = target;
/**
* Result
* @member {*}
*/
this.result = undef;
/**
* Data
* @member {*}
*/
this.data = undef;
this._isDefaultPrevented = false;
this._isPropagationStopped = false;
},
/**
* Prevents default action
*/
preventDefault : function() {
this._isDefaultPrevented = true;
},
/**
* Returns whether is default action prevented
* @returns {Boolean}
*/
isDefaultPrevented : function() {
return this._isDefaultPrevented;
},
/**
* Stops propagation
*/
stopPropagation : function() {
this._isPropagationStopped = true;
},
/**
* Returns whether is propagation stopped
* @returns {Boolean}
*/
isPropagationStopped : function() {
return this._isPropagationStopped;
}
}),
/**
* @lends Emitter
* @lends Emitter.prototype
*/
EmitterProps = {
/**
* Adds an event handler
* @param {String} 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 {Emitter} this
*/
on : function(e, data, fn, ctx, _special) {
if(typeof e === 'string') {
if(functions.isFunction(data)) {
ctx = fn;
fn = data;
data = undef;
}
var id = getFnId(fn, ctx),
storage = this[storageExpando] || (this[storageExpando] = {}),
eventTypes = e.split(' '), eventType,
i = 0, list, item,
eventStorage;
while(eventType = eventTypes[i++]) {
eventStorage = storage[eventType] || (storage[eventType] = { ids : {}, list : {} });
if(!(id in eventStorage.ids)) {
list = eventStorage.list;
item = { fn : fn, data : data, ctx : ctx, special : _special };
if(list.last) {
list.last.next = item;
item.prev = list.last;
} else {
list.first = item;
}
eventStorage.ids[id] = list.last = item;
}
}
} else {
for(var key in e) {
e.hasOwnProperty(key) && this.on(key, e[key], data, _special);
}
}
return this;
},
/**
* Adds a one time handler for the event.
* Handler is executed only the next time the event is fired, after which it is removed.
* @param {String} 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 {Emitter} this
*/
once : function(e, data, fn, ctx) {
return this.on(e, data, fn, ctx, { once : true });
},
/**
* Removes event handler or handlers
* @param {String} [e] Event type
* @param {Function} [fn] Handler
* @param {Object} [ctx] Handler context
* @returns {Emitter} this
*/
un : function(e, fn, ctx) {
if(typeof e === 'string' || typeof e === 'undefined') {
var storage = this[storageExpando];
if(storage) {
if(e) { // if event type was passed
var eventTypes = e.split(' '),
i = 0, eventStorage;
while(e = eventTypes[i++]) {
if(eventStorage = storage[e]) {
if(fn) { // if specific handler was passed
var id = getFnId(fn, ctx),
ids = eventStorage.ids;
if(id in ids) {
var list = eventStorage.list,
item = ids[id],
prev = item.prev,
next = item.next;
if(prev) {
prev.next = next;
} else if(item === list.first) {
list.first = next;
}
if(next) {
next.prev = prev;
} else if(item === list.last) {
list.last = prev;
}
delete ids[id];
}
} else {
delete this[storageExpando][e];
}
}
}
} else {
delete this[storageExpando];
}
}
} else {
for(var key in e) {
e.hasOwnProperty(key) && this.un(key, e[key], fn);
}
}
return this;
},
/**
* Fires event handlers
* @param {String|events:Event} e Event
* @param {Object} [data] Additional data
* @returns {Emitter} this
*/
emit : function(e, data) {
var storage = this[storageExpando],
eventInstantiated = false;
if(storage) {
var eventTypes = [typeof e === 'string'? e : e.type, '*'],
i = 0, eventType, eventStorage;
while(eventType = eventTypes[i++]) {
if(eventStorage = storage[eventType]) {
var item = eventStorage.list.first,
lastItem = eventStorage.list.last,
res;
while(item) {
if(!eventInstantiated) { // instantiate Event only on demand
eventInstantiated = true;
typeof e === 'string' && (e = new Event(e));
e.target || (e.target = this);
}
e.data = item.data;
res = item.fn.apply(item.ctx || this, arguments);
if(typeof res !== 'undefined') {
e.result = res;
if(res === false) {
e.preventDefault();
e.stopPropagation();
}
}
item.special && item.special.once &&
this.un(e.type, item.fn, item.ctx);
if(item === lastItem) {
break;
}
item = item.next;
}
}
}
}
return this;
}
},
/**
* @class Emitter
* @exports events:Emitter
*/
Emitter = inherit(
EmitterProps,
EmitterProps);
provide({
Emitter : Emitter,
Event : Event
});
});
/* end: ../../libs/bem-core/common.blocks/events/events.vanilla.js */
/* begin: ../../libs/bem-core/common.blocks/i-bem/__dom/i-bem__dom.js */
/**
* @module i-bem__dom
*/
modules.define(
'i-bem__dom',
['i-bem', 'i-bem__internal', 'identify', 'objects', 'functions', 'jquery', 'dom'],
function(provide, BEM, INTERNAL, identify, objects, functions, $, dom) {
var undef,
win = $(window),
doc = $(document),
/**
* Storage for DOM elements by unique key
* @type Object
*/
uniqIdToDomElems = {},
/**
* Storage for blocks by unique key
* @type Object
*/
uniqIdToBlock = {},
/**
* Storage for DOM element's parent nodes
* @type Object
*/
domNodesToParents = {},
/**
* Storage for block parameters
* @type Object
*/
domElemToParams = {},
/**
* Storage for liveCtx event handlers
* @type Object
*/
liveEventCtxStorage = {},
/**
* Storage for liveClass event handlers
* @type Object
*/
liveClassEventStorage = {},
blocks = BEM.blocks,
BEM_CLASS = 'i-bem',
BEM_SELECTOR = '.' + BEM_CLASS,
BEM_PARAMS_ATTR = 'data-bem',
NAME_PATTERN = INTERNAL.NAME_PATTERN,
MOD_DELIM = INTERNAL.MOD_DELIM,
ELEM_DELIM = INTERNAL.ELEM_DELIM,
EXTRACT_MODS_RE = RegExp(
'[^' + MOD_DELIM + ']' + MOD_DELIM + '(' + NAME_PATTERN + ')' +
'(?:' + MOD_DELIM + '(' + NAME_PATTERN + '))?$'),
buildModPostfix = INTERNAL.buildModPostfix,
buildClass = INTERNAL.buildClass,
reverse = Array.prototype.reverse;
/**
* Initializes blocks on a DOM element
* @param {jQuery} domElem DOM element
* @param {String} uniqInitId ID of the "initialization wave"
*/
function initBlocks(domElem, uniqInitId) {
var domNode = domElem[0],
params = getParams(domNode),
blockName;
for(blockName in params)
initBlock(
blockName,
domElem,
processParams(params[blockName], blockName, uniqInitId));
}
/**
* Initializes a specific block on a DOM element, or returns the existing block if it was already created
* @param {String} blockName Block name
* @param {jQuery} domElem DOM element
* @param {Object} [params] Initialization parameters
* @param {Boolean} [forceLive=false] Force live initialization
* @param {Function} [callback] Handler to call after complete initialization
*/
function initBlock(blockName, domElem, params, forceLive, callback) {
var domNode = domElem[0];
params || (params = processParams(getBlockParams(domNode, blockName), blockName));
var uniqId = params.uniqId,
block = uniqIdToBlock[uniqId];
if(block) {
if(block.domElem.index(domNode) < 0) {
block.domElem = block.domElem.add(domElem);
objects.extend(block.params, params);
}
return block;
}
uniqIdToDomElems[uniqId] = uniqIdToDomElems[uniqId]?
uniqIdToDomElems[uniqId].add(domElem) :
domElem;
var parentDomNode = domNode.parentNode;
if(!parentDomNode || parentDomNode.nodeType === 11) { // jquery doesn't unique disconnected node
$.unique(uniqIdToDomElems[uniqId]);
}
var blockClass = blocks[blockName] || DOM.decl(blockName, {}, { live : true }, true);
if(!(blockClass._liveInitable = !!blockClass._processLive()) || forceLive || params.live === false) {
forceLive && domElem.addClass(BEM_CLASS); // add css class for preventing memory leaks in further destructing
block = new blockClass(uniqIdToDomElems[uniqId], params, !!forceLive);
delete uniqIdToDomElems[uniqId];
callback && callback.apply(block, Array.prototype.slice.call(arguments, 4));
return block;
}
}
/**
* Processes and adds necessary block parameters
* @param {Object} params Initialization parameters
* @param {String} blockName Block name
* @param {String} [uniqInitId] ID of the "initialization wave"
*/
function processParams(params, blockName, uniqInitId) {
params.uniqId ||
(params.uniqId = (params.id?
blockName + '-id-' + params.id :
identify()) + (uniqInitId || identify()));
return params;
}
/**
* Helper for searching for a DOM element using a selector inside the context, including the context itself
* @param {jQuery} ctx Context
* @param {String} selector CSS selector
* @param {Boolean} [excludeSelf=false] Exclude context from search
* @returns {jQuery}
*/
function findDomElem(ctx, selector, excludeSelf) {
var res = ctx.find(selector);
return excludeSelf?
res :
res.add(ctx.filter(selector));
}
/**
* Returns parameters of a block's DOM element
* @param {HTMLElement} domNode DOM node
* @returns {Object}
*/
function getParams(domNode, blockName) {
var uniqId = identify(domNode);
return domElemToParams[uniqId] ||
(domElemToParams[uniqId] = extractParams(domNode));
}
/**
* Returns parameters of a block extracted from DOM node
* @param {HTMLElement} domNode DOM node
* @param {String} blockName
* @returns {Object}
*/
function getBlockParams(domNode, blockName) {
var params = getParams(domNode);
return params[blockName] || (params[blockName] = {});
}
/**
* Retrieves block parameters from a DOM element
* @param {HTMLElement} domNode DOM node
* @returns {Object}
*/
function extractParams(domNode) {
var attrVal = domNode.getAttribute(BEM_PARAMS_ATTR);
return attrVal? JSON.parse(attrVal) : {};
}
/**
* Uncouple DOM node from the block. If this is the last node, then destroys the block.
* @param {BEMDOM} block block
* @param {HTMLElement} domNode DOM node
*/
function removeDomNodeFromBlock(block, domNode) {
block.domElem.length === 1?
block._destruct() :
block.domElem = block.domElem.not(domNode);
}
/**
* Fills DOM node's parent nodes to the storage
* @param {jQuery} domElem
*/
function storeDomNodeParents(domElem) {
domElem.each(function() {
domNodesToParents[identify(this)] = this.parentNode;
});
}
/**
* @class BEMDOM
* @description Base block for creating BEM blocks that have DOM representation
* @exports
*/
var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{
/**
* @constructor
* @private
* @param {jQuery} domElem DOM element that the block is created on
* @param {Object} params Block parameters
* @param {Boolean} [initImmediately=true]
*/
__constructor : function(domElem, params, initImmediately) {
/**
* DOM elements of block
* @member {jQuery}
* @readonly
*/
this.domElem = domElem;
/**
* Cache for names of events on DOM elements
* @member {Object}
* @private
*/
this._eventNameCache = {};
/**
* Cache for elements
* @member {Object}
* @private
*/
this._elemCache = {};
/**
* @member {String} Unique block ID
* @private
*/
this._uniqId = params.uniqId;
uniqIdToBlock[this._uniqId] = this;
/**
* @member {Boolean} Flag for whether it's necessary to unbind from the document and window when destroying the block
* @private
*/
this._needSpecialUnbind = false;
this.__base(null, params, initImmediately);
},
/**
* Finds blocks inside the current block or its elements (including context)
* @param {String|jQuery} [elem] Block element
* @param {String|Object} block Name or description (block,modName,modVal) of the block to find
* @returns {BEMDOM[]}
*/
findBlocksInside : function(elem, block) {
return this._findBlocks('find', elem, block);
},
/**
* Finds the first block inside the current block or its elements (including context)
* @param {String|jQuery} [elem] Block element
* @param {String|Object} block Name or description (block,modName,modVal) of the block to find
* @returns {BEMDOM}
*/
findBlockInside : function(elem, block) {
return this._findBlocks('find', elem, block, true);
},
/**
* Finds blocks outside the current block or its elements (including context)
* @param {String|jQuery} [elem] Block element
* @param {String|Object} block Name or description (block,modName,modVal) of the block to find
* @returns {BEMDOM[]}
*/
findBlocksOutside : function(elem, block) {
return this._findBlocks('parents', elem, block);
},
/**
* Finds the first block outside the current block or its elements (including context)
* @param {String|jQuery} [elem] Block element
* @param {String|Object} block Name or description (block,modName,modVal) of the block to find
* @returns {BEMDOM}
*/
findBlockOutside : function(elem, block) {
return this._findBlocks('closest', elem, block)[0] || null;
},
/**
* Finds blocks on DOM elements of the current block or its elements
* @param {String|jQuery} [elem] Block element
* @param {String|Object} block Name or description (block,modName,modVal) of the block to find
* @returns {BEMDOM[]}
*/
findBlocksOn : function(elem, block) {
return this._findBlocks('', elem, block);
},
/**
* Finds the first block on DOM elements of the current block or its elements
* @param {String|jQuery} [elem] Block element
* @param {String|Object} block Name or description (block,modName,modVal) of the block to find
* @returns {BEMDOM}
*/
findBlockOn : function(elem, block) {
return this._findBlocks('', elem, block, true);
},
_findBlocks : function(select, elem, block, onlyFirst) {
if(!block) {
block = elem;
elem = undef;
}
var ctxElem = elem?
(typeof elem === 'string'? this.findElem(elem) : elem) :
this.domElem,
isSimpleBlock = typeof block === 'string',
blockName = isSimpleBlock? block : (block.block || block.blockName),
selector = '.' +
(isSimpleBlock?
buildClass(blockName) :
buildClass(blockName, block.modName, block.modVal)) +
(onlyFirst? ':first' : ''),
domElems = ctxElem.filter(selector);
select && (domElems = domElems.add(ctxElem[select](selector)));
if(onlyFirst) {
return domElems[0]? initBlock(blockName, domElems.eq(0), undef, true)._init() : null;
}
var res = [],
uniqIds = {};
domElems.each(function(i, domElem) {
var block = initBlock(blockName, $(domElem), undef, true)._init();
if(!uniqIds[block._uniqId]) {
uniqIds[block._uniqId] = true;
res.push(block);
}
});
return res;
},
/**
* Adds an event handler for any DOM element
* @protected
* @param {jQuery} domElem DOM element where the event will be listened for
* @param {String|Object} event Event name or event object
* @param {Object} [data] Additional event data
* @param {Function} fn Handler function, which will be executed in the block's context
* @returns {BEMDOM} this
*/
bindToDomElem : function(domElem, event, data, fn) {
if(functions.isFunction(data)) {
fn = data;
data = undef;
}
fn?
domElem.bind(
this._buildEventName(event),
data,
$.proxy(fn, this)) :
objects.each(event, function(fn, event) {
this.bindToDomElem(domElem, event, data, fn);
}, this);
return this;
},
/**
* Adds an event handler to the document
* @protected
* @param {String|Object} event Event name or event object
* @param {Object} [data] Additional event data
* @param {Function} fn Handler function, which will be executed in the block's context
* @returns {BEMDOM} this
*/
bindToDoc : function(event, data, fn) {
this._needSpecialUnbind = true;
return this.bindToDomElem(doc, event, data, fn);
},
/**
* Adds an event handler to the window
* @protected
* @param {String|Object} event Event name or event object
* @param {Object} [data] Additional event data
* @param {Function} fn Handler function, which will be executed in the block's context
* @returns {BEMDOM} this
*/
bindToWin : function(event, data, fn) {
this._needSpecialUnbind = true;
return this.bindToDomElem(win, event, data, fn);
},
/**
* Adds an event handler to the block's main DOM elements or its nested elements
* @protected
* @param {jQuery|String} [elem] Element
* @param {String|Object} event Event name or event object
* @param {Object} [data] Additional event data
* @param {Function} fn Handler function, which will be executed in the block's context
* @returns {BEMDOM} this
*/
bindTo : function(elem, event, data, fn) {
var len = arguments.length;
if(len === 3) {
if(functions.isFunction(data)) {
fn = data;
if(typeof event === 'object') {
data = event;
event = elem;
elem = this.domElem;
}
}
} else if(len === 2) {
if(functions.isFunction(event)) {
fn = event;
event = elem;
elem = this.domElem;
} else if(!(typeof elem === 'string' || elem instanceof $)) {
data = event;
event = elem;
elem = this.domElem;
}
} else if(len === 1) {
event = elem;
elem = this.domElem;
}
typeof elem === 'string' && (elem = this.elem(elem));
return this.bindToDomElem(elem, event, data, fn);
},
/**
* Removes event handlers from any DOM element
* @protected
* @param {jQuery} domElem DOM element where the event was being listened for
* @param {String|Object} event Event name or event object
* @param {Function} [fn] Handler function
* @returns {BEMDOM} this
*/
unbindFromDomElem : function(domElem, event, fn) {
if(typeof event === 'string') {
event = this._buildEventName(event);
fn?
domElem.unbind(event, fn) :
domElem.unbind(event);
} else {
objects.each(event, function(fn, event) {
this.unbindFromDomElem(domElem, event, fn);
}, this);
}
return this;
},
/**
* Removes event handler from document
* @protected
* @param {String|Object} event Event name or event object
* @param {Function} [fn] Handler function
* @returns {BEMDOM} this
*/
unbindFromDoc : function(event, fn) {
return this.unbindFromDomElem(doc, event, fn);
},
/**
* Removes event handler from window
* @protected
* @param {String|Object} event Event name or event object
* @param {Function} [fn] Handler function
* @returns {BEMDOM} this
*/
unbindFromWin : function(event, fn) {
return this.unbindFromDomElem(win, event, fn);
},
/**
* Removes event handlers from the block's main DOM elements or its nested elements
* @protected
* @param {jQuery|String} [elem] Nested element
* @param {String|Object} event Event name or event object
* @param {Function} [fn] Handler function
* @returns {BEMDOM} this
*/
unbindFrom : function(elem, event, fn) {
var argLen = arguments.length;
if(argLen === 1) {
event = elem;
elem = this.domElem;
} else if(argLen === 2 && functions.isFunction(event)) {
fn = event;
event = elem;
elem = this.domElem;
} else if(typeof elem === 'string') {
elem = this.elem(elem);
}
return this.unbindFromDomElem(elem, event, fn);
},
/**
* Builds a full name for an event
* @private
* @param {String} event Event name
* @returns {String}
*/
_buildEventName : function(event) {
return event.indexOf(' ') > 1?
event.split(' ').map(function(e) {
return this._buildOneEventName(e);
}, this).join(' ') :
this._buildOneEventName(event);
},
/**
* Builds a full name for a single event
* @private
* @param {String} event Event name
* @returns {String}
*/
_buildOneEventName : function(event) {
var eventNameCache = this._eventNameCache;
if(event in eventNameCache) return eventNameCache[event];
var uniq = '.' + this._uniqId;
if(event.indexOf('.') < 0) return eventNameCache[event] = event + uniq;
var lego = '.bem_' + this.__self._name;
return eventNameCache[event] = event.split('.').map(function(e, i) {
return i === 0? e + lego : lego + '_' + e;
}).join('') + uniq;
},
_ctxEmit : function(e, data) {
this.__base.apply(this, arguments);
var _this = this,
storage = liveEventCtxStorage[_this.__self._buildCtxEventName(e.type)],
ctxIds = {};
storage && _this.domElem.each(function(_, ctx) {
var counter = storage.counter;
while(ctx && counter) {
var ctxId = identify(ctx, true);
if(ctxId) {
if(ctxIds[ctxId]) break;
var storageCtx = storage.ctxs[ctxId];
if(storageCtx) {
objects.each(storageCtx, function(handler) {
handler.fn.call(
handler.ctx || _this,
e,
data);
});
counter--;
}
ctxIds[ctxId] = true;
}
ctx = ctx.parentNode || domNodesToParents[ctxId];
}
});
},
/**
* Sets a modifier for a block/nested element
* @param {jQuery} [elem] Nested element
* @param {String} modName Modifier name
* @param {String} modVal Modifier value
* @returns {BEMDOM} this
*/
setMod : function(elem, modName, modVal) {
if(elem && typeof modVal !== 'undefined' && elem.length > 1) {
var _this = this;
elem.each(function() {
var item = $(this);
item.__bemElemName = elem.__bemElemName;
_this.setMod(item, modName, modVal);
});
return _this;
}
return this.__base(elem, modName, modVal);
},
/**
* Retrieves modifier value from the DOM node's CSS class
* @private
* @param {String} modName Modifier name
* @param {jQuery} [elem] Nested element
* @param {String} [elemName] Name of the nested element
* @returns {String} Modifier value
*/
_extractModVal : function(modName, elem, elemName) {
var domNode = (elem || this.domElem)[0],
matches;
domNode &&
(matches = domNode.className
.match(this.__self._buildModValRE(modName, elemName || elem)));
return matches? matches[2] || true : '';
},
/**
* Retrieves a name/value list of modifiers
* @private
* @param {Array} [modNames] Names of modifiers
* @param {Object} [elem] Element
* @returns {Object} Hash of modifier values by names
*/
_extractMods : function(modNames, elem) {
var res = {},
extractAll = !modNames.length,
countMatched = 0;
((elem || this.domElem)[0].className
.match(this.__self._buildModValRE(
'(' + (extractAll? NAME_PATTERN : modNames.join('|')) + ')',
elem,
'g')) || []).forEach(function(className) {
var matches = className.match(EXTRACT_MODS_RE);
res[matches[1]] = matches[2] || true;
++countMatched;
});
// empty modifier values are not reflected in classes; they must be filled with empty values
countMatched < modNames.length && modNames.forEach(function(modName) {
modName in res || (res[modName] = '');
});
return res;
},
/**
* Sets a modifier's CSS class for a block's DOM element or nested element
* @private
* @param {String} modName Modifier name
* @param {String} modVal Modifier value
* @param {String} oldModVal Old modifier value
* @param {jQuery} [elem] Element
* @param {String} [elemName] Element name
*/
_onSetMod : function(modName, modVal, oldModVal, elem, elemName) {
if(modName !== 'js' || modVal !== '') {
var _self = this.__self,
classPrefix = _self._buildModClassPrefix(modName, elemName),
classRE = _self._buildModValRE(modName, elemName),
needDel = modVal === '' || modVal === false;
(elem || this.domElem).each(function() {
var className = this.className,
modClassName = classPrefix;
modVal !== true && (modClassName += MOD_DELIM + modVal);
(oldModVal === true?
classRE.test(className) :
className.indexOf(classPrefix + MOD_DELIM) > -1)?
this.className = className.replace(
classRE,
(needDel? '' : '$1' + modClassName)) :
needDel || $(this).addClass(modClassName);
});
elemName && this
.dropElemCache(elemName, modName, oldModVal)
.dropElemCache(elemName, modName, modVal);
}
this.__base.apply(this, arguments);
},
/**
* Finds elements nested in a block
* @param {jQuery} [ctx=this.domElem] Element where search is being performed
* @param {String} names Nested element name (or names separated by spaces)
* @param {String} [modName] Modifier name
* @param {String} [modVal] Modifier value
* @param {Boolean} [strictMode=false]
* @returns {jQuery} DOM elements
*/
findElem : function(ctx, names, modName, modVal, strictMode) {
if(typeof ctx === 'string') {
strictMode = modVal;
modVal = modName;
modName = names;
names = ctx;
ctx = this.domElem;
}
if(typeof modName === 'boolean') {
strictMode = modName;
modName = undef;
}
var _self = this.__self,
selector = '.' +
names.split(' ').map(function(name) {
return _self.buildClass(name, modName, modVal);
}).join(',.'),
res = findDomElem(ctx, selector);
return strictMode? this._filterFindElemResults(res) : res;
},
/**
* Filters results of findElem helper execution in strict mode
* @param {jQuery} res DOM elements
* @returns {jQuery} DOM elements
*/
_filterFindElemResults : function(res) {
var blockSelector = this.buildSelector(),
domElem = this.domElem;
return res.filter(function() {
return domElem.index($(this).closest(blockSelector)) > -1;
});
},
/**
* Finds elements nested in a block
* @private
* @param {String} name Nested element name
* @param {String} [modName] Modifier name
* @param {String} [modVal] Modifier value
* @returns {jQuery} DOM elements
*/
_elem : function(name, modName, modVal) {
var key = name + buildModPostfix(modName, modVal),
res;
if(!(res = this._elemCache[key])) {
res = this._elemCache[key] = this.findElem(name, modName, modVal);
res.__bemElemName = name;
}
return res;
},
/**
* Lazy search for elements nested in a block (caches results)
* @param {String} names Nested element name (or names separated by spaces)
* @param {String} [modName] Modifier name
* @param {String} [modVal] Modifier value
* @returns {jQuery} DOM elements
*/
elem : function(names, modName, modVal) {
if(modName && typeof modName !== 'string') {
modName.__bemElemName = names;
return modName;
}
if(names.indexOf(' ') < 0) {
return this._elem(names, modName, modVal);
}
var res = $([]);
names.split(' ').forEach(function(name) {
res = res.add(this._elem(name, modName, modVal));
}, this);
return res;
},
/**
* Finds elements outside the context
* @param {jQuery} ctx context
* @param {String} elemName Element name
* @returns {jQuery} DOM elements
*/
closestElem : function(ctx, elemName) {
return ctx.closest(this.buildSelector(elemName));
},
/**
* Clearing the cache for elements
* @protected
* @param {String} [names] Nested element name (or names separated by spaces)
* @param {String} [modName] Modifier name
* @param {String} [modVal] Modifier value
* @returns {BEMDOM} this
*/
dropElemCache : function(names, modName, modVal) {
if(names) {
var modPostfix = buildModPostfix(modName, modVal);
names.indexOf(' ') < 0?
delete this._elemCache[names + modPostfix] :
names.split(' ').forEach(function(name) {
delete this._elemCache[name + modPostfix];
}, this);
} else {
this._elemCache = {};
}
return this;
},
/**
* Retrieves parameters of a block element
* @param {String|jQuery} elem Element
* @returns {Object} Parameters
*/
elemParams : function(elem) {
var elemName;
if(typeof elem === 'string') {
elemName = elem;
elem = this.elem(elem);
} else {
elemName = this.__self._extractElemNameFrom(elem);
}
return extractParams(elem[0])[this.__self.buildClass(elemName)] || {};
},
/**
* Elemify given element
* @param {jQuery} elem Element
* @param {String} elemName Name
* @returns {jQuery}
*/
elemify : function(elem, elemName) {
(elem = $(elem)).__bemElemName = elemName;
return elem;
},
/**
* Checks whether a DOM element is in a block
* @protected
* @param {jQuery} [ctx=this.domElem] Element where check is being performed
* @param {jQuery} domElem DOM element
* @returns {Boolean}
*/
containsDomElem : function(ctx, domElem) {
if(arguments.length === 1) {
domElem = ctx;
ctx = this.domElem;
}
return dom.contains(ctx, domElem);
},
/**
* Builds a CSS selector corresponding to a block/element and modifier
* @param {String} [elem] Element name
* @param {String} [modName] Modifier name
* @param {String} [modVal] Modifier value
* @returns {String}
*/
buildSelector : function(elem, modName, modVal) {
return this.__self.buildSelector(elem, modName, modVal);
},
/**
* Destructs a block
* @private
*/
_destruct : function() {
var _this = this,
_self = _this.__self;
_this._needSpecialUnbind && _self.doc.add(_self.win).unbind('.' + _this._uniqId);
_this.__base();
delete uniqIdToBlock[_this.un()._uniqId];
}
}, /** @lends BEMDOM */{
/**
* Scope, will be set on onDomReady to `<body>`
* @type jQuery
*/
scope : null,
/**
* Document shortcut
* @type jQuery
*/
doc : doc,
/**
* Window shortcut
* @type jQuery
*/
win : win,
/**
* 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) {
var res = this._liveInitable;
if('live' in this) {
var noLive = typeof res === 'undefined';
if(noLive ^ heedLive) { // should be opposite to each other
res = this.live() !== false;
var blockName = this.getName(),
origLive = this.live;
this.live = function() {
return this.getName() === blockName?
res :
origLive.apply(this, arguments);
};
}
}
return res;
},
/**
* Initializes blocks on a fragment of the DOM tree
* @param {jQuery|String} [ctx=scope] Root DOM node
* @returns {jQuery} ctx Initialization context
*/
init : function(ctx) {
if(typeof ctx === 'string') {
ctx = $(ctx);
} else if(!ctx) ctx = DOM.scope;
var uniqInitId = identify();
findDomElem(ctx, BEM_SELECTOR).each(function() {
initBlocks($(this), uniqInitId);
});
this._runInitFns();
return ctx;
},
/**
* Destroys blocks on a fragment of the DOM tree
* @param {jQuery} ctx Root DOM node
* @param {Boolean} [excludeSelf=false] Exclude the main domElem
*/
destruct : function(ctx, excludeSelf) {
var _ctx;
if(excludeSelf) {
storeDomNodeParents(_ctx = ctx.children());
ctx.empty();
} else {
storeDomNodeParents(_ctx = ctx);
ctx.remove();
}
reverse.call(findDomElem(_ctx, BEM_SELECTOR)).each(function(_, domNode) {
var params = getParams(domNode);
objects.each(params, function(blockParams) {
if(blockParams.uniqId) {
var block = uniqIdToBlock[blockParams.uniqId];
block?
removeDomNodeFromBlock(block, domNode) :
delete uniqIdToDomElems[blockParams.uniqId];
}
});
delete domElemToParams[identify(domNode)];
});
// flush parent nodes storage that has been filled above
domNodesToParents = {};
},
/**
* Replaces a fragment of the DOM tree inside the context, destroying old blocks and intializing new ones
* @param {jQuery} ctx Root DOM node
* @param {jQuery|String} content New content
* @returns {jQuery} Updated root DOM node
*/
update : function(ctx, content) {
this.destruct(ctx, true);
return this.init(ctx.html(content));
},
/**
* Changes a fragment of the DOM tree including the context and initializes blocks.
* @param {jQuery} ctx Root DOM node
* @param {jQuery|String} content Content to be added
* @returns {jQuery} New content
*/
replace : function(ctx, content) {
var prev = ctx.prev(),
parent = ctx.parent();
this.destruct(ctx);
return this.init(prev.length?
$(content).insertAfter(prev) :
$(content).prependTo(parent));
},
/**
* Adds a fragment of the DOM tree at the end of the context and initializes blocks
* @param {jQuery} ctx Root DOM node
* @param {jQuery|String} content Content to be added
* @returns {jQuery} New content
*/
append : function(ctx, content) {
return this.init($(content).appendTo(ctx));
},
/**
* Adds a fragment of the DOM tree at the beginning of the context and initializes blocks
* @param {jQuery} ctx Root DOM node
* @param {jQuery|String} content Content to be added
* @returns {jQuery} New content
*/
prepend : function(ctx, content) {
return this.init($(content).prependTo(ctx));
},
/**
* Adds a fragment of the DOM tree before the context and initializes blocks
* @param {jQuery} ctx Contextual DOM node
* @param {jQuery|String} content Content to be added
* @returns {jQuery} New content
*/
before : function(ctx, content) {
return this.init($(content).insertBefore(ctx));
},
/**
* Adds a fragment of the DOM tree after the context and initializes blocks
* @param {jQuery} ctx Contextual DOM node
* @param {jQuery|String} content Content to be added
* @returns {jQuery} New content
*/
after : function(ctx, content) {
return this.init($(content).insertAfter(ctx));
},
/**
* Builds a full name for a live event
* @private
* @param {String} e Event name
* @returns {String}
*/
_buildCtxEventName : function(e) {
return this._name + ':' + e;
},
_liveClassBind : function(className, e, callback, invokeOnInit) {
if(e.indexOf(' ') > -1) {
e.split(' ').forEach(function(e) {
this._liveClassBind(className, e, callback, invokeOnInit);
}, this);
} else {
var storage = liveClassEventStorage[e],
uniqId = identify(callback);
if(!storage) {
storage = liveClassEventStorage[e] = {};
DOM.scope.bind(e, $.proxy(this._liveClassTrigger, this));
}
storage = storage[className] || (storage[className] = { uniqIds : {}, fns : [] });
if(!(uniqId in storage.uniqIds)) {
storage.fns.push({ uniqId : uniqId, fn : this._buildLiveEventFn(callback, invokeOnInit) });
storage.uniqIds[uniqId] = storage.fns.length - 1;
}
}
return this;
},
_liveClassUnbind : function(className, e, callback) {
var storage = liveClassEventStorage[e];
if(storage) {
if(callback) {
if(storage = storage[className]) {
var uniqId = identify(callback);
if(uniqId in storage.uniqIds) {
var i = storage.uniqIds[uniqId],
len = storage.fns.length - 1;
storage.fns.splice(i, 1);
while(i < len) storage.uniqIds[storage.fns[i++].uniqId] = i - 1;
delete storage.uniqIds[uniqId];
}
}
} else {
delete storage[className];
}
}
return this;
},
_liveClassTrigger : function(e) {
var storage = liveClassEventStorage[e.type];
if(storage) {
var node = e.target, classNames = [];
for(var className in storage) {
classNames.push(className);
}
do {
var nodeClassName = ' ' + node.className + ' ', i = 0;
while(className = classNames[i++]) {
if(nodeClassName.indexOf(' ' + className + ' ') > -1) {
var j = 0, fns = storage[className].fns, fn, stopPropagationAndPreventDefault = false;
while(fn = fns[j++])
if(fn.fn.call($(node), e) === false) stopPropagationAndPreventDefault = true;
stopPropagationAndPreventDefault && e.preventDefault();
if(stopPropagationAndPreventDefault || e.isPropagationStopped()) return;
classNames.splice(--i, 1);
}
}
} while(classNames.length && (node = node.parentNode));
}
},
_buildLiveEventFn : function(callback, invokeOnInit) {
var _this = this;
return function(e) {
e.currentTarget = this;
var args = [
_this._name,
$(this).closest(_this.buildSelector()),
undef,
true
],
block = initBlock.apply(null, invokeOnInit? args.concat([callback, e]) : args);
if(block && !invokeOnInit && callback)
return callback.apply(block, arguments);
};
},
/**
* Helper for live initialization for an event on DOM elements of a block or its elements
* @protected
* @param {String} [elemName] Element name or names (separated by spaces)
* @param {String} event Event name
* @param {Function} [callback] Handler to call after successful initialization
*/
liveInitOnEvent : function(elemName, event, callback) {
return this.liveBindTo(elemName, event, callback, true);
},
/**
* Helper for subscribing to live events on DOM elements of a block or its elements
* @protected
* @param {String|Object} [to] Description (object with modName, modVal, elem) or name of the element or elements (space-separated)
* @param {String} event Event name
* @param {Function} [callback] Handler
*/
liveBindTo : function(to, event, callback, invokeOnInit) {
if(!event || functions.isFunction(event)) {
callback = event;
event = to;
to = undef;
}
if(!to || typeof to === 'string') {
to = { elem : to };
}
if(to.elem && to.elem.indexOf(' ') > 0) {
to.elem.split(' ').forEach(function(elem) {
this._liveClassBind(
this.buildClass(elem, to.modName, to.modVal),
event,
callback,
invokeOnInit);
}, this);
return this;
}
return this._liveClassBind(
this.buildClass(to.elem, to.modName, to.modVal),
event,
callback,
invokeOnInit);
},
/**
* Helper for unsubscribing from live events on DOM elements of a block or its elements
* @protected
* @param {String} [elem] Name of the element or elements (space-separated)
* @param {String} event Event name
* @param {Function} [callback] Handler
*/
liveUnbindFrom : function(elem, event, callback) {
if(!event || functions.isFunction(event)) {
callback = event;
event = elem;
elem = undef;
}
if(elem && elem.indexOf(' ') > 1) {
elem.split(' ').forEach(function(elem) {
this._liveClassUnbind(
this.buildClass(elem),
event,
callback);
}, this);
return this;
}
return this._liveClassUnbind(
this.buildClass(elem),
event,
callback);
},
/**
* Helper for live initialization when a different block is initialized
* @private
* @param {String} event Event name
* @param {String} blockName Name of the block that should trigger a reaction when initialized
* @param {Function} callback Handler to be called after successful initialization in the new block's context
* @param {String} findFnName Name of the method for searching
*/
_liveInitOnBlockEvent : function(event, blockName, callback, findFnName) {
var name = this._name;
blocks[blockName].on(event, function(e) {
var args = arguments,
blocks = e.target[findFnName](name);
callback && blocks.forEach(function(block) {
callback.apply(block, args);
});
});
return this;
},
/**
* Helper for live initialization for a different block's event on the current block's DOM element
* @protected
* @param {String} event Event name
* @param {String} blockName Name of the block that should trigger a reaction when initialized
* @param {Function} callback Handler to be called after successful initialization in the new block's context
*/
liveInitOnBlockEvent : function(event, blockName, callback) {
return this._liveInitOnBlockEvent(event, blockName, callback, 'findBlocksOn');
},
/**
* Helper for live initialization for a different block's event inside the current block
* @protected
* @param {String} event Event name
* @param {String} blockName Name of the block that should trigger a reaction when initialized
* @param {Function} [callback] Handler to be called after successful initialization in the new block's context
*/
liveInitOnBlockInsideEvent : function(event, blockName, callback) {
return this._liveInitOnBlockEvent(event, blockName, callback, 'findBlocksOutside');
},
/**
* Adds a live event handler to a block, based on a specified element where the event will be listened for
* @param {jQuery} [ctx] The element in which the event will be listened for
* @param {String} e Event name
* @param {Object} [data] Additional information that the handler gets as e.data
* @param {Function} fn Handler
* @param {Object} [fnCtx] Handler's context
*/
on : function(ctx, e, data, fn, fnCtx) {
return typeof ctx === 'object' && ctx.jquery?
this._liveCtxBind(ctx, e, data, fn, fnCtx) :
this.__base(ctx, e, data, fn);
},
/**
* Removes the live event handler from a block, based on a specified element where the event was being listened for
* @param {jQuery} [ctx] The element in which the event was being listened for
* @param {String} e Event name
* @param {Function} [fn] Handler
* @param {Object} [fnCtx] Handler context
*/
un : function(ctx, e, fn, fnCtx) {
return typeof ctx === 'object' && ctx.jquery?
this._liveCtxUnbind(ctx, e, fn, fnCtx) :
this.__base(ctx, e, fn);
},
/**
* Adds a live event handler to a block, based on a specified element where the event will be listened for
* @private
* @param {jQuery} ctx The element in which the event will be listened for
* @param {String} e Event name
* @param {Object} [data] Additional information that the handler gets as e.data
* @param {Function} fn Handler
* @param {Object} [fnCtx] Handler context
* @returns {BEMDOM} this
*/
_liveCtxBind : function(ctx, e, data, fn, fnCtx) {
if(typeof e === 'object') {
if(functions.isFunction(data) || functions.isFunction(fn)) { // mod change event
e = this._buildModEventName(e);
} else {
objects.each(e, function(fn, e) {
this._liveCtxBind(ctx, e, fn, data);
}, this);
return this;
}
}
if(functions.isFunction(data)) {
fnCtx = fn;
fn = data;
data = undef;
}
if(e.indexOf(' ') > -1) {
e.split(' ').forEach(function(e) {
this._liveCtxBind(ctx, e, data, fn, fnCtx);
}, this);
} else {
var ctxE = this._buildCtxEventName(e),
storage = liveEventCtxStorage[ctxE] ||
(liveEventCtxStorage[ctxE] = { counter : 0, ctxs : {} });
ctx.each(function() {
var ctxId = identify(this),
ctxStorage = storage.ctxs[ctxId];
if(!ctxStorage) {
ctxStorage = storage.ctxs[ctxId] = {};
++storage.counter;
}
ctxStorage[identify(fn) + (fnCtx? identify(fnCtx) : '')] = {
fn : fn,
data : data,
ctx : fnCtx
};
});
}
return this;
},
/**
* Removes a live event handler from a block, based on a specified element where the event was being listened for
* @private
* @param {jQuery} ctx The element in which the event was being listened for
* @param {String|Object} e Event name
* @param {Function} [fn] Handler
* @param {Object} [fnCtx] Handler context
*/
_liveCtxUnbind : function(ctx, e, fn, fnCtx) {
if(typeof e === 'object' && functions.isFunction(fn)) { // mod change event
e = this._buildModEventName(e);
}
var storage = liveEventCtxStorage[e = this._buildCtxEventName(e)];
if(storage) {
ctx.each(function() {
var ctxId = identify(this, true),
ctxStorage;
if(ctxId && (ctxStorage = storage.ctxs[ctxId])) {
fn && delete ctxStorage[identify(fn) + (fnCtx? identify(fnCtx) : '')];
if(!fn || objects.isEmpty(ctxStorage)) {
storage.counter--;
delete storage.ctxs[ctxId];
}
}
});
storage.counter || delete liveEventCtxStorage[e];
}
return this;
},
/**
* Retrieves the name of an element nested in a block
* @private
* @param {jQuery} elem Nested element
* @returns {String|undef}
*/
_extractElemNameFrom : function(elem) {
if(elem.__bemElemName) return elem.__bemElemName;
var matches = elem[0].className.match(this._buildElemNameRE());
return matches? matches[1] : undef;
},
/**
* Builds a prefix for the CSS class of a DOM element or nested element of the block, based on modifier name
* @private
* @param {String} modName Modifier name
* @param {jQuery|String} [elem] Element
* @returns {String}
*/
_buildModClassPrefix : function(modName, elem) {
return this._name +
(elem?
ELEM_DELIM + (typeof elem === 'string'? elem : this._extractElemNameFrom(elem)) :
'') +
MOD_DELIM + modName;
},
/**
* Builds a regular expression for extracting modifier values from a DOM element or nested element of a block
* @private
* @param {String} modName Modifier name
* @param {jQuery|String} [elem] Element
* @param {String} [quantifiers] Regular expression quantifiers
* @returns {RegExp}
*/
_buildModValRE : function(modName, elem, quantifiers) {
return new RegExp(
'(\\s|^)' +
this._buildModClassPrefix(modName, elem) +
'(?:' + MOD_DELIM + '(' + NAME_PATTERN + '))?(?=\\s|$)',
quantifiers);
},
/**
* Builds a regular expression for extracting names of elements nested in a block
* @private
* @returns {RegExp}
*/
_buildElemNameRE : function() {
return new RegExp(this._name + ELEM_DELIM + '(' + NAME_PATTERN + ')(?:\\s|$)');
},
/**
* Builds a CSS class corresponding to the block/element and modifier
* @param {String} [elem] Element name
* @param {String} [modName] Modifier name
* @param {String} [modVal] Modifier value
* @returns {String}
*/
buildClass : function(elem, modName, modVal) {
return buildClass(this._name, elem, modName, modVal);
},
/**
* Builds a CSS selector corresponding to the block/element and modifier
* @param {String} [elem] Element name
* @param {String} [modName] Modifier name
* @param {String} [modVal] Modifier value
* @returns {String}
*/
buildSelector : function(elem, modName, modVal) {
return '.' + this.buildClass(elem, modName, modVal);
}
});
/**
* Returns a block on a DOM element and initializes it if necessary
* @param {String} blockName Block name
* @param {Object} params Block parameters
* @returns {BEMDOM}
*/
$.fn.bem = function(blockName, params) {
return initBlock(blockName, this, params, true)._init();
};
// Set default scope after DOM ready
$(function() {
DOM.scope = $('body');
});
provide(DOM);
});
(function() {
var origDefine = modules.define;
modules.define = function(name, deps, decl) {
origDefine.apply(modules, arguments);
name !== 'i-bem__dom_init' && arguments.length > 2 && ~deps.indexOf('i-bem__dom') &&
modules.define('i-bem__dom_init', [name], function(provide, _, prev) {
provide(prev);
});
};
})();
/* end: ../../libs/bem-core/common.blocks/i-bem/__dom/i-bem__dom.js */
/* begin: ../../libs/bem-core/common.blocks/jquery/jquery.js */
/**
* @module jquery
* @description Provide jQuery (load if it does not exist).
*/
modules.define(
'jquery',
['loader_type_js', 'jquery__config'],
function(provide, loader, cfg) {
/* global jQuery */
function doProvide(preserveGlobal) {
/**
* @exports
* @type Function
*/
provide(preserveGlobal? jQuery : jQuery.noConflict(true));
}
typeof jQuery !== 'undefined'?
doProvide(true) :
loader(cfg.url, doProvide);
});
/* end: ../../libs/bem-core/common.blocks/jquery/jquery.js */
/* begin: ../../libs/bem-core/common.blocks/jquery/__config/jquery__config.js */
/**
* @module jquery__config
* @description Configuration for jQuery
*/
modules.define('jquery__config', function(provide) {
provide(/** @exports */{
/**
* URL for loading jQuery if it does not exist
*/
url : '//yastatic.net/jquery/2.1.3/jquery.min.js'
});
});
/* end: ../../libs/bem-core/common.blocks/jquery/__config/jquery__config.js */
/* begin: ../../libs/bem-core/desktop.blocks/jquery/__config/jquery__config.js */
/**
* @module jquery__config
* @description Configuration for jQuery
*/
modules.define(
'jquery__config',
['ua', 'objects'],
function(provide, ua, objects, base) {
provide(
ua.msie && parseInt(ua.version, 10) < 9?
objects.extend(
base,
{
url : '//yastatic.net/jquery/1.11.2/jquery.min.js'
}) :
base);
});
/* end: ../../libs/bem-core/desktop.blocks/jquery/__config/jquery__config.js */
/* begin: ../../libs/bem-core/desktop.blocks/ua/ua.js */
/**
* @module ua
* @description Detect some user agent features (works like jQuery.browser in jQuery 1.8)
* @see http://code.jquery.com/jquery-migrate-1.1.1.js
*/
modules.define('ua', function(provide) {
var ua = navigator.userAgent.toLowerCase(),
match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
/(webkit)[ \/]([\w.]+)/.exec(ua) ||
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
/(msie) ([\w.]+)/.exec(ua) ||
ua.indexOf('compatible') < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
[],
matched = {
browser : match[1] || '',
version : match[2] || '0'
},
browser = {};
if(matched.browser) {
browser[matched.browser] = true;
browser.version = matched.version;
}
if(browser.chrome) {
browser.webkit = true;
} else if(browser.webkit) {
browser.safari = true;
}
/**
* @exports
* @type Object
*/
provide(browser);
});
/* end: ../../libs/bem-core/desktop.blocks/ua/ua.js */
/* begin: ../../libs/bem-core/common.blocks/dom/dom.js */
/**
* @module dom
* @description some DOM utils
*/
modules.define('dom', ['jquery'], function(provide, $) {
provide(/** @exports */{
/**
* Checks whether a DOM elem is in a context
* @param {jQuery} ctx DOM elem where check is being performed
* @param {jQuery} domElem DOM elem to check
* @returns {Boolean}
*/
contains : function(ctx, domElem) {
var res = false;
domElem.each(function() {
var domNode = this;
do {
if(~ctx.index(domNode)) return !(res = true);
} while(domNode = domNode.parentNode);
return res;
});
return res;
},
/**
* Returns current focused DOM elem in document
* @returns {jQuery}
*/
getFocused : function() {
// "Error: Unspecified error." in iframe in IE9
try { return $(document.activeElement); } catch(e) {}
},
/**
* Checks whether a DOM element contains focus
* @param {jQuery} domElem
* @returns {Boolean}
*/
containsFocus : function(domElem) {
return this.contains(domElem, this.getFocused());
},
/**
* Checks whether a browser currently can set focus on DOM elem
* @param {jQuery} domElem
* @returns {Boolean}
*/
isFocusable : function(domElem) {
var domNode = domElem[0];
if(!domNode) return false;
if(domNode.hasAttribute('tabindex')) return true;
switch(domNode.tagName.toLowerCase()) {
case 'iframe':
return true;
case 'input':
case 'button':
case 'textarea':
case 'select':
return !domNode.disabled;
case 'a':
return !!domNode.href;
}
return false;
},
/**
* Checks whether a domElem is intended to edit text
* @param {jQuery} domElem
* @returns {Boolean}
*/
isEditable : function(domElem) {
var domNode = domElem[0];
if(!domNode) return false;
switch(domNode.tagName.toLowerCase()) {
case 'input':
var type = domNode.type;
return (type === 'text' || type === 'password') && !domNode.disabled && !domNode.readOnly;
case 'textarea':
return !domNode.disabled && !domNode.readOnly;
default:
return domNode.contentEditable === 'true';
}
}
});
});
/* end: ../../libs/bem-core/common.blocks/dom/dom.js */
/* begin: ../../libs/bem-core/common.blocks/i-bem/__dom/_init/i-bem__dom_init.js */
/**
* @module i-bem__dom_init
*/
modules.define('i-bem__dom_init', ['i-bem__dom'], function(provide, BEMDOM) {
provide(
/**
* Initializes blocks on a fragment of the DOM tree
* @exports
* @param {jQuery} [ctx=scope] Root DOM node
* @returns {jQuery} ctx Initialization context
*/
function(ctx) {
return BEMDOM.init(ctx);
});
});
/* end: ../../libs/bem-core/common.blocks/i-bem/__dom/_init/i-bem__dom_init.js */
/* begin: ./blocks/test/test.js */
modules.define('kg-menu', ['i-bem__dom'], function(provide, BEMDOM){
provide(BEMDOM.decl(this.name, {
onSetMod: {
js: {
inited: function() {
console.log('hello')
}
}
}
}));
});
/* end: ./blocks/test/test.js */
/* begin: ../../libs/bem-core/common.blocks/i-bem/__dom/_init/i-bem__dom_init_auto.js */
/**
* Auto initialization on DOM ready
*/
modules.require(
['i-bem__dom_init', 'jquery', 'next-tick'],
function(init, $, nextTick) {
$(function() {
nextTick(init);
});
});
/* end: ../../libs/bem-core/common.blocks/i-bem/__dom/_init/i-bem__dom_init_auto.js */
/* begin: ../../libs/bem-core/common.blocks/loader/_type/loader_type_js.js */
/**
* @module loader_type_js
* @description Load JS from external URL.
*/
modules.define('loader_type_js', function(provide) {
var loading = {},
loaded = {},
head = document.getElementsByTagName('head')[0],
runCallbacks = function(path, type) {
var cbs = loading[path], cb, i = 0;
delete loading[path];
while(cb = cbs[i++]) {
cb[type] && cb[type]();
}
},
onSuccess = function(path) {
loaded[path] = true;
runCallbacks(path, 'success');
},
onError = function(path) {
runCallbacks(path, 'error');
};
provide(
/**
* @exports
* @param {String} path resource link
* @param {Function} success to be called if the script succeeds
* @param {Function} error to be called if the script fails
*/
function(path, success, error) {
if(loaded[path]) {
success();
return;
}
if(loading[path]) {
loading[path].push({ success : success, error : error });
return;
}
loading[path] = [{ success : success, error : error }];
var script = document.createElement('script');
script.type = 'text/javascript';
script.charset = 'utf-8';
script.src = (location.protocol === 'file:' && !path.indexOf('//')? 'http:' : '') + path;
if('onload' in script) {
script.onload = function() {
script.onload = script.onerror = null;
onSuccess(path);
};
script.onerror = function() {
script.onload = script.onerror = null;
onError(path);
};
} else {
script.onreadystatechange = function() {
var readyState = this.readyState;
if(readyState === 'loaded' || readyState === 'complete') {
script.onreadystatechange = null;
onSuccess(path);
}
};
}
head.insertBefore(script, head.lastChild);
}
);
});
/* end: ../../libs/bem-core/common.blocks/loader/_type/loader_type_js.js */
<!DOCTYPE html><html class="ua_js_no"><head><meta http-equiv="X-UA-Compatible" content="IE=edge"/><meta charset="utf-8"/><title>noinit</title><script>(function(e,c){e[c]=e[c].replace(/(ua_js_)no/g,"$1yes");})(document.documentElement,"className");</script></head><body class="page"><div class="test test_foo_bar i-bem" data-bem="{&quot;test&quot;:{}}"></div><script src="_noinit.js"></script></body></html>
([
{
block: 'page',
content: [
{ block: 'test', mods: { foo: 'bar'}, js: true}
]
}
]);
module.exports = function(bh) {
bh.match('page', function(ctx){
ctx
.param('title', 'noinit')
.param('scripts', [
{ elem: 'js', url: '_noinit.js' },
]);
});
};
modules.define('kg-menu', ['i-bem__dom'], function(provide, BEMDOM){
provide(BEMDOM.decl(this.name, {
onSetMod: {
js: {
inited: function() {
console.log('hello')
}
}
}
}));
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment