diff --git a/i-bem.js b/i-bem.js new file mode 100644 index 0000000..6f7e02c --- /dev/null +++ b/i-bem.js @@ -0,0 +1,3361 @@ +/*! + * @file i-bem — library to write client side with BEM methodology + * @version 2.0.4 + * @tutorial http://ru.bem.info/libs/bem-bl/dev/desktop.sets/i-bem/ + * @link https://github.com/bem-node/i-bem-doc + */ + +/** + * Inheritance plugin + * + * Copyright (c) 2010 Filatov Dmitry (dfilatov@yandex-team.ru) + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * @version 1.3.5 + */ + +(function($) { + +var hasIntrospection = (function(){'_';}).toString().indexOf('_') > -1, + emptyBase = function() {}, + objCreate = Object.create || function(ptp) { + var inheritance = function() {}; + inheritance.prototype = ptp; + return new inheritance(); + }, + 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 override(base, result, add) { + + var hasSpecProps = false; + if(needCheckProps) { + var addList = []; + $.each(specProps, function() { + add.hasOwnProperty(this) && (hasSpecProps = true) && addList.push({ + name : this, + val : add[this] + }); + }); + if(hasSpecProps) { + $.each(add, function(name) { + addList.push({ + name : name, + val : this + }); + }); + add = addList; + } + } + + $.each(add, function(name, prop) { + if(hasSpecProps) { + name = prop.name; + prop = prop.val; + } + if($.isFunction(prop) && + (!hasIntrospection || prop.toString().indexOf('.__base') > -1)) { + + var baseMethod = base[name] || function() {}; + result[name] = function() { + var baseSaved = this.__base; + this.__base = baseMethod; + var result = prop.apply(this, arguments); + this.__base = baseSaved; + return result; + }; + + } + else { + result[name] = prop; + } + + }); + +} + +$.inherit = function() { + + var args = arguments, + hasBase = $.isFunction(args[0]), + base = hasBase? args[0] : emptyBase, + props = args[hasBase? 1 : 0] || {}, + staticProps = args[hasBase? 2 : 1], + result = props.__constructor || (hasBase && base.prototype.__constructor)? + function() { + return this.__constructor.apply(this, arguments); + } : function() {}; + + if(!hasBase) { + result.prototype = props; + result.prototype.__self = result.prototype.constructor = result; + return $.extend(result, staticProps); + } + + $.extend(result, base); + + var basePtp = base.prototype, + resultPtp = result.prototype = objCreate(basePtp); + + resultPtp.__self = resultPtp.constructor = result; + + override(basePtp, resultPtp, props); + staticProps && override(base, result, staticProps); + + return result; + +}; + +$.inheritSelf = function(base, props, staticProps) { + + var basePtp = base.prototype; + + override(basePtp, basePtp, props); + staticProps && override(base, base, staticProps); + + return base; + +}; + +})(jQuery); + +/** + * Identify plugin + * + * @version 1.0.0 + */ + +(function($) { + +var counter = 0, + expando = '__' + (+new Date), + get = function() { + return 'uniq' + ++counter; + }; + +/** + * Makes unique ID + * @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 + */ +$.identify = 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(); + +}; + +})(jQuery); +(function($) { + +$.isEmptyObject || ($.isEmptyObject = function(obj) { + for(var i in obj) return false; + return true; + }); + +})(jQuery); + +/** + * Debounce and throttle function's decorator plugin 1.0.6 + * + * Copyright (c) 2009 Filatov Dmitry (alpha@zforms.ru) + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + */ + +(function($) { + +$.extend({ + + debounce : function(fn, timeout, invokeAsap, ctx) { + + if(arguments.length == 3 && typeof invokeAsap != 'boolean') { + ctx = invokeAsap; + invokeAsap = false; + } + + var timer; + + return function() { + + var args = arguments; + ctx = ctx || this; + + invokeAsap && !timer && fn.apply(ctx, args); + + clearTimeout(timer); + + timer = setTimeout(function() { + invokeAsap || fn.apply(ctx, args); + timer = null; + }, timeout); + + }; + + }, + + throttle : function(fn, timeout, ctx) { + + var timer, args, needInvoke; + + return function() { + + args = arguments; + needInvoke = true; + ctx = ctx || this; + + timer || (function() { + if(needInvoke) { + fn.apply(ctx, args); + needInvoke = false; + timer = setTimeout(arguments.callee, timeout); + } + else { + timer = null; + } + })(); + + }; + + } + +}); + +})(jQuery); +/** + * Observable plugin + * + * Copyright (c) 2010 Filatov Dmitry (alpha@zforms.ru) + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * @version 1.0.0 + * @requires $.identify + * @requires $.inherit + */ + +(function($) { + +var storageExpando = '__' + (+new Date) + 'storage', + getFnId = function(fn, ctx) { + return $.identify(fn) + (ctx? $.identify(ctx) : ''); + }, + Observable = /** @lends $.observable.prototype */{ + + /** + * Builds full event name + * @protected + * @param {String} e Event type + * @returns {String} + */ + buildEventName : function(e) { + + return e; + + }, + + /** + * Adding 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 {$.observable} + */ + on : function(e, data, fn, ctx, _special) { + + if(typeof e == 'string') { + if($.isFunction(data)) { + ctx = fn; + fn = data; + data = undefined; + } + + var id = getFnId(fn, ctx), + storage = this[storageExpando] || (this[storageExpando] = {}), + eList = e.split(' '), + i = 0, + eStorage; + + while(e = eList[i++]) { + e = this.buildEventName(e); + eStorage = storage[e] || (storage[e] = { ids : {}, list : {} }); + + if(!(id in eStorage.ids)) { + var list = eStorage.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; + } + + eStorage.ids[id] = list.last = item; + } + } + } else { + var _this = this; + $.each(e, function(e, fn) { + _this.on(e, fn, data, _special); + }); + } + + return this; + + }, + + onFirst : function(e, data, fn, ctx) { + + return this.on(e, data, fn, ctx, { one : true }); + + }, + + /** + * Removing event handler(s) + * @param {String} [e] Event type + * @param {Function} [fn] Handler + * @param {Object} [ctx] Handler context + * @returns {$.observable} + */ + 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 eList = e.split(' '), + i = 0, + eStorage; + while(e = eList[i++]) { + e = this.buildEventName(e); + if(eStorage = storage[e]) { + if(fn) { // if specific handler was passed + var id = getFnId(fn, ctx), + ids = eStorage.ids; + if(id in ids) { + var list = eStorage.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 { + var _this = this; + $.each(e, function(e, fn) { + _this.un(e, fn, ctx); + }); + } + + return this; + + }, + + /** + * Fires event handlers + * @param {String|$.Event} e Event + * @param {Object} [data] Additional data + * @returns {$.observable} + */ + trigger : function(e, data) { + + var _this = this, + storage = _this[storageExpando], + rawType; + + typeof e === 'string'? + e = $.Event(_this.buildEventName(rawType = e)) : + e.type = _this.buildEventName(rawType = e.type); + + e.target || (e.target = _this); + + if(storage && (storage = storage[e.type])) { + var item = storage.list.first, + ret; + while(item) { + e.data = item.data; + ret = item.fn.call(item.ctx || _this, e, data); + if(typeof ret !== 'undefined') { + e.result = ret; + if(ret === false) { + e.preventDefault(); + e.stopPropagation(); + } + } + + item.special && item.special.one && + _this.un(rawType, item.fn, item.ctx); + item = item.next; + } + } + + return this; + + } + + }; + +$.observable = $.inherit(Observable, Observable); + +})(jQuery); +/** @requires jquery.inherit */ +/** @requires jquery.isEmptyObject */ +/** @requires jquery.identify */ +/** @requires jquery.observable */ + +(function($, undefined) { + +/** + * Storage for deferred functions + * @private + * @type Array + */ +var afterCurrentEventFns = [], + +/** + * Storage for block declarations (hash by block name) + * @private + * @type Object + */ + blocks = {}, + +/** + * Communication channels + * @static + * @private + * @type Object + */ + channels = {}; + +/** + * Builds the name of the handler method for setting a modifier + * @static + * @private + * @param {String} elemName Element name + * @param {String} modName Modifier name + * @param {String} modVal Modifier value + * @returns {String} + */ +function buildModFnName(elemName, modName, modVal) { + + return (elemName? '__elem_' + elemName : '') + + '__mod' + + (modName? '_' + modName : '') + + (modVal? '_' + modVal : ''); + +} + +/** + * Transforms a hash of modifier handlers to methods + * @static + * @private + * @param {Object} modFns + * @param {Object} props + * @param {String} [elemName] + */ +function modFnsToProps(modFns, props, elemName) { + + $.isFunction(modFns)? + (props[buildModFnName(elemName, '*', '*')] = modFns) : + $.each(modFns, function(modName, modFn) { + $.isFunction(modFn)? + (props[buildModFnName(elemName, modName, '*')] = modFn) : + $.each(modFn, function(modVal, modFn) { + props[buildModFnName(elemName, modName, modVal)] = modFn; + }); + }); + +} + +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); + }; + +} + +/** @namespace */ +this.BEM = $.inherit($.observable, /** @lends BEM.prototype */ { + + /** + * @class Base block for creating BEM blocks + * @constructs + * @private + * @param {Object} mods Block modifiers + * @param {Object} params Block parameters + * @param {Boolean} [initImmediately=true] + */ + __constructor : function(mods, params, initImmediately) { + + var _this = this; + + /** + * Cache of block modifiers + * @private + * @type Object + */ + _this._modCache = mods || {}; + + /** + * Current modifiers in the stack + * @private + * @type Object + */ + _this._processingMods = {}; + + /** + * The block's parameters, taking into account the defaults + * @protected + * @type Object + */ + _this._params = params; // это нужно для правильной сборки параметров у блока из нескольких нод + _this.params = null; + + initImmediately !== false? + _this._init() : + _this.afterCurrentEvent(function() { + _this._init(); + }); + + }, + + /** + * Initializes the block + * @private + */ + _init : function() { + + if(!this._initing && !this.hasMod('js', 'inited')) { + this._initing = true; + + if(!this.params) { + this.params = $.extend(this.getDefaultParams(), this._params); + delete this._params; + } + + this.setMod('js', 'inited'); + delete this._initing; + this.hasMod('js', 'inited') && this.trigger('init'); + } + + return this; + + }, + + /** + * Changes the context of the function being passed + * @protected + * @param {Function} fn + * @param {Object} [ctx=this] Context + * @returns {Function} Function with a modified context + */ + changeThis : function(fn, ctx) { + + return fn.bind(ctx || this); + + }, + + /** + * Executes the function in the context of the block, after the "current event" + * @protected + * @param {Function} fn + * @param {Object} [ctx] Context + */ + afterCurrentEvent : function(fn, ctx) { + + this.__self.afterCurrentEvent(this.changeThis(fn, ctx)); + + }, + + /** + * Executes the block's event handlers and live event handlers + * @protected + * @param {String} e Event name + * @param {Object} [data] Additional information + * @returns {BEM} + */ + trigger : function(e, data) { + + this + .__base(e = this.buildEvent(e), data) + .__self.trigger(e, data); + + return this; + + }, + + buildEvent : function(e) { + + typeof e == 'string' && (e = $.Event(e)); + e.block = this; + + return e; + + }, + + /** + * Checks whether a block or nested element has a modifier + * @protected + * @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 = undefined; + invert = true; + } + else if(len == 2) { + if(typeof elem == 'string') { + modVal = modName; + modName = elem; + elem = undefined; + } + 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 + * @protected + * @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} [elem] 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 + * @protected + * @param {Object} [elem] Nested element + * @param {String} [modName1, ..., modNameN] Modifier names + * @returns {Object} Hash of modifier values + */ + getMods : function(elem) { + + var hasElem = elem && typeof elem != 'string', + _this = this, + modNames = [].slice.call(arguments, hasElem? 1 : 0), + res = _this._extractMods(modNames, hasElem? elem : undefined); + + if(!hasElem) { // caching + modNames.length? + modNames.forEach(function(name) { + _this._modCache[name] = res[name]; + }): + _this._modCache = res; + } + + return res; + + }, + + /** + * Sets the modifier for a block/nested element + * @protected + * @param {Object} [elem] Nested element + * @param {String} modName Modifier name + * @param {String} modVal Modifier value + * @returns {BEM} + */ + setMod : function(elem, modName, modVal) { + + if(typeof modVal == 'undefined') { + modVal = modName; + modName = elem; + elem = undefined; + } + + var _this = this; + + if(!elem || elem[0]) { + + 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); + + [['*', '*'], [modName, '*'], [modName, modVal]].forEach(function(mod) { + needSetMod = _this._callModFn(elemName, mod[0], mod[1], modFnParams) !== false && needSetMod; + }); + + !elem && needSetMod && (_this._modCache[modName] = modVal); + + needSetMod && _this._afterSetMod(modName, modVal, curModVal, elem, elemName); + + delete this._processingMods[modId]; + } + + 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 + */ + _afterSetMod : function(modName, modVal, oldModVal, elem, elemName) {}, + + /** + * 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. + * @protected + * @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} + */ + 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 = undefined; + } + 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} + */ + delMod : function(elem, modName) { + + if(!modName) { + modName = elem; + elem = undefined; + } + + return this.setMod(elem, modName, ''); + + }, + + /** + * Executes handlers for setting modifiers + * @private + * @param {String} elemName Element name + * @param {String} modName Modifier name + * @param {String} modVal Modifier value + * @param {Array} modFnParams Handler parameters + */ + _callModFn : function(elemName, modName, modVal, modFnParams) { + + var modFnName = buildModFnName(elemName, modName, modVal); + return this[modFnName]? + this[modFnName].apply(this, modFnParams) : + undefined; + + }, + + /** + * 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 named communication channel + * @param {String} [id='default'] Channel ID + * @param {Boolean} [drop=false] Destroy the channel + * @returns {$.observable|undefined} Communication channel + */ + channel : function(id, drop) { + + return this.__self.channel(id, drop); + + }, + + /** + * Returns a block's default parameters + * @returns {Object} + */ + getDefaultParams : function() { + + return {}; + + }, + + /** + * Helper for cleaning up block properties + * @param {Object} [obj=this] + */ + del : function(obj) { + + var args = [].slice.call(arguments); + typeof obj == 'string' && args.unshift(this); + this.__self.del.apply(this.__self, args); + return this; + + }, + + /** + * Deletes a block + */ + destruct : function() {} + +}, { + + _name : 'i-bem', + + /** + * Storage for block declarations (hash by block name) + * @static + * @protected + * @type Object + */ + blocks : blocks, + + /** + * Declares blocks and creates a block class + * @static + * @protected + * @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 {String} [decl.modName] Modifier name + * @param {String} [decl.modVal] Modifier value + * @param {Object} [props] Methods + * @param {Object} [staticProps] Static methods + */ + decl : function(decl, props, staticProps) { + + if(typeof decl == 'string') + decl = { block : decl }; + else if(decl.name) { + decl.block = decl.name; + } + + if(decl.baseBlock && !blocks[decl.baseBlock]) + throw('baseBlock "' + decl.baseBlock + '" for "' + decl.block + '" is undefined'); + + props || (props = {}); + + if(props.onSetMod) { + modFnsToProps(props.onSetMod, props); + delete props.onSetMod; + } + + if(props.onElemSetMod) { + $.each(props.onElemSetMod, function(elemName, modFns) { + modFnsToProps(modFns, props, elemName); + }); + delete props.onElemSetMod; + } + + var baseBlock = blocks[decl.baseBlock || decl.block] || this; + + if(decl.modName) { + var checkMod = buildCheckMod(decl.modName, decl.modVal); + $.each(props, function(name, prop) { + $.isFunction(prop) && + (props[name] = function() { + var method; + if(checkMod(this)) { + method = prop; + } else { + var baseMethod = baseBlock.prototype[name]; + baseMethod && baseMethod !== props[name] && + (method = this.__base); + } + return method? + method.apply(this, arguments) : + undefined; + }); + }); + } + + if(staticProps && typeof staticProps.live === 'boolean') { + var live = staticProps.live; + staticProps.live = function() { + return live; + }; + } + + var block; + if(decl.block == baseBlock._name) { + // makes a new "live" if the old one was already executed + (block = $.inheritSelf(baseBlock, props, staticProps))._processLive(true); + } else { + (block = blocks[decl.block] = $.inherit(baseBlock, props, staticProps))._name = decl.block; + delete block._liveInitable; + } + + return block; + + }, + + /** + * 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 + * @static + * @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 + * @static + * @protected + * @returns {String} + */ + getName : function() { + + return this._name; + + }, + + /** + * Retrieves the name of an element nested in a block + * @static + * @private + * @param {Object} elem Nested element + * @returns {String|undefined} + */ + _extractElemNameFrom : function(elem) {}, + + /** + * Adds a function to the queue for executing after the "current event" + * @static + * @protected + * @param {Function} fn + * @param {Object} ctx + */ + afterCurrentEvent : function(fn, ctx) { + + afterCurrentEventFns.push({ fn : fn, ctx : ctx }) == 1 && + setTimeout(this._runAfterCurrentEventFns, 0); + + }, + + /** + * Executes the queue + * @private + */ + _runAfterCurrentEventFns : function() { + + var fnsLen = afterCurrentEventFns.length; + if(fnsLen) { + var fnObj, + fnsCopy = afterCurrentEventFns.splice(0, fnsLen); + + while(fnObj = fnsCopy.shift()) fnObj.fn.call(fnObj.ctx || this); + } + + }, + + /** + * Changes the context of the function being passed + * @protected + * @param {Function} fn + * @param {Object} ctx Context + * @returns {Function} Function with a modified context + */ + changeThis : function(fn, ctx) { + + return fn.bind(ctx || this); + + }, + + /** + * Helper for cleaning out properties + * @param {Object} [obj=this] + */ + del : function(obj) { + + var delInThis = typeof obj == 'string', + i = delInThis? 0 : 1, + len = arguments.length; + delInThis && (obj = this); + + while(i < len) delete obj[arguments[i++]]; + + return this; + + }, + + /** + * Returns/destroys a named communication channel + * @param {String} [id='default'] Channel ID + * @param {Boolean} [drop=false] Destroy the channel + * @returns {$.observable|undefined} Communication channel + */ + channel : function(id, drop) { + + if(typeof id == 'boolean') { + drop = id; + id = undefined; + } + + id || (id = 'default'); + + if(drop) { + if(channels[id]) { + channels[id].un(); + delete channels[id]; + } + return; + } + + return channels[id] || (channels[id] = new $.observable()); + + } + +}); + +})(jQuery); + +/** + * Block i-ecma. Shim for some ES5 methods + * + * @block i-ecma + */ +(function() { + +/** + * Возвращает массив свойств объекта + * + * @param {Object} obj объект + * @returns {Array} + */ +Object.keys || (Object.keys = function(obj) { + var res = []; + + for(var i in obj) obj.hasOwnProperty(i) && + res.push(i); + + return res; +}); + +})(); + +(function() { + +var ptp = Array.prototype, + toStr = Object.prototype.toString, + methods = { + + /** + * Finds the index of an element in an array + * + * @param {Object} item + * @param {Number} [fromIdx] Starting from index (length - 1 - fromIdx, if fromIdx < 0) + * @returns {Number} Element index or -1, if not found + */ + indexOf : function(item, fromIdx) { + + fromIdx = +(fromIdx || 0); + + var t = this, len = t.length; + + if(len > 0 && fromIdx < len) { + fromIdx = fromIdx < 0? Math.ceil(fromIdx) : Math.floor(fromIdx); + fromIdx < -len && (fromIdx = 0); + fromIdx < 0 && (fromIdx = fromIdx + len); + + while(fromIdx < len) { + if(fromIdx in t && t[fromIdx] === item) + return fromIdx; + ++fromIdx; + } + } + + return -1; + + }, + + /** + * Calls the callback for each element + * + * @param {Function} callback Called for each element + * @param {Object} [ctx=null] Callback context + */ + forEach : function(callback, ctx) { + + var i = -1, t = this, len = t.length; + while(++i < len) i in t && + (ctx? callback.call(ctx, t[i], i, t) : callback(t[i], i, t)); + + }, + + /** + * Creates array B from array A so that B[i] = callback(A[i]) + * @param {Function} callback Called for each element + * @param {Object} [ctx=null] Callback context + * @returns {Array} + */ + map : function(callback, ctx) { + + var i = -1, t = this, len = t.length, + res = new Array(len); + + while(++i < len) i in t && + (res[i] = ctx? callback.call(ctx, t[i], i, t) : callback(t[i], i, t)); + + return res; + + }, + + /** + * Creates an array containing only the elements from the source array that the callback returns true for. + * @param {Function} callback Called for each element + * @param {Object} [ctx] Callback context + * @returns {Array} + */ + filter : function(callback, ctx) { + + var i = -1, t = this, len = t.length, + res = []; + + while(++i < len) i in t && + (ctx? callback.call(ctx, t[i], i, t) : callback(t[i], i, t)) && res.push(t[i]); + + return res; + + }, + + /** + * Wraps the array using an accumulator + * @param {Function} callback Called for each element + * @param {Object} [initialVal] Initial value of the accumulator + * @returns {Object} Accumulator + */ + reduce : function(callback, initialVal) { + + var i = -1, t = this, len = t.length, + res; + + if(arguments.length < 2) { + while(++i < len) { + if(i in t) { + res = t[i]; + break; + } + } + } + else { + res = initialVal; + } + + while(++i < len) i in t && + (res = callback(res, t[i], i, t)); + + return res; + + }, + + /** + * Checks whether at least one element in the array meets the condition in the callback + * @param {Function} callback + * @param {Object} [ctx=this] Callback context + * @returns {Boolean} + */ + some : function(callback, ctx) { + + var i = -1, t = this, len = t.length; + + while(++i < len) + if(i in t && (ctx ? callback.call(ctx, t[i], i, t) : callback(t[i], i, t))) + return true; + + return false; + + }, + + /** + * Checks whether every element in the array meets the condition in the callback + * @param {Function} callback + * @param {Object} [ctx=this] Context of the callback call + * @returns {Boolean} + */ + every : function(callback, ctx) { + + var i = -1, t = this, len = t.length; + + while(++i < len) + if(i in t && !(ctx ? callback.call(ctx, t[i], i, t) : callback(t[i], i, t))) + return false; + + return true; + + } + + }; + +for(var name in methods) + ptp[name] || (ptp[name] = methods[name]); + +Array.isArray || (Array.isArray = function(obj) { + return toStr.call(obj) === '[object Array]'; +}); + +})(); + +(function() { + +var slice = Array.prototype.slice; + +Function.prototype.bind || (Function.prototype.bind = function(ctx) { + + var fn = this, + args = slice.call(arguments, 1); + + return function () { + return fn.apply(ctx, args.concat(slice.call(arguments))); + } + +}); + +})(); +/** @fileOverview Module for internal BEM helpers */ +/** @requires BEM */ + +(function(BEM, $, undefined) { + +/** + * Separator for modifiers and their values + * @const + * @type String + */ +var 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 buildModPostfix(modName, modVal, buffer) { + + buffer.push(MOD_DELIM, modName, MOD_DELIM, modVal); + +} + +function buildBlockClass(name, modName, modVal, buffer) { + + buffer.push(name); + modVal && buildModPostfix(modName, modVal, buffer); + +} + +function buildElemClass(block, name, modName, modVal, buffer) { + + buildBlockClass(block, undefined, undefined, buffer); + buffer.push(ELEM_DELIM, name); + modVal && buildModPostfix(modName, modVal, buffer); + +} + +BEM.INTERNAL = { + + NAME_PATTERN : NAME_PATTERN, + + MOD_DELIM : MOD_DELIM, + ELEM_DELIM : ELEM_DELIM, + + buildModPostfix : function(modName, modVal, buffer) { + + var res = buffer || []; + buildModPostfix(modName, modVal, res); + return buffer? res : res.join(''); + + }, + + /** + * Builds the class of a block or element with a modifier + * @private + * @param {String} block Block name + * @param {String} [elem] Element name + * @param {String} [modName] Modifier name + * @param {String} [modVal] Modifier value + * @param {Array} [buffer] Buffer + * @returns {String|Array} Class or buffer string (depending on whether the buffer parameter is present) + */ + buildClass : function(block, elem, modName, modVal, buffer) { + + var typeOf = typeof modName; + if(typeOf == 'string') { + if(typeof modVal != 'string' && typeof modVal != 'number') { + buffer = modVal; + modVal = modName; + modName = elem; + elem = undefined; + } + } else if(typeOf != 'undefined') { + buffer = modName; + modName = undefined; + } else if(elem && typeof elem != 'string') { + buffer = elem; + elem = undefined; + } + + if(!(elem || modName || buffer)) { // оптимизация для самого простого случая + return block; + } + + var res = buffer || []; + + elem? + buildElemClass(block, elem, modName, modVal, res) : + buildBlockClass(block, modName, modVal, res); + + return buffer? res : res.join(''); + + }, + + /** + * Builds full classes for a buffer or element with modifiers + * @private + * @param {String} block Block name + * @param {String} [elem] Element name + * @param {Object} [mods] Modifiers + * @param {Array} [buffer] Buffer + * @returns {String|Array} Class or buffer string (depending on whether the buffer parameter is present) + */ + buildClasses : function(block, elem, mods, buffer) { + + if(elem && typeof elem != 'string') { + buffer = mods; + mods = elem; + elem = undefined; + } + + var res = buffer || []; + + elem? + buildElemClass(block, elem, undefined, undefined, res) : + buildBlockClass(block, undefined, undefined, res); + + mods && $.each(mods, function(modName, modVal) { + if(modVal) { + res.push(' '); + elem? + buildElemClass(block, elem, modName, modVal, res) : + buildBlockClass(block, modName, modVal, res); + } + }); + + return buffer? res : res.join(''); + + /*var typeOf = typeof elem; + if(typeOf != 'string' && typeOf != 'undefined') { + buffer = mods; + mods = elem; + elem = undefined; + } + if($.isArray(mods)) { + buffer = mods; + mods = undefined; + } + + var res = buffer || []; + buildClasses(block, elem, mods, res); + return buffer? res : res.join('');*/ + + } + +} + +})(BEM, jQuery); +/** @requires BEM */ +/** @requires BEM.INTERNAL */ + +(function(BEM, $, undefined) { + +var win = $(window), + doc = $(document), + +/** + * Storage for DOM elements by unique key + * @private + * @type Object + */ + uniqIdToDomElems = {}, + +/** + * Storage for blocks by unique key + * @static + * @private + * @type Object + */ + uniqIdToBlock = {}, + +/** + * Storage for block parameters + * @private + * @type Object + */ + domElemToParams = {}, + +/** + * Storage for liveCtx event handlers + * @private + * @type Object + */ + liveEventCtxStorage = {}, + +/** + * Storage for liveClass event handlers + * @private + * @type Object + */ + liveClassEventStorage = {}, + + blocks = BEM.blocks, + + INTERNAL = BEM.INTERNAL, + + NAME_PATTERN = INTERNAL.NAME_PATTERN, + + MOD_DELIM = INTERNAL.MOD_DELIM, + ELEM_DELIM = INTERNAL.ELEM_DELIM, + + buildModPostfix = INTERNAL.buildModPostfix, + buildClass = INTERNAL.buildClass, + + slice = Array.prototype.slice, + reverse = Array.prototype.reverse; + +/** + * Initializes blocks on a DOM element + * @private + * @param {jQuery} domElem DOM element + * @param {String} uniqInitId ID of the "initialization wave" + */ +function init(domElem, uniqInitId) { + + var domNode = domElem[0]; + $.each(getParams(domNode), function(blockName, params) { + processParams(params, domNode, blockName, uniqInitId); + var block = uniqIdToBlock[params.uniqId]; + if(block) { + if(block.domElem.index(domNode) < 0) { + block.domElem = block.domElem.add(domElem); + $.extend(block._params, params); + } + } else { + initBlock(blockName, domElem, params); + } + }); + +} + +/** + * Initializes a specific block on a DOM element, or returns the existing block if it was already created + * @private + * @param {String} blockName Block name + * @param {jQuery} domElem DOM element + * @param {Object} [params] Initialization parameters + * @param {Boolean} [forceLive] Force live initialization + * @param {Function} [callback] Handler to call after complete initialization + */ +function initBlock(blockName, domElem, params, forceLive, callback) { + + if(typeof params == 'boolean') { + callback = forceLive; + forceLive = params; + params = undefined; + } + + var domNode = domElem[0]; + params = processParams(params || getParams(domNode)[blockName], domNode, blockName); + + var uniqId = params.uniqId; + if(uniqIdToBlock[uniqId]) { + return uniqIdToBlock[uniqId]._init(); + } + + 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 }); + if(!(blockClass._liveInitable = !!blockClass._processLive()) || forceLive || params.live === false) { + forceLive && domElem.addClass('i-bem'); + + var block = new blockClass(uniqIdToDomElems[uniqId], params, !!forceLive); + delete uniqIdToDomElems[uniqId]; + callback && callback.apply(block, slice.call(arguments, 4)); + return block; + } + +} + +/** + * Processes and adds necessary block parameters + * @private + * @param {Object} params Initialization parameters + * @param {HTMLElement} domNode DOM node + * @param {String} blockName Block name + * @param {String} [uniqInitId] ID of the "initialization wave" + */ +function processParams(params, domNode, blockName, uniqInitId) { + + (params || (params = {})).uniqId || + (params.uniqId = (params.id? blockName + '-id-' + params.id : $.identify()) + (uniqInitId || $.identify())); + + var domUniqId = $.identify(domNode), + domParams = domElemToParams[domUniqId] || (domElemToParams[domUniqId] = {}); + + domParams[blockName] || (domParams[blockName] = params); + + return params; + +} + +/** + * Helper for searching for a DOM element using a selector inside the context, including the context itself + * @private + * @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 + * @private + * @param {HTMLElement} domNode DOM node + * @returns {Object} + */ +function getParams(domNode) { + + var uniqId = $.identify(domNode); + return domElemToParams[uniqId] || + (domElemToParams[uniqId] = extractParams(domNode)); + +} + +/** + * Retrieves block parameters from a DOM element + * @private + * @param {HTMLElement} domNode DOM node + * @returns {Object} + */ +function extractParams(domNode) { + + var fn, elem, + attr = domNode.getAttribute('data-bem'); + + if (attr) return JSON.parse(attr); + + fn = domNode.onclick || domNode.ondblclick; + if(!fn && domNode.tagName.toLowerCase() == 'body') { // LEGO-2027 in FF onclick doesn't work on body + elem = $(domNode); + attr = elem.attr('onclick') || elem.attr('ondblclick'); + attr && (fn = Function(attr)); + } + + return fn? fn() : {}; + +} + +/** + * Cleans up all the BEM storages associated with a DOM node + * @private + * @param {HTMLElement} domNode DOM node + */ +function cleanupDomNode(domNode) { + + delete domElemToParams[$.identify(domNode)]; + +} + +/** + * Uncople DOM node from the block. If this is the last node, then destroys the block. + * @private + * @param {BEM.DOM} block block + * @param {HTMLElement} domNode DOM node + */ +function removeDomNodeFromBlock(block, domNode) { + + block.domElem.length === 1? + block.destruct(true) : + block.domElem = block.domElem.not(domNode); + +} + +/** + * Returns a DOM node for calculating the window size in IE + * @returns {HTMLElement} + */ +function getClientNode() { + + return doc[0][$.support.boxModel? 'documentElement' : 'body']; + +} + +/** + * Returns a block on a DOM element and initializes it if necessary + * @param {String} blockName Block name + * @param {Object} params Block parameters + * @returns {BEM} + */ +$.fn.bem = function(blockName, params) { + return initBlock(blockName, this, params, true); +}; + +/** + * Provides methods for work with DOM tree + * + * @block i-bem + * @mod default + */ +var DOM = BEM.DOM = BEM.decl('i-bem__dom', { + /** + * @class Base block for creating BEM blocks that have DOM representation + * @constructs + * @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) { + + var _this = this; + + /** + * Block's DOM elements + * @protected + * @type jQuery + */ + _this.domElem = domElem; + + /** + * Cache for names of events on DOM elements + * @private + * @type Object + */ + _this._eventNameCache = {}; + + /** + * Cache for elements + * @private + * @type Object + */ + _this._elemCache = {}; + + /** + * Unique block ID + * @private + * @type String + */ + uniqIdToBlock[_this._uniqId = params.uniqId || $.identify(_this)] = _this; + + /** + * Flag for whether it's necessary to unbind from the document and window when destroying the block + * @private + * @type Boolean + */ + _this._needSpecialUnbind = false; + + _this.__base(null, params, initImmediately); + + }, + + /** + * Finds blocks inside the current block or its elements (including context) + * @protected + * @param {String|jQuery} [elem] Block element + * @param {String|Object} block Name or description (block,modName,modVal) of the block to find + * @returns {BEM[]} + */ + findBlocksInside : function(elem, block) { + + return this._findBlocks('find', elem, block); + + }, + + /** + * Finds the first block inside the current block or its elements (including context) + * @protected + * @param {String|jQuery} [elem] Block element + * @param {String|Object} block Name or description (block,modName,modVal) of the block to find + * @returns {BEM} + */ + findBlockInside : function(elem, block) { + + return this._findBlocks('find', elem, block, true); + + }, + + /** + * Finds blocks outside the current block or its elements (including context) + * @protected + * @param {String|jQuery} [elem] Block element + * @param {String|Object} block Name or description (block,modName,modVal) of the block to find + * @returns {BEM[]} + */ + findBlocksOutside : function(elem, block) { + + return this._findBlocks('parents', elem, block); + + }, + + /** + * Finds the first block outside the current block or its elements (including context) + * @protected + * @param {String|jQuery} [elem] Block element + * @param {String|Object} block Name or description (block,modName,modVal) of the block to find + * @returns {BEM} + */ + findBlockOutside : function(elem, block) { + + return this._findBlocks('closest', elem, block)[0] || null; + + }, + + /** + * Finds blocks on DOM elements of the current block or its elements + * @protected + * @param {String|jQuery} [elem] Block element + * @param {String|Object} block Name or description (block,modName,modVal) of the block to find + * @returns {BEM[]} + */ + findBlocksOn : function(elem, block) { + + return this._findBlocks('', elem, block); + + }, + + /** + * Finds the first block on DOM elements of the current block or its elements + * @protected + * @param {String|jQuery} [elem] Block element + * @param {String|Object} block Name or description (block,modName,modVal) of the block to find + * @returns {BEM} + */ + findBlockOn : function(elem, block) { + + return this._findBlocks('', elem, block, true); + + }, + + _findBlocks : function(select, elem, block, onlyFirst) { + if(!this.domElem) return []; + + if(!block) { + block = elem; + elem = undefined; + } + + 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), true) : null; + } + + var res = [], + uniqIds = {}; + + $.each(domElems, function(i, domElem) { + var block = initBlock(blockName, $(domElem), true); + 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 {Function} fn Handler function, which will be executed in the block's context + * @returns {BEM} + */ + bindToDomElem : function(domElem, event, fn) { + + var _this = this; + + fn? + domElem.bind( + _this._buildEventName(event), + function(e) { + (e.data || (e.data = {})).domElem = $(this); + return fn.apply(_this, arguments); + } + ) : + $.each(event, function(event, fn) { + _this.bindToDomElem(domElem, event, fn); + }); + + return _this; + + }, + + /** + * Adds an event handler to the document + * @protected + * @param {String} event Event name + * @param {Function} fn Handler function, which will be executed in the block's context + * @returns {BEM} + */ + bindToDoc : function(event, fn) { + + this._needSpecialUnbind = true; + return this.bindToDomElem(doc, event, fn); + + }, + + /** + * Adds an event handler to the window + * @protected + * @param {String} event Event name + * @param {Function} fn Handler function, which will be executed in the block's context + * @returns {BEM} + */ + bindToWin : function(event, fn) { + + var _fn = fn, + currentHeight, + currentWidth; + + if (event === 'resize') { + + fn = function() { + + var height = win.height(), + width = win.width(); + + if (currentHeight !== height || currentWidth !== width) { + + currentHeight = height; + currentWidth = width; + + _fn.apply(this, arguments); + + } + + + } + + } + + this._needSpecialUnbind = true; + return this.bindToDomElem(win, event, fn); + + }, + + /** + * Adds an event handler to the block's main DOM elements or its nested elements + * @protected + * @param {jQuery|String} [elem] Element + * @param {String} event Event name + * @param {Function} fn Handler function, which will be executed in the block's context + * @returns {BEM} + */ + bindTo : function(elem, event, fn) { + + if(!event || $.isFunction(event)) { // if there is no element + fn = event; + event = elem; + elem = this.domElem; + } else if(typeof elem == 'string') { + elem = this.elem(elem); + } + + return this.bindToDomElem(elem, event, fn); + + }, + + /** + * Removes event handlers from any DOM element + * @protected + * @param {jQuery} domElem DOM element where the event was being listened for + * @param {String} event Event name + * @returns {BEM} + */ + unbindFromDomElem : function(domElem, event) { + + domElem.unbind(this._buildEventName(event)); + return this; + + }, + + /** + * Removes event handler from document + * @protected + * @param {String} event Event name + * @returns {BEM} + */ + unbindFromDoc : function(event) { + + return this.unbindFromDomElem(doc, event); + + }, + + /** + * Removes event handler from window + * @protected + * @param {String} event Event name + * @returns {BEM} + */ + unbindFromWin : function(event) { + + return this.unbindFromDomElem(win, event); + + }, + + /** + * Removes event handlers from the block's main DOM elements or its nested elements + * @protected + * @param {jQuery|String} [elem] Nested element + * @param {String} event Event name + * @returns {BEM} + */ + unbindFrom : function(elem, event) { + + if(!event) { + event = elem; + elem = this.domElem; + } else if(typeof elem == 'string') { + elem = this.elem(elem); + } + + return this.unbindFromDomElem(elem, event); + + }, + + /** + * Builds a full name for an event + * @private + * @param {String} event Event name + * @returns {String} + */ + _buildEventName : function(event) { + + var _this = this; + return event.indexOf(' ') > 1? + event.split(' ').map(function(e) { + return _this._buildOneEventName(e); + }).join(' ') : + _this._buildOneEventName(event); + + }, + + /** + * Builds a full name for a single event + * @private + * @param {String} event Event name + * @returns {String} + */ + _buildOneEventName : function(event) { + + var _this = this, + 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; + + }, + + /** + * Triggers block event handlers and live event handlers + * @protected + * @param {String} e Event name + * @param {Object} [data] Additional information + * @returns {BEM} + */ + trigger : function(e, data) { + + this + .__base(e = this.buildEvent(e), data) + .domElem && this._ctxTrigger(e, data); + + return this; + + }, + + _ctxTrigger : function(e, data) { + + var _this = this, + storage = liveEventCtxStorage[_this.__self._buildCtxEventName(e.type)], + ctxIds = {}; + + storage && _this.domElem.each(function() { + var ctx = this, + counter = storage.counter; + while(ctx && counter) { + var ctxId = $.identify(ctx, true); + if(ctxId) { + if(ctxIds[ctxId]) break; + var storageCtx = storage.ctxs[ctxId]; + if(storageCtx) { + $.each(storageCtx, function(uniqId, handler) { + handler.fn.call( + handler.ctx || _this, + e, + data); + }); + counter--; + } + ctxIds[ctxId] = true; + } + ctx = ctx.parentNode; + } + }); + + }, + + /** + * Sets a modifier for a block/nested element + * @protected + * @param {jQuery} [elem] Nested element + * @param {String} modName Modifier name + * @param {String} modVal Modifier value + * @returns {BEM} + */ + 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] : ''; + + }, + + /** + * 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 iModVal = (className = className.trim()).lastIndexOf(MOD_DELIM), + iModName = className.substr(0, iModVal - 1).lastIndexOf(MOD_DELIM); + res[className.substr(iModName + 1, iModVal - iModName - 1)] = className.substr(iModVal + 1); + ++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 + */ + _afterSetMod : function(modName, modVal, oldModVal, elem, elemName) { + + var _self = this.__self, + classPrefix = _self._buildModClassPrefix(modName, elemName), + classRE = _self._buildModValRE(modName, elemName), + needDel = modVal === ''; + + (elem || this.domElem).each(function() { + var className = this.className; + className.indexOf(classPrefix) > -1? + this.className = className.replace( + classRE, + (needDel? '' : '$1' + classPrefix + modVal)) : + needDel || $(this).addClass(classPrefix + modVal); + }); + + elemName && this + .dropElemCache(elemName, modName, oldModVal) + .dropElemCache(elemName, modName, modVal); + + }, + + /** + * Finds elements nested in a block + * @protected + * @param {String|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 + * @returns {jQuery} DOM elements + */ + findElem : function(ctx, names, modName, modVal) { + + if(arguments.length % 2) { // if the number of arguments is one or three + modVal = modName; + modName = names; + names = ctx; + ctx = this.domElem; + } else if(typeof ctx == 'string') { + ctx = this.findElem(ctx); + } + + var _self = this.__self, + selector = '.' + + names.split(' ').map(function(name) { + return buildClass(_self._name, name, modName, modVal); + }).join(',.'); + return findDomElem(ctx, selector); + + }, + + /** + * Finds elements nested in a block + * @protected + * @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) + * @protected + * @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 = $([]), + _this = this; + names.split(' ').forEach(function(name) { + res = res.add(_this._elem(name, modName, modVal)); + }); + return res; + + }, + + /** + * 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 {BEM} + */ + dropElemCache : function(names, modName, modVal) { + + if(names) { + var _this = this, + modPostfix = buildModPostfix(modName, modVal); + names.indexOf(' ') < 0? + delete _this._elemCache[names + modPostfix] : + names.split(' ').forEach(function(name) { + delete _this._elemCache[name + modPostfix]; + }); + } 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])[buildClass(this.__self.getName(), 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} domElem DOM element + * @returns {Boolean} + */ + containsDomElem : function(domElem) { + + var res = false; + + this.domElem.each(function() { + return !(res = domElem.parents().andSelf().index(this) > -1); + }); + + return res; + + }, + + /** + * 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); + + }, + + /** + * Deletes a block + * @param {Boolean} [keepDOM=false] Whether to keep the block's DOM nodes in the document + */ + destruct : function(keepDOM) { + + var _this = this, + _self = _this.__self; + + if(_this._isDestructing) return; + + _this._isDestructing = true; + + _this._needSpecialUnbind && _self.doc.add(_self.win).unbind('.' + _this._uniqId); + + _this.dropElemCache().domElem.each(function(i, domNode) { + var params = getParams(domNode); + $.each(params, function(blockName, blockParams) { + var block = uniqIdToBlock[blockParams.uniqId]; + block? + block._isDestructing || removeDomNodeFromBlock(block, domNode) : + delete uniqIdToDomElems[blockParams.uniqId]; + }); + cleanupDomNode(domNode); + }); + + keepDOM || _this.domElem.remove(); + + delete uniqIdToBlock[_this.un()._uniqId]; + delete _this.domElem; + delete _this._elemCache; + + _this.__base(); + + } + +}, { + + /** + * Scope + * Will be set on onDomReady to tag `body` + * @protected + * @type jQuery + */ + scope : null, + + /** + * Document shortcut + * @protected + * @type jQuery + */ + doc : doc, + + /** + * Window shortcut + * @protected + * @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 _this = this, + res = _this._liveInitable; + + if('live' in _this) { + var noLive = typeof res == 'undefined'; + + if(noLive ^ heedLive) { + 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 + * @static + * @protected + * @param {jQuery} [ctx=document] Root DOM node + * @returns {jQuery} ctx Initialization context + */ + init : function(ctx, callback, callbackCtx) { + + if(!ctx || $.isFunction(ctx)) { + callbackCtx = callback; + callback = ctx; + ctx = doc; + } + + var uniqInitId = $.identify(); + findDomElem(ctx, '.i-bem').each(function() { + init($(this), uniqInitId); + }); + + callback && this.afterCurrentEvent( + function() { + callback.call(callbackCtx || this, ctx); + }); + + // makes initialization completely synchronous + this._runAfterCurrentEventFns(); + + return ctx; + + }, + + /** + * Destroys blocks on a fragment of the DOM tree + * @static + * @protected + * @param {Boolean} [keepDOM=false] Whether to keep DOM nodes in the document + * @param {jQuery} ctx Root DOM node + * @param {Boolean} [excludeSelf=false] Exclude the context + */ + destruct : function(keepDOM, ctx, excludeSelf) { + + if(typeof keepDOM != 'boolean') { + excludeSelf = ctx; + ctx = keepDOM; + keepDOM = undefined; + } + + reverse.call(findDomElem(ctx, '.i-bem', excludeSelf)).each(function(i, domNode) { + var params = getParams(this); + $.each(params, function(blockName, blockParams) { + if(blockParams.uniqId) { + var block = uniqIdToBlock[blockParams.uniqId]; + block? + removeDomNodeFromBlock(block, domNode) : + delete uniqIdToDomElems[blockParams.uniqId]; + } + }); + cleanupDomNode(this); + }); + keepDOM || (excludeSelf? ctx.empty() : ctx.remove()); + + }, + + /** + * Replaces a fragment of the DOM tree inside the context, destroying old blocks and intializing new ones + * @static + * @protected + * @param {jQuery} ctx Root DOM node + * @param {jQuery|String} content New content + * @param {Function} [callback] Handler to be called after initialization + * @param {Object} [callbackCtx] Handler's context + */ + update : function(ctx, content, callback, callbackCtx) { + + this.destruct(ctx, true); + this.init(ctx.html(content), callback, callbackCtx); + + }, + + /** + * 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 + */ + replace : function(ctx, content) { + + this.destruct(true, ctx); + this.init($(content).replaceAll(ctx)); + + }, + + /** + * 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 + */ + append : function(ctx, content) { + + 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 + */ + prepend : function(ctx, content) { + + 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 + */ + before : function(ctx, content) { + + 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 + */ + after : function(ctx, content) { + + this.init($(content).insertAfter(ctx)); + + }, + + /** + * Builds a full name for a live event + * @static + * @private + * @param {String} e Event name + * @returns {String} + */ + _buildCtxEventName : function(e) { + + return this._name + ':' + e; + + }, + + _liveClassBind : function(className, e, callback, invokeOnInit) { + + var _this = this; + if(e.indexOf(' ') > -1) { + e.split(' ').forEach(function(e) { + _this._liveClassBind(className, e, callback, invokeOnInit); + }); + } + else { + var storage = liveClassEventStorage[e], + uniqId = $.identify(callback); + + if(!storage) { + storage = liveClassEventStorage[e] = {}; + doc.bind(e, _this.changeThis(_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) storage.hasOwnProperty(className) && 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) { + var args = [ + _this._name, + ((e.data || (e.data = {})).domElem = $(this)).closest(_this.buildSelector()), + 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 + * @static + * @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 + * @static + * @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 || $.isFunction(event)) { + callback = event; + event = to; + to = undefined; + } + + if(!to || typeof to == 'string') { + to = { elem : to }; + } + + to.elemName && (to.elem = to.elemName); + + var _this = this; + + if(to.elem && to.elem.indexOf(' ') > 0) { + to.elem.split(' ').forEach(function(elem) { + _this._liveClassBind( + buildClass(_this._name, elem, to.modName, to.modVal), + event, + callback, + invokeOnInit); + }); + return _this; + } + + return _this._liveClassBind( + buildClass(_this._name, to.elem, to.modName, to.modVal), + event, + callback, + invokeOnInit); + + }, + + /** + * Helper for unsubscribing from live events on DOM elements of a block or its elements + * @static + * @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 || $.isFunction(event)) { + callback = event; + event = elem; + elem = undefined; + } + + var _this = this; + + if(elem && elem.indexOf(' ') > 1) { + elem.split(' ').forEach(function(elem) { + _this._liveClassUnbind( + buildClass(_this._name, elem), + event, + callback); + }); + return _this; + } + + return _this._liveClassUnbind( + buildClass(_this._name, elem), + event, + callback); + + }, + + /** + * Helper for live initialization when a different block is initialized + * @static + * @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) { + if(!e.block.domElem) return; // if block was destructed at that moment + + var args = arguments, + blocks = e.block[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 + * @static + * @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 + * @static + * @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'); + + }, + + /** + * Helper for live initialization when a different block is initialized on a DOM element of the current block + * @deprecated - use liveInitOnBlockEvent + * @static + * @protected + * @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 + */ + liveInitOnBlockInit : function(blockName, callback) { + + return this.liveInitOnBlockEvent('init', blockName, callback); + + }, + + /** + * Helper for live initialization when a different block is initialized inside the current block + * @deprecated - use liveInitOnBlockInsideEvent + * @static + * @protected + * @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 + */ + liveInitOnBlockInsideInit : function(blockName, callback) { + + return this.liveInitOnBlockInsideEvent('init', blockName, callback); + + }, + + /** + * Adds a live event handler to a block, based on a specified element where the event will be listened for + * @static + * @protected + * @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 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 + * @static + * @protected + * @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 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 + * @deprecated Use on + * @static + * @protected + * @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 + */ + liveCtxBind : function(ctx, e, data, fn, fnCtx) { + + return this._liveCtxBind(ctx, e, data, fn, fnCtx); + + }, + + /** + * Adds a live event handler to a block, based on a specified element where the event will be listened for + * @static + * @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 + */ + _liveCtxBind : function(ctx, e, data, fn, fnCtx) { + + var _this = this; + + if(typeof e == 'string') { + if($.isFunction(data)) { + fnCtx = fn; + fn = data; + data = undefined; + } + + if(e.indexOf(' ') > -1) { + e.split(' ').forEach(function(e) { + _this._liveCtxBind(ctx, e, data, fn, fnCtx); + }); + } 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 + }; + }); + } + } else { + $.each(e, function(e, fn) { + _this._liveCtxBind(ctx, e, fn, data); + }); + } + + return _this; + + }, + + /** + * Removes a live event handler from a block, based on a specified element where the event was being listened for + * @deprecated Use on + * @static + * @protected + * @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 + */ + liveCtxUnbind : function(ctx, e, fn, fnCtx) { + + return this._liveCtxUnbind(ctx, e, fn, fnCtx); + + }, + + /** + * Removes a live event handler from a block, based on a specified element where the event was being listened for + * @static + * @private + * @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 + */ + _liveCtxUnbind : function(ctx, e, fn, fnCtx) { + + var _this = this, + 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 || $.isEmptyObject(ctxStorage)) { + storage.counter--; + delete storage.ctxs[ctxId]; + } + } + }); + storage.counter || delete liveEventCtxStorage[e]; + } + + return _this; + + }, + + /** + * Retrieves the name of an element nested in a block + * @static + * @private + * @param {jQuery} elem Nested element + * @returns {String|undefined} + */ + _extractElemNameFrom : function(elem) { + + if(elem.__bemElemName) return elem.__bemElemName; + + var matches = elem[0].className.match(this._buildElemNameRE()); + return matches? matches[1] : undefined; + + }, + + /** + * Retrieves block parameters from a DOM element + * @static + * @param {HTMLElement} domNode DOM node + * @returns {Object} + */ + extractParams : extractParams, + + /** + * Builds a prefix for the CSS class of a DOM element or nested element of the block, based on modifier name + * @static + * @private + * @param {String} modName Modifier name + * @param {jQuery|String} [elem] Element + * @returns {String} + */ + _buildModClassPrefix : function(modName, elem) { + + return buildClass(this._name) + + (elem? + ELEM_DELIM + (typeof elem === 'string'? elem : this._extractElemNameFrom(elem)) : + '') + + MOD_DELIM + modName + MOD_DELIM; + + }, + + /** + * Builds a regular expression for extracting modifier values from a DOM element or nested element of a block + * @static + * @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) + '(' + NAME_PATTERN + ')(?=\\s|$)', quantifiers); + + }, + + /** + * Builds a regular expression for extracting names of elements nested in a block + * @static + * @private + * @returns {RegExp} + */ + _buildElemNameRE : function() { + + return new RegExp(this._name + ELEM_DELIM + '(' + NAME_PATTERN + ')(?:\\s|$)'); + + }, + + /** + * 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 '.' + buildClass(this._name, elem, modName, modVal); + + }, + + /** + * Returns a block instance by unique ID + * @deprecated + * @param {String} [uniqId] + * @returns {BEM.DOM} + */ + getBlockByUniqId : function(uniqId) { + + return uniqIdToBlock[uniqId]; + + }, + + /** + * Returns the size of the current window + * @returns {Object} Object with width and height fields + */ + getWindowSize : function() { + + return { + width : win.width(), + height : win.height() + }; + + } + +}); + +/** + * Set default scope after DOM ready + */ +$(function() { + BEM.DOM.scope = $('body'); +}); + +})(BEM, jQuery); + +(function() { + +String.prototype.trim || (String.prototype.trim = function () { + + var str = this.replace(/^\s\s*/, ''), + ws = /\s/, + i = str.length; + + while(ws.test(str.charAt(--i))); + + return str.slice(0, i + 1); + +}); + +})(); +(function(undefined) { + +if(window.JSON) return; + +var _toString = Object.prototype.toString, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + meta = { + '\b' : '\\b', + '\t' : '\\t', + '\n' : '\\n', + '\f' : '\\f', + '\r' : '\\r', + '"' : '\\"', + '\\' : '\\\\' + }, + stringify; + +window.JSON = { + stringify : stringify = function(val) { + if(val === null) { + return 'null'; + } + if(typeof val === 'undefined') { + return undefined; + } + switch(_toString.call(val)) { + case '[object String]': + escapable.lastIndex = 0; + return '"' + + (escapable.test(val)? + val.replace(escapable, function(a) { + var c = meta[a]; + return typeof c === 'string'? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) : + val) + + '"'; + case '[object Number]': + case '[object Boolean]': + return '' + val; + case '[object Array]': + var res = '[', i = 0, len = val.length, strVal; + while(i < len) { + strVal = stringify(val[i]); + res += (i++? ',' : '') + (typeof strVal === 'undefined'? 'null' : strVal); + } + return res + ']'; + case '[object Object]': + if(_toString.call(val.toJSON) === '[object Function]') { + return stringify(val.toJSON()); + } + var res = '{', i = 0, strVal; + for(var key in val) { + if(val.hasOwnProperty(key)) { + strVal = stringify(val[key]); + typeof strVal !== 'undefined' && (res += (i++? ',' : '') + '"' + key + '":' + strVal); + } + } + return res + '}'; + default: + return undefined; + } + }, + parse : function(str) { + /*jshint -W061 */ + return Function('return ' + str)(); + } +}; +})(); diff --git a/i-bem.min.js b/i-bem.min.js new file mode 100644 index 0000000..c89e516 --- /dev/null +++ b/i-bem.min.js @@ -0,0 +1,7 @@ +/*! + * @file i-bem — library to write client side with BEM methodology + * @version 2.0.4 + * @tutorial http://ru.bem.info/libs/bem-bl/dev/desktop.sets/i-bem/ + * @link https://github.com/bem-node/i-bem-doc + */ +!function(a){function b(b,d,e){var g=!1;if(f){var h=[];a.each(i,function(){e.hasOwnProperty(this)&&(g=!0)&&h.push({name:this,val:e[this]})}),g&&(a.each(e,function(a){h.push({name:a,val:this})}),e=h)}a.each(e,function(e,f){if(g&&(e=f.name,f=f.val),a.isFunction(f)&&(!c||f.toString().indexOf(".__base")>-1)){var h=b[e]||function(){};d[e]=function(){var a=this.__base;this.__base=h;var b=f.apply(this,arguments);return this.__base=a,b}}else d[e]=f})}var c=function(){"_"}.toString().indexOf("_")>-1,d=function(){},e=Object.create||function(a){var b=function(){};return b.prototype=a,new b},f=!0,g={toString:""};for(var h in g)g.hasOwnProperty(h)&&(f=!1);var i=f?["toString","valueOf"]:null;a.inherit=function(){var c=arguments,f=a.isFunction(c[0]),g=f?c[0]:d,h=c[f?1:0]||{},i=c[f?2:1],j=h.__constructor||f&&g.prototype.__constructor?function(){return this.__constructor.apply(this,arguments)}:function(){};if(!f)return j.prototype=h,j.prototype.__self=j.prototype.constructor=j,a.extend(j,i);a.extend(j,g);var k=g.prototype,l=j.prototype=e(k);return l.__self=l.constructor=j,b(k,l,h),i&&b(g,j,i),j},a.inheritSelf=function(a,c,d){var e=a.prototype;return b(e,e,c),d&&b(a,a,d),a}}(jQuery),function(a){var b=0,c="__"+ +new Date,d=function(){return"uniq"+ ++b};a.identify=function(a,b){if(!a)return d();var e="uniqueID"in a?"uniqueID":c;return b||e in a?a[e]:a[e]=d()}}(jQuery),function(a){a.isEmptyObject||(a.isEmptyObject=function(a){for(var b in a)return!1;return!0})}(jQuery),function(a){a.extend({debounce:function(a,b,c,d){3==arguments.length&&"boolean"!=typeof c&&(d=c,c=!1);var e;return function(){var f=arguments;d=d||this,c&&!e&&a.apply(d,f),clearTimeout(e),e=setTimeout(function(){c||a.apply(d,f),e=null},b)}},throttle:function(a,b,c){var d,e,f;return function(){e=arguments,f=!0,c=c||this,d||function(){f?(a.apply(c,e),f=!1,d=setTimeout(arguments.callee,b)):d=null}()}}})}(jQuery),function(a){var b="__"+ +new Date+"storage",c=function(b,c){return a.identify(b)+(c?a.identify(c):"")},d={buildEventName:function(a){return a},on:function(d,e,f,g,h){if("string"==typeof d){a.isFunction(e)&&(g=f,f=e,e=void 0);for(var i,j=c(f,g),k=this[b]||(this[b]={}),l=d.split(" "),m=0;d=l[m++];)if(d=this.buildEventName(d),i=k[d]||(k[d]={ids:{},list:{}}),!(j in i.ids)){var n=i.list,o={fn:f,data:e,ctx:g,special:h};n.last?(n.last.next=o,o.prev=n.last):n.first=o,i.ids[j]=n.last=o}}else{var p=this;a.each(d,function(a,b){p.on(a,b,e,h)})}return this},onFirst:function(a,b,c,d){return this.on(a,b,c,d,{one:!0})},un:function(d,e,f){if("string"==typeof d||"undefined"==typeof d){var g=this[b];if(g)if(d){for(var h,i=d.split(" "),j=0;d=i[j++];)if(d=this.buildEventName(d),h=g[d])if(e){var k=c(e,f),l=h.ids;if(k in l){var m=h.list,n=l[k],o=n.prev,p=n.next;o?o.next=p:n===m.first&&(m.first=p),p?p.prev=o:n===m.last&&(m.last=o),delete l[k]}}else delete this[b][d]}else delete this[b]}else{var q=this;a.each(d,function(a,b){q.un(a,b,f)})}return this},trigger:function(c,d){var e,f=this,g=f[b];if("string"==typeof c?c=a.Event(f.buildEventName(e=c)):c.type=f.buildEventName(e=c.type),c.target||(c.target=f),g&&(g=g[c.type]))for(var h,i=g.list.first;i;)c.data=i.data,h=i.fn.call(i.ctx||f,c,d),"undefined"!=typeof h&&(c.result=h,h===!1&&(c.preventDefault(),c.stopPropagation())),i.special&&i.special.one&&f.un(e,i.fn,i.ctx),i=i.next;return this}};a.observable=a.inherit(d,d)}(jQuery),function(a,b){function c(a,b,c){return(a?"__elem_"+a:"")+"__mod"+(b?"_"+b:"")+(c?"_"+c:"")}function d(b,d,e){a.isFunction(b)?d[c(e,"*","*")]=b:a.each(b,function(b,f){a.isFunction(f)?d[c(e,b,"*")]=f:a.each(f,function(a,f){d[c(e,b,a)]=f})})}function e(a,b){return b?Array.isArray(b)?function(c){for(var d=0,e=b.length;e>d;)if(c.hasMod(a,b[d++]))return!0;return!1}:function(c){return c.hasMod(a,b)}:function(b){return b.hasMod(a)}}var f=[],g={},h={};this.BEM=a.inherit(a.observable,{__constructor:function(a,b,c){var d=this;d._modCache=a||{},d._processingMods={},d._params=b,d.params=null,c!==!1?d._init():d.afterCurrentEvent(function(){d._init()})},_init:function(){return this._initing||this.hasMod("js","inited")||(this._initing=!0,this.params||(this.params=a.extend(this.getDefaultParams(),this._params),delete this._params),this.setMod("js","inited"),delete this._initing,this.hasMod("js","inited")&&this.trigger("init")),this},changeThis:function(a,b){return a.bind(b||this)},afterCurrentEvent:function(a,b){this.__self.afterCurrentEvent(this.changeThis(a,b))},trigger:function(a,b){return this.__base(a=this.buildEvent(a),b).__self.trigger(a,b),this},buildEvent:function(b){return"string"==typeof b&&(b=a.Event(b)),b.block=this,b},hasMod:function(a,c,d){var e=arguments.length,f=!1;1==e?(d="",c=a,a=b,f=!0):2==e&&("string"==typeof a?(d=c,c=a,a=b):(d="",f=!0));var g=this.getMod(a,c)===d;return f?!g:g},getMod:function(a,b){var c=typeof a;if("string"===c||"undefined"===c){b=a||b;var d=this._modCache;return b in d?d[b]:d[b]=this._extractModVal(b)}return this._getElemMod(b,a)},_getElemMod:function(a,b,c){return this._extractModVal(a,b,c)},getMods:function(a){var c=a&&"string"!=typeof a,d=this,e=[].slice.call(arguments,c?1:0),f=d._extractMods(e,c?a:b);return c||(e.length?e.forEach(function(a){d._modCache[a]=f[a]}):d._modCache=f),f},setMod:function(c,d,e){"undefined"==typeof e&&(e=d,d=c,c=b);var f=this;if(!c||c[0]){var g=(c&&c[0]?a.identify(c[0]):"")+"_"+d;if(this._processingMods[g])return f;var h,i=c?f._getElemMod(d,c,h=f.__self._extractElemNameFrom(c)):f.getMod(d);if(i===e)return f;this._processingMods[g]=!0;var j=!0,k=[d,e,i];c&&k.unshift(c),[["*","*"],[d,"*"],[d,e]].forEach(function(a){j=f._callModFn(h,a[0],a[1],k)!==!1&&j}),!c&&j&&(f._modCache[d]=e),j&&f._afterSetMod(d,e,i,c,h),delete this._processingMods[g]}return f},_afterSetMod:function(){},toggleMod:function(a,c,d,e,f){"string"==typeof a&&(f=e,e=d,d=c,c=a,a=b),"undefined"==typeof e?e="":"boolean"==typeof e&&(f=e,e="");var g=this.getMod(a,c);return(g==d||g==e)&&this.setMod(a,c,"boolean"==typeof f?f?d:e:this.hasMod(a,c,d)?e:d),this},delMod:function(a,c){return c||(c=a,a=b),this.setMod(a,c,"")},_callModFn:function(a,d,e,f){var g=c(a,d,e);return this[g]?this[g].apply(this,f):b},_extractModVal:function(){return""},_extractMods:function(){return{}},channel:function(a,b){return this.__self.channel(a,b)},getDefaultParams:function(){return{}},del:function(a){var b=[].slice.call(arguments);return"string"==typeof a&&b.unshift(this),this.__self.del.apply(this.__self,b),this},destruct:function(){}},{_name:"i-bem",blocks:g,decl:function(c,f,h){if("string"==typeof c?c={block:c}:c.name&&(c.block=c.name),c.baseBlock&&!g[c.baseBlock])throw'baseBlock "'+c.baseBlock+'" for "'+c.block+'" is undefined';f||(f={}),f.onSetMod&&(d(f.onSetMod,f),delete f.onSetMod),f.onElemSetMod&&(a.each(f.onElemSetMod,function(a,b){d(b,f,a)}),delete f.onElemSetMod);var i=g[c.baseBlock||c.block]||this;if(c.modName){var j=e(c.modName,c.modVal);a.each(f,function(c,d){a.isFunction(d)&&(f[c]=function(){var a;if(j(this))a=d;else{var e=i.prototype[c];e&&e!==f[c]&&(a=this.__base)}return a?a.apply(this,arguments):b})})}if(h&&"boolean"==typeof h.live){var k=h.live;h.live=function(){return k}}var l;return c.block==i._name?(l=a.inheritSelf(i,f,h))._processLive(!0):((l=g[c.block]=a.inherit(i,f,h))._name=c.block,delete l._liveInitable),l},_processLive:function(){return!1},create:function(a,b){return"string"==typeof a&&(a={block:a}),new g[a.block](a.mods,b)},getName:function(){return this._name},_extractElemNameFrom:function(){},afterCurrentEvent:function(a,b){1==f.push({fn:a,ctx:b})&&setTimeout(this._runAfterCurrentEventFns,0)},_runAfterCurrentEventFns:function(){var a=f.length;if(a)for(var b,c=f.splice(0,a);b=c.shift();)b.fn.call(b.ctx||this)},changeThis:function(a,b){return a.bind(b||this)},del:function(a){var b="string"==typeof a,c=b?0:1,d=arguments.length;for(b&&(a=this);d>c;)delete a[arguments[c++]];return this},channel:function(c,d){return"boolean"==typeof c&&(d=c,c=b),c||(c="default"),d?void(h[c]&&(h[c].un(),delete h[c])):h[c]||(h[c]=new a.observable)}})}(jQuery),function(){Object.keys||(Object.keys=function(a){var b=[];for(var c in a)a.hasOwnProperty(c)&&b.push(c);return b})}(),function(){var a=Array.prototype,b=Object.prototype.toString,c={indexOf:function(a,b){b=+(b||0);var c=this,d=c.length;if(d>0&&d>b)for(b=0>b?Math.ceil(b):Math.floor(b),-d>b&&(b=0),0>b&&(b+=d);d>b;){if(b in c&&c[b]===a)return b;++b}return-1},forEach:function(a,b){for(var c=-1,d=this,e=d.length;++c1?a.split(" ").map(function(a){return b._buildOneEventName(a)}).join(" "):b._buildOneEventName(a)},_buildOneEventName:function(a){var b=this,c=b._eventNameCache;if(a in c)return c[a];var d="."+b._uniqId;if(a.indexOf(".")<0)return c[a]=a+d;var e=".bem_"+b.__self._name;return c[a]=a.split(".").map(function(a,b){return 0==b?a+e:e+"_"+a}).join("")+d},trigger:function(a,b){return this.__base(a=this.buildEvent(a),b).domElem&&this._ctxTrigger(a,b),this},_ctxTrigger:function(a,c){var d=this,e=q[d.__self._buildCtxEventName(a.type)],f={};e&&d.domElem.each(function(){for(var g=this,h=e.counter;g&&h;){var i=b.identify(g,!0);if(i){if(f[i])break;var j=e.ctxs[i];j&&(b.each(j,function(b,e){e.fn.call(e.ctx||d,a,c)}),h--),f[i]=!0}g=g.parentNode}})},setMod:function(a,c,d){if(a&&"undefined"!=typeof d&&a.length>1){var e=this;return a.each(function(){var f=b(this);f.__bemElemName=a.__bemElemName,e.setMod(f,c,d)}),e}return this.__base(a,c,d)},_extractModVal:function(a,b,c){var d,e=(b||this.domElem)[0];return e&&(d=e.className.match(this.__self._buildModValRE(a,c||b))),d?d[2]:""},_extractMods:function(a,b){var c={},d=!a.length,e=0;return((b||this.domElem)[0].className.match(this.__self._buildModValRE("("+(d?u:a.join("|"))+")",b,"g"))||[]).forEach(function(a){var b=(a=a.trim()).lastIndexOf(v),d=a.substr(0,b-1).lastIndexOf(v);c[a.substr(d+1,b-d-1)]=a.substr(b+1),++e}),e-1?this.className=a.replace(i,j?"":"$1"+h+c):j||b(this).addClass(h+c)}),f&&this.dropElemCache(f,a,d).dropElemCache(f,a,c)},findElem:function(a,b,c,d){arguments.length%2?(d=c,c=b,b=a,a=this.domElem):"string"==typeof a&&(a=this.findElem(a));var e=this.__self,f="."+b.split(" ").map(function(a){return y(e._name,a,c,d)}).join(",.");return g(a,f)},_elem:function(a,b,c){var d,e=a+x(b,c);return(d=this._elemCache[e])||(d=this._elemCache[e]=this.findElem(a,b,c),d.__bemElemName=a),d},elem:function(a,c,d){if(c&&"string"!=typeof c)return c.__bemElemName=a,c;if(a.indexOf(" ")<0)return this._elem(a,c,d);var e=b([]),f=this;return a.split(" ").forEach(function(a){e=e.add(f._elem(a,c,d))}),e},dropElemCache:function(a,b,c){if(a){var d=this,e=x(b,c);a.indexOf(" ")<0?delete d._elemCache[a+e]:a.split(" ").forEach(function(a){delete d._elemCache[a+e]})}else this._elemCache={};return this},elemParams:function(a){var b;return"string"==typeof a?(b=a,a=this.elem(a)):b=this.__self._extractElemNameFrom(a),i(a[0])[y(this.__self.getName(),b)]||{}},elemify:function(a,c){return(a=b(a)).__bemElemName=c,a},containsDomElem:function(a){var b=!1;return this.domElem.each(function(){return!(b=a.parents().andSelf().index(this)>-1)}),b},buildSelector:function(a,b,c){return this.__self.buildSelector(a,b,c)},destruct:function(a){var c=this,d=c.__self;c._isDestructing||(c._isDestructing=!0,c._needSpecialUnbind&&d.doc.add(d.win).unbind("."+c._uniqId),c.dropElemCache().domElem.each(function(a,c){var d=h(c);b.each(d,function(a,b){var d=o[b.uniqId];d?d._isDestructing||k(d,c):delete n[b.uniqId]}),j(c)}),a||c.domElem.remove(),delete o[c.un()._uniqId],delete c.domElem,delete c._elemCache,c.__base())}},{scope:null,doc:m,win:l,_processLive:function(a){var b=this,c=b._liveInitable;if("live"in b){var d="undefined"==typeof c;if(d^a){c=b.live()!==!1;var e=b.getName(),f=b.live;b.live=function(){return this.getName()===e?c:f.apply(this,arguments)}}}return c},init:function(a,c,e){(!a||b.isFunction(a))&&(e=c,c=a,a=m);var f=b.identify();return g(a,".i-bem").each(function(){d(b(this),f)}),c&&this.afterCurrentEvent(function(){c.call(e||this,a)}),this._runAfterCurrentEventFns(),a},destruct:function(a,d,e){"boolean"!=typeof a&&(e=d,d=a,a=c),A.call(g(d,".i-bem",e)).each(function(a,c){var d=h(this);b.each(d,function(a,b){if(b.uniqId){var d=o[b.uniqId];d?k(d,c):delete n[b.uniqId]}}),j(this)}),a||(e?d.empty():d.remove())},update:function(a,b,c,d){this.destruct(a,!0),this.init(a.html(b),c,d)},replace:function(a,c){this.destruct(!0,a),this.init(b(c).replaceAll(a))},append:function(a,c){this.init(b(c).appendTo(a))},prepend:function(a,c){this.init(b(c).prependTo(a))},before:function(a,c){this.init(b(c).insertBefore(a))},after:function(a,c){this.init(b(c).insertAfter(a))},_buildCtxEventName:function(a){return this._name+":"+a},_liveClassBind:function(a,c,d,e){var f=this;if(c.indexOf(" ")>-1)c.split(" ").forEach(function(b){f._liveClassBind(a,b,d,e)});else{var g=r[c],h=b.identify(d);g||(g=r[c]={},m.bind(c,f.changeThis(f._liveClassTrigger,f))),g=g[a]||(g[a]={uniqIds:{},fns:[]}),h in g.uniqIds||(g.fns.push({uniqId:h,fn:f._buildLiveEventFn(d,e)}),g.uniqIds[h]=g.fns.length-1)}return this},_liveClassUnbind:function(a,c,d){var e=r[c];if(e)if(d){if(e=e[a]){var f=b.identify(d);if(f in e.uniqIds){var g=e.uniqIds[f],h=e.fns.length-1;for(e.fns.splice(g,1);h>g;)e.uniqIds[e.fns[g++].uniqId]=g-1;delete e.uniqIds[f]}}}else delete e[a];return this},_liveClassTrigger:function(a){var c=r[a.type];if(c){var d=a.target,e=[];for(var f in c)c.hasOwnProperty(f)&&e.push(f);do for(var g=" "+d.className+" ",h=0;f=e[h++];)if(g.indexOf(" "+f+" ")>-1){for(var i,j=0,k=c[f].fns,l=!1;i=k[j++];)i.fn.call(b(d),a)===!1&&(l=!0);if(l&&a.preventDefault(),l||a.isPropagationStopped())return;e.splice(--h,1)}while(e.length&&(d=d.parentNode))}},_buildLiveEventFn:function(a,c){var d=this;return function(f){var g=[d._name,((f.data||(f.data={})).domElem=b(this)).closest(d.buildSelector()),!0],h=e.apply(null,c?g.concat([a,f]):g);return h&&!c&&a?a.apply(h,arguments):void 0}},liveInitOnEvent:function(a,b,c){return this.liveBindTo(a,b,c,!0)},liveBindTo:function(a,d,e,f){(!d||b.isFunction(d))&&(e=d,d=a,a=c),a&&"string"!=typeof a||(a={elem:a}),a.elemName&&(a.elem=a.elemName);var g=this;return a.elem&&a.elem.indexOf(" ")>0?(a.elem.split(" ").forEach(function(b){g._liveClassBind(y(g._name,b,a.modName,a.modVal),d,e,f)}),g):g._liveClassBind(y(g._name,a.elem,a.modName,a.modVal),d,e,f)},liveUnbindFrom:function(a,d,e){(!d||b.isFunction(d))&&(e=d,d=a,a=c);var f=this;return a&&a.indexOf(" ")>1?(a.split(" ").forEach(function(a){f._liveClassUnbind(y(f._name,a),d,e)}),f):f._liveClassUnbind(y(f._name,a),d,e)},_liveInitOnBlockEvent:function(a,b,c,d){var e=this._name;return s[b].on(a,function(a){if(a.block.domElem){var b=arguments,f=a.block[d](e);c&&f.forEach(function(a){c.apply(a,b)})}}),this},liveInitOnBlockEvent:function(a,b,c){return this._liveInitOnBlockEvent(a,b,c,"findBlocksOn")},liveInitOnBlockInsideEvent:function(a,b,c){return this._liveInitOnBlockEvent(a,b,c,"findBlocksOutside")},liveInitOnBlockInit:function(a,b){return this.liveInitOnBlockEvent("init",a,b)},liveInitOnBlockInsideInit:function(a,b){return this.liveInitOnBlockInsideEvent("init",a,b)},on:function(a,b,c,d,e){return a.jquery?this._liveCtxBind(a,b,c,d,e):this.__base(a,b,c,d)},un:function(a,b,c,d){return a.jquery?this._liveCtxUnbind(a,b,c,d):this.__base(a,b,c)},liveCtxBind:function(a,b,c,d,e){return this._liveCtxBind(a,b,c,d,e)},_liveCtxBind:function(a,d,e,f,g){var h=this;if("string"==typeof d)if(b.isFunction(e)&&(g=f,f=e,e=c),d.indexOf(" ")>-1)d.split(" ").forEach(function(b){h._liveCtxBind(a,b,e,f,g)});else{var i=h._buildCtxEventName(d),j=q[i]||(q[i]={counter:0,ctxs:{}});a.each(function(){var a=b.identify(this),c=j.ctxs[a];c||(c=j.ctxs[a]={},++j.counter),c[b.identify(f)+(g?b.identify(g):"")]={fn:f,data:e,ctx:g}})}else b.each(d,function(b,c){h._liveCtxBind(a,b,c,e)});return h},liveCtxUnbind:function(a,b,c,d){return this._liveCtxUnbind(a,b,c,d)},_liveCtxUnbind:function(a,c,d,e){var f=this,g=q[c=f._buildCtxEventName(c)];return g&&(a.each(function(){var a,c=b.identify(this,!0);c&&(a=g.ctxs[c])&&(d&&delete a[b.identify(d)+(e?b.identify(e):"")],(!d||b.isEmptyObject(a))&&(g.counter--,delete g.ctxs[c]))}),g.counter||delete q[c]),f},_extractElemNameFrom:function(a){if(a.__bemElemName)return a.__bemElemName;var b=a[0].className.match(this._buildElemNameRE());return b?b[1]:c},extractParams:i,_buildModClassPrefix:function(a,b){return y(this._name)+(b?w+("string"==typeof b?b:this._extractElemNameFrom(b)):"")+v+a+v},_buildModValRE:function(a,b,c){return new RegExp("(\\s|^)"+this._buildModClassPrefix(a,b)+"("+u+")(?=\\s|$)",c)},_buildElemNameRE:function(){return new RegExp(this._name+w+"("+u+")(?:\\s|$)")},buildSelector:function(a,b,c){return"."+y(this._name,a,b,c)},getBlockByUniqId:function(a){return o[a]},getWindowSize:function(){return{width:l.width(),height:l.height()}}});b(function(){a.DOM.scope=b("body")})}(BEM,jQuery),function(){String.prototype.trim||(String.prototype.trim=function(){for(var a=this.replace(/^\s\s*/,""),b=/\s/,c=a.length;b.test(a.charAt(--c)););return a.slice(0,c+1)})}(),function(a){if(!window.JSON){var b,c=Object.prototype.toString,d=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,e={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"};window.JSON={stringify:b=function(f){if(null===f)return"null";if("undefined"==typeof f)return a;switch(c.call(f)){case"[object String]":return d.lastIndex=0,'"'+(d.test(f)?f.replace(d,function(a){var b=e[a];return"string"==typeof b?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}):f)+'"';case"[object Number]":case"[object Boolean]":return""+f;case"[object Array]":for(var g,h="[",i=0,j=f.length;j>i;)g=b(f[i]),h+=(i++?",":"")+("undefined"==typeof g?"null":g);return h+"]";case"[object Object]":if("[object Function]"===c.call(f.toJSON))return b(f.toJSON());var g,h="{",i=0;for(var k in f)f.hasOwnProperty(k)&&(g=b(f[k]),"undefined"!=typeof g&&(h+=(i++?",":"")+'"'+k+'":'+g));return h+"}";default:return a}},parse:function(a){return Function("return "+a)()}}}}(); \ No newline at end of file