Last active
August 29, 2015 14:14
-
-
Save Guria/6bb4a5dc7d97cba5fa67 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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 ? | |
'{"' + base + '":{}}' : | |
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 = { '"': '"', '&': '&', '<': '<', '>': '>' }, | |
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 */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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="{"test":{}}"></div><script src="_noinit.js"></script></body></html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
([ | |
{ | |
block: 'page', | |
content: [ | |
{ block: 'test', mods: { foo: 'bar'}, js: true} | |
] | |
} | |
]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module.exports = function(bh) { | |
bh.match('page', function(ctx){ | |
ctx | |
.param('title', 'noinit') | |
.param('scripts', [ | |
{ elem: 'js', url: '_noinit.js' }, | |
]); | |
}); | |
}; | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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