diff --git a/common.blocks/i-bem-dom/__init/_auto/i-bem-dom__init_auto.deps.js b/common.blocks/i-bem-dom/__init/_auto/i-bem-dom__init_auto.deps.js new file mode 100644 index 000000000..781b0d791 --- /dev/null +++ b/common.blocks/i-bem-dom/__init/_auto/i-bem-dom__init_auto.deps.js @@ -0,0 +1,3 @@ +({ + shouldDeps : 'jquery' +}) diff --git a/common.blocks/i-bem/__dom/_init/i-bem__dom_init_auto.js b/common.blocks/i-bem-dom/__init/_auto/i-bem-dom__init_auto.js similarity index 74% rename from common.blocks/i-bem/__dom/_init/i-bem__dom_init_auto.js rename to common.blocks/i-bem-dom/__init/_auto/i-bem-dom__init_auto.js index 863a9c2ea..c3718d817 100644 --- a/common.blocks/i-bem/__dom/_init/i-bem__dom_init_auto.js +++ b/common.blocks/i-bem-dom/__init/_auto/i-bem-dom__init_auto.js @@ -3,7 +3,7 @@ */ modules.require( - ['i-bem__dom_init', 'jquery', 'next-tick'], + ['i-bem-dom__init', 'jquery', 'next-tick'], function(init, $, nextTick) { $(function() { diff --git a/common.blocks/i-bem/__dom/_init/i-bem__dom_init.js b/common.blocks/i-bem-dom/__init/i-bem-dom__init.js similarity index 72% rename from common.blocks/i-bem/__dom/_init/i-bem__dom_init.js rename to common.blocks/i-bem-dom/__init/i-bem-dom__init.js index a60cb92ae..1609fd142 100644 --- a/common.blocks/i-bem/__dom/_init/i-bem__dom_init.js +++ b/common.blocks/i-bem-dom/__init/i-bem-dom__init.js @@ -1,8 +1,8 @@ /** - * @module i-bem__dom_init + * @module i-bem-dom__init */ -modules.define('i-bem__dom_init', ['i-bem__dom'], function(provide, BEMDOM) { +modules.define('i-bem-dom__init', ['i-bem-dom'], function(provide, BEMDOM) { provide( /** diff --git a/common.blocks/i-bem-dom/__init/i-bem-dom__init.spec.js b/common.blocks/i-bem-dom/__init/i-bem-dom__init.spec.js new file mode 100644 index 000000000..505892e8c --- /dev/null +++ b/common.blocks/i-bem-dom/__init/i-bem-dom__init.spec.js @@ -0,0 +1,23 @@ +modules.define( + 'spec', + ['i-bem', 'i-bem-dom'], + function(provide, BEM, BEMDOM) { + +describe('i-bem-dom__init', function() { + it('block should exist on init', function(done) { + var name = 'b' + Math.random(); + + modules.define(name, ['i-bem-dom'], function(provide, BEMDOM) { + provide(BEMDOM.declBlock(this.name, {})); + }); + + modules.require(['i-bem-dom__init'], function() { + BEM.blocks.should.have.property(name); + done(); + }); + }); +}); + +provide(); + +}); diff --git a/common.blocks/i-bem/__dom/i-bem__dom.deps.js b/common.blocks/i-bem-dom/i-bem-dom.deps.js similarity index 94% rename from common.blocks/i-bem/__dom/i-bem__dom.deps.js rename to common.blocks/i-bem-dom/i-bem-dom.deps.js index 468ddc4d8..d4ab48801 100644 --- a/common.blocks/i-bem/__dom/i-bem__dom.deps.js +++ b/common.blocks/i-bem-dom/i-bem-dom.deps.js @@ -1,5 +1,6 @@ [{ shouldDeps : [ + 'inherit', 'jquery', 'objects', 'functions', diff --git a/common.blocks/i-bem/__dom/i-bem__dom.js b/common.blocks/i-bem-dom/i-bem-dom.js similarity index 84% rename from common.blocks/i-bem/__dom/i-bem__dom.js rename to common.blocks/i-bem-dom/i-bem-dom.js index c04019731..8505c0138 100644 --- a/common.blocks/i-bem/__dom/i-bem__dom.js +++ b/common.blocks/i-bem-dom/i-bem-dom.js @@ -1,11 +1,29 @@ /** - * @module i-bem__dom + * @module i-bem-dom */ modules.define( - 'i-bem__dom', - ['i-bem', 'i-bem__internal', 'identify', 'objects', 'functions', 'jquery', 'dom'], - function(provide, BEM, INTERNAL, identify, objects, functions, $, dom) { + 'i-bem-dom', + [ + 'i-bem', + 'i-bem__internal', + 'inherit', + 'identify', + 'objects', + 'functions', + 'jquery', + 'dom' + ], + function( + provide, + BEM, + BEMINTERNAL, + inherit, + identify, + objects, + functions, + $, + dom) { var undef, win = $(window), @@ -24,9 +42,9 @@ var undef, uniqIdToBlock = {}, /** - * Storage for DOM element's parent nodes - * @type Object - */ + * Storage for DOM element's parent nodes + * @type Object + */ domNodesToParents = {}, /** @@ -53,17 +71,17 @@ var undef, BEM_SELECTOR = '.' + BEM_CLASS, BEM_PARAMS_ATTR = 'data-bem', - NAME_PATTERN = INTERNAL.NAME_PATTERN, + NAME_PATTERN = BEMINTERNAL.NAME_PATTERN, - MOD_DELIM = INTERNAL.MOD_DELIM, - ELEM_DELIM = INTERNAL.ELEM_DELIM, + MOD_DELIM = BEMINTERNAL.MOD_DELIM, + ELEM_DELIM = BEMINTERNAL.ELEM_DELIM, EXTRACT_MODS_RE = RegExp( '[^' + MOD_DELIM + ']' + MOD_DELIM + '(' + NAME_PATTERN + ')' + '(?:' + MOD_DELIM + '(' + NAME_PATTERN + '))?$'), - buildModPostfix = INTERNAL.buildModPostfix, - buildClass = INTERNAL.buildClass, + buildModPostfix = BEMINTERNAL.buildModPostfix, + buildClass = BEMINTERNAL.buildClass, reverse = Array.prototype.reverse; @@ -118,12 +136,11 @@ function initBlock(blockName, domElem, params, forceLive, callback) { $.unique(uniqIdToDomElems[uniqId]); } - var blockClass = blocks[blockName] || DOM.decl(blockName, {}, { live : true }, true); - if(!(blockClass._liveInitable = !!blockClass._processLive()) || forceLive || params.live === false) { + var blockCls = blocks[blockName] || BEMDOM.declBlock(blockName, {}, { live : true }, true); + if(!(blockCls._liveInitable = !!blockCls._processLive()) || forceLive || params.live === false) { forceLive && domElem.addClass(BEM_CLASS); // add css class for preventing memory leaks in further destructing - block = new blockClass(uniqIdToDomElems[uniqId], params, !!forceLive); - + block = new blockCls(uniqIdToDomElems[uniqId], params, !!forceLive); delete uniqIdToDomElems[uniqId]; callback && callback.apply(block, Array.prototype.slice.call(arguments, 4)); return block; @@ -164,7 +181,7 @@ function findDomElem(ctx, selector, excludeSelf) { * @param {HTMLElement} domNode DOM node * @returns {Object} */ -function getParams(domNode, blockName) { +function getParams(domNode) { var uniqId = identify(domNode); return domElemToParams[uniqId] || (domElemToParams[uniqId] = extractParams(domNode)); @@ -194,17 +211,20 @@ function extractParams(domNode) { /** * Uncouple DOM node from the block. If this is the last node, then destroys the block. - * @param {BEMDOM} block block + * @param {i-bem-dom:Block} block block * @param {HTMLElement} domNode DOM node */ function removeDomNodeFromBlock(block, domNode) { - block.domElem.length === 1? - block._destruct() : + if(block.domElem.length === 1) { + block._destruct(); + delete uniqIdToBlock[block._uniqId]; + } else { block.domElem = block.domElem.not(domNode); + } } /** - * Fills DOM node's parent nodes to the storage + * Stores DOM node's parent nodes to the storage * @param {jQuery} domElem */ function storeDomNodeParents(domElem) { @@ -214,12 +234,13 @@ function storeDomNodeParents(domElem) { } /** - * @class BEMDOM + * @class Block * @description Base block for creating BEM blocks that have DOM representation + * @augments i-bem:Block * @exports */ -var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ +var Block = inherit(BEM.Block,/** @lends Block.prototype */{ /** * @constructor * @private @@ -269,47 +290,47 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ /** * Finds blocks inside the current block or its elements (including context) * @param {String|jQuery} [elem] Block element - * @param {String|Object} block Name or description (block,modName,modVal) of the block to find + * @param {Function|Object} Block Block or description (block,modName,modVal) of the block to find * @returns {BEMDOM[]} */ - findBlocksInside : function(elem, block) { - return this._findBlocks('find', elem, block); + findBlocksInside : function(elem, Block) { + return this._findBlocks('find', elem, Block); }, /** * Finds the first block inside the current block or its elements (including context) * @param {String|jQuery} [elem] Block element - * @param {String|Object} block Name or description (block,modName,modVal) of the block to find + * @param {Function|Object} Block Block or description (block,modName,modVal) of the block to find * @returns {BEMDOM} */ - findBlockInside : function(elem, block) { - return this._findBlocks('find', elem, block, true); + findBlockInside : function(elem, Block) { + return this._findBlocks('find', elem, Block, true); }, /** * Finds blocks outside the current block or its elements (including context) * @param {String|jQuery} [elem] Block element - * @param {String|Object} block Name or description (block,modName,modVal) of the block to find + * @param {Function|Object} Block Block or description (block,modName,modVal) of the block to find * @returns {BEMDOM[]} */ - findBlocksOutside : function(elem, block) { - return this._findBlocks('parents', elem, block); + findBlocksOutside : function(elem, Block) { + return this._findBlocks('parents', elem, Block); }, /** * Finds the first block outside the current block or its elements (including context) * @param {String|jQuery} [elem] Block element - * @param {String|Object} block Name or description (block,modName,modVal) of the block to find + * @param {Function|Object} Block Block or description (block,modName,modVal) of the block to find * @returns {BEMDOM} */ - findBlockOutside : function(elem, block) { - return this._findBlocks('closest', elem, block)[0] || null; + findBlockOutside : function(elem, Block) { + return this._findBlocks('closest', elem, Block)[0] || null; }, /** * Finds blocks on DOM elements of the current block or its elements * @param {String|jQuery} [elem] Block element - * @param {String|Object} block Name or description (block,modName,modVal) of the block to find + * @param {Function|Object} Block Block or description (block,modName,modVal) of the block to find * @returns {BEMDOM[]} */ findBlocksOn : function(elem, block) { @@ -319,28 +340,28 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ /** * Finds the first block on DOM elements of the current block or its elements * @param {String|jQuery} [elem] Block element - * @param {String|Object} block Name or description (block,modName,modVal) of the block to find + * @param {Function|Object} Block Block or description (block,modName,modVal) of the block to find * @returns {BEMDOM} */ - findBlockOn : function(elem, block) { - return this._findBlocks('', elem, block, true); + findBlockOn : function(elem, Block) { + return this._findBlocks('', elem, Block, true); }, - _findBlocks : function(select, elem, block, onlyFirst) { - if(!block) { - block = elem; + _findBlocks : function(select, elem, Block, onlyFirst) { + if(!Block) { + Block = elem; elem = undef; } var ctxElem = elem? (typeof elem === 'string'? this.findElem(elem) : elem) : this.domElem, - isSimpleBlock = typeof block === 'string', - blockName = isSimpleBlock? block : (block.block || block.blockName), + isSimpleBlock = typeof Block !== 'object', + blockName = (isSimpleBlock? Block : Block.block).getName(), selector = '.' + (isSimpleBlock? buildClass(blockName) : - buildClass(blockName, block.modName, block.modVal)) + + buildClass(blockName, Block.modName, Block.modVal)) + (onlyFirst? ':first' : ''), domElems = ctxElem.filter(selector); @@ -371,7 +392,7 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ * @param {String|Object} event Event name or event object * @param {Object} [data] Additional event data * @param {Function} fn Handler function, which will be executed in the block's context - * @returns {BEMDOM} this + * @returns {Block} this */ bindToDomElem : function(domElem, event, data, fn) { if(functions.isFunction(data)) { @@ -397,7 +418,7 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ * @param {String|Object} event Event name or event object * @param {Object} [data] Additional event data * @param {Function} fn Handler function, which will be executed in the block's context - * @returns {BEMDOM} this + * @returns {Block} this */ bindToDoc : function(event, data, fn) { this._needSpecialUnbind = true; @@ -410,7 +431,7 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ * @param {String|Object} event Event name or event object * @param {Object} [data] Additional event data * @param {Function} fn Handler function, which will be executed in the block's context - * @returns {BEMDOM} this + * @returns {Block} this */ bindToWin : function(event, data, fn) { this._needSpecialUnbind = true; @@ -424,7 +445,7 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ * @param {String|Object} event Event name or event object * @param {Object} [data] Additional event data * @param {Function} fn Handler function, which will be executed in the block's context - * @returns {BEMDOM} this + * @returns {Block} this */ bindTo : function(elem, event, data, fn) { var len = arguments.length; @@ -458,13 +479,13 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ }, /** - * Removes event handlers from any DOM element - * @protected - * @param {jQuery} domElem DOM element where the event was being listened for - * @param {String|Object} event Event name or event object - * @param {Function} [fn] Handler function - * @returns {BEMDOM} this - */ + * Removes event handlers from any DOM element + * @protected + * @param {jQuery} domElem DOM element where the event was being listened for + * @param {String|Object} event Event name or event object + * @param {Function} [fn] Handler function + * @returns {Block} this + */ unbindFromDomElem : function(domElem, event, fn) { if(typeof event === 'string') { event = this._buildEventName(event); @@ -481,35 +502,35 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ }, /** - * Removes event handler from document - * @protected - * @param {String|Object} event Event name or event object - * @param {Function} [fn] Handler function - * @returns {BEMDOM} this - */ + * Removes event handler from document + * @protected + * @param {String|Object} event Event name or event object + * @param {Function} [fn] Handler function + * @returns {Block} this + */ unbindFromDoc : function(event, fn) { return this.unbindFromDomElem(doc, event, fn); }, /** - * Removes event handler from window - * @protected - * @param {String|Object} event Event name or event object - * @param {Function} [fn] Handler function - * @returns {BEMDOM} this - */ + * Removes event handler from window + * @protected + * @param {String|Object} event Event name or event object + * @param {Function} [fn] Handler function + * @returns {Block} this + */ unbindFromWin : function(event, fn) { return this.unbindFromDomElem(win, event, fn); }, /** - * Removes event handlers from the block's main DOM elements or its nested elements - * @protected - * @param {jQuery|String} [elem] Nested element - * @param {String|Object} event Event name or event object - * @param {Function} [fn] Handler function - * @returns {BEMDOM} this - */ + * Removes event handlers from the block's main DOM elements or its nested elements + * @protected + * @param {jQuery|String} [elem] Nested element + * @param {String|Object} event Event name or event object + * @param {Function} [fn] Handler function + * @returns {Block} this + */ unbindFrom : function(elem, event, fn) { var argLen = arguments.length; if(argLen === 1) { @@ -555,10 +576,10 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ if(event.indexOf('.') < 0) return eventNameCache[event] = event + uniq; - var lego = '.bem_' + this.__self._name; + var ns = '.bem_' + this.__self._name; return eventNameCache[event] = event.split('.').map(function(e, i) { - return i === 0? e + lego : lego + '_' + e; + return i? ns + '_' + e : e + ns; }).join('') + uniq; }, @@ -597,7 +618,7 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ * @param {jQuery} [elem] Nested element * @param {String} modName Modifier name * @param {String} modVal Modifier value - * @returns {BEMDOM} this + * @returns {Block} this */ setMod : function(elem, modName, modVal) { if(elem && typeof modVal !== 'undefined' && elem.length > 1) { @@ -703,32 +724,21 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ /** * Finds elements nested in a block * @param {jQuery} [ctx=this.domElem] Element where search is being performed - * @param {String} names Nested element name (or names separated by spaces) - * @param {String} [modName] Modifier name - * @param {String} [modVal] Modifier value + * @param {String|Object} elem Element name or description elem, modName, modVal * @param {Boolean} [strictMode=false] * @returns {jQuery} DOM elements */ - findElem : function(ctx, names, modName, modVal, strictMode) { - if(typeof ctx === 'string') { - strictMode = modVal; - modVal = modName; - modName = names; - names = ctx; + findElem : function(ctx, elem, strictMode) { + if(!(ctx instanceof $)) { + strictMode = elem; + elem = ctx; ctx = this.domElem; } - if(typeof modName === 'boolean') { - strictMode = modName; - modName = undef; - } + typeof elem === 'string' && (elem = { elem : elem }); - var _self = this.__self, - selector = '.' + - names.split(' ').map(function(name) { - return _self.buildClass(name, modName, modVal); - }).join(',.'), - res = findDomElem(ctx, selector); + var res = findDomElem(ctx, this.buildSelector(elem.elem, elem.modName, elem.modVal)); + res.__bemElemName = elem.elem; return strictMode? this._filterFindElemResults(res) : res; }, @@ -746,48 +756,18 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ }); }, - /** - * Finds elements nested in a block - * @private - * @param {String} name Nested element name - * @param {String} [modName] Modifier name - * @param {String} [modVal] Modifier value - * @returns {jQuery} DOM elements - */ - _elem : function(name, modName, modVal) { - var key = name + buildModPostfix(modName, modVal), - res; - - if(!(res = this._elemCache[key])) { - res = this._elemCache[key] = this.findElem(name, modName, modVal); - res.__bemElemName = name; - } - - return res; - }, - /** * Lazy search for elements nested in a block (caches results) - * @param {String} names Nested element name (or names separated by spaces) - * @param {String} [modName] Modifier name - * @param {String} [modVal] Modifier value + * @private + * @param {String|Object} elem Element name or description elem, modName, modVal * @returns {jQuery} DOM elements */ - elem : function(names, modName, modVal) { - if(modName && typeof modName !== 'string') { - modName.__bemElemName = names; - return modName; - } + elem : function(elem) { + typeof elem === 'string' && (elem = { elem : elem }); - if(names.indexOf(' ') < 0) { - return this._elem(names, modName, modVal); - } + var key = elem.elem + buildModPostfix(elem.modName, elem.modVal); - var res = $([]); - names.split(' ').forEach(function(name) { - res = res.add(this._elem(name, modName, modVal)); - }, this); - return res; + return this._elemCache[key] || (this._elemCache[key] = this.findElem(elem)); }, /** @@ -806,7 +786,7 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ * @param {String} [names] Nested element name (or names separated by spaces) * @param {String} [modName] Modifier name * @param {String} [modVal] Modifier value - * @returns {BEMDOM} this + * @returns {Block} this */ dropElemCache : function(names, modName, modVal) { if(names) { @@ -883,36 +863,18 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ * @private */ _destruct : function() { - var _this = this, - _self = _this.__self; - - _this._needSpecialUnbind && _self.doc.add(_self.win).unbind('.' + _this._uniqId); + if(this._needSpecialUnbind) { + var eventNs = '.' + this._uniqId; + doc.off(eventNs); + win.off(eventNs); + } - _this.__base(); + this.__base(); - delete uniqIdToBlock[_this.un()._uniqId]; + this.un(); } -}, /** @lends BEMDOM */{ - - /** - * Scope, will be set on onDomReady to `` - * @type jQuery - */ - scope : null, - - /** - * Document shortcut - * @type jQuery - */ - doc : doc, - - /** - * Window shortcut - * @type jQuery - */ - win : win, - +}, /** @lends Block */{ /** * Processes a block's live properties * @private @@ -934,126 +896,6 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ return res; }, - /** - * Initializes blocks on a fragment of the DOM tree - * @param {jQuery|String} [ctx=scope] Root DOM node - * @returns {jQuery} ctx Initialization context - */ - init : function(ctx) { - if(typeof ctx === 'string') { - ctx = $(ctx); - } else if(!ctx) ctx = DOM.scope; - - var uniqInitId = identify(); - findDomElem(ctx, BEM_SELECTOR).each(function() { - initBlocks($(this), uniqInitId); - }); - - this._runInitFns(); - - return ctx; - }, - - /** - * Destroys blocks on a fragment of the DOM tree - * @param {jQuery} ctx Root DOM node - * @param {Boolean} [excludeSelf=false] Exclude the main domElem - */ - destruct : function(ctx, excludeSelf) { - var _ctx; - if(excludeSelf) { - storeDomNodeParents(_ctx = ctx.children()); - ctx.empty(); - } else { - storeDomNodeParents(_ctx = ctx); - ctx.remove(); - } - - reverse.call(findDomElem(_ctx, BEM_SELECTOR)).each(function(_, domNode) { - var params = getParams(domNode); - objects.each(params, function(blockParams) { - if(blockParams.uniqId) { - var block = uniqIdToBlock[blockParams.uniqId]; - block? - removeDomNodeFromBlock(block, domNode) : - delete uniqIdToDomElems[blockParams.uniqId]; - } - }); - delete domElemToParams[identify(domNode)]; - }); - - // flush parent nodes storage that has been filled above - domNodesToParents = {}; - }, - - /** - * Replaces a fragment of the DOM tree inside the context, destroying old blocks and intializing new ones - * @param {jQuery} ctx Root DOM node - * @param {jQuery|String} content New content - * @returns {jQuery} Updated root DOM node - */ - update : function(ctx, content) { - this.destruct(ctx, true); - return this.init(ctx.html(content)); - }, - - /** - * Changes a fragment of the DOM tree including the context and initializes blocks. - * @param {jQuery} ctx Root DOM node - * @param {jQuery|String} content Content to be added - * @returns {jQuery} New content - */ - replace : function(ctx, content) { - var prev = ctx.prev(), - parent = ctx.parent(); - - this.destruct(ctx); - - return this.init(prev.length? - $(content).insertAfter(prev) : - $(content).prependTo(parent)); - }, - - /** - * Adds a fragment of the DOM tree at the end of the context and initializes blocks - * @param {jQuery} ctx Root DOM node - * @param {jQuery|String} content Content to be added - * @returns {jQuery} New content - */ - append : function(ctx, content) { - return this.init($(content).appendTo(ctx)); - }, - - /** - * Adds a fragment of the DOM tree at the beginning of the context and initializes blocks - * @param {jQuery} ctx Root DOM node - * @param {jQuery|String} content Content to be added - * @returns {jQuery} New content - */ - prepend : function(ctx, content) { - return this.init($(content).prependTo(ctx)); - }, - - /** - * Adds a fragment of the DOM tree before the context and initializes blocks - * @param {jQuery} ctx Contextual DOM node - * @param {jQuery|String} content Content to be added - * @returns {jQuery} New content - */ - before : function(ctx, content) { - return this.init($(content).insertBefore(ctx)); - }, - - /** - * Adds a fragment of the DOM tree after the context and initializes blocks - * @param {jQuery} ctx Contextual DOM node - * @param {jQuery|String} content Content to be added - * @returns {jQuery} New content - */ - after : function(ctx, content) { - return this.init($(content).insertAfter(ctx)); - }, - /** * Builds a full name for a live event * @private @@ -1075,7 +917,7 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ if(!storage) { storage = liveClassEventStorage[e] = {}; - DOM.scope.bind(e, $.proxy(this._liveClassTrigger, this)); + BEMDOM.scope.on(e, $.proxy(this._liveClassTrigger, this)); } storage = storage[className] || (storage[className] = { uniqIds : {}, fns : [] }); @@ -1170,6 +1012,7 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ * @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 + * @returns {Function} this */ liveBindTo : function(to, event, callback, invokeOnInit) { if(!event || functions.isFunction(event)) { @@ -1206,16 +1049,10 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ * @param {String} [elem] Name of the element or elements (space-separated) * @param {String} event Event name * @param {Function} [callback] Handler + * @returns {Function} this */ liveUnbindFrom : function(elem, event, callback) { - - if(!event || functions.isFunction(event)) { - callback = event; - event = elem; - elem = undef; - } - - if(elem && elem.indexOf(' ') > 1) { + if(elem.indexOf(' ') > 1) { elem.split(' ').forEach(function(elem) { this._liveClassUnbind( this.buildClass(elem), @@ -1235,20 +1072,23 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ * Helper for live initialization when a different block is initialized * @private * @param {String} event Event name - * @param {String} blockName Name of the block that should trigger a reaction when initialized + * @param {Function} Block Block that should emit a reaction when initialized * @param {Function} callback Handler to be called after successful initialization in the new block's context * @param {String} findFnName Name of the method for searching */ - _liveInitOnBlockEvent : function(event, blockName, callback, findFnName) { - var name = this._name; - blocks[blockName].on(event, function(e) { - var args = arguments, - blocks = e.target[findFnName](name); - - callback && blocks.forEach(function(block) { - callback.apply(block, args); - }); - }); + _liveInitOnBlockEvent : function(event, Block, callback, findFnName) { + Block.on( + event, + function(e) { + var args = arguments, + blocks = e.target[findFnName](this); + + callback && blocks.forEach(function(block) { + callback.apply(block, args); + }); + }, + this); + return this; }, @@ -1256,22 +1096,24 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ * Helper for live initialization for a different block's event on the current block's DOM element * @protected * @param {String} event Event name - * @param {String} blockName Name of the block that should trigger a reaction when initialized + * @param {Function} Block Block that should emit a reaction when initialized * @param {Function} callback Handler to be called after successful initialization in the new block's context + * @returns {Function} this */ - liveInitOnBlockEvent : function(event, blockName, callback) { - return this._liveInitOnBlockEvent(event, blockName, callback, 'findBlocksOn'); + liveInitOnBlockEvent : function(event, Block, callback) { + return this._liveInitOnBlockEvent(event, Block, callback, 'findBlocksOn'); }, /** * Helper for live initialization for a different block's event inside the current block * @protected * @param {String} event Event name - * @param {String} blockName Name of the block that should trigger a reaction when initialized + * @param {Function} Block Block that should emit a reaction when initialized * @param {Function} [callback] Handler to be called after successful initialization in the new block's context + * @returns {Function} this */ - liveInitOnBlockInsideEvent : function(event, blockName, callback) { - return this._liveInitOnBlockEvent(event, blockName, callback, 'findBlocksOutside'); + liveInitOnBlockInsideEvent : function(event, Block, callback) { + return this._liveInitOnBlockEvent(event, Block, callback, 'findBlocksOutside'); }, /** @@ -1281,6 +1123,7 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ * @param {Object} [data] Additional information that the handler gets as e.data * @param {Function} fn Handler * @param {Object} [fnCtx] Handler's context + * @returns {Function} this */ on : function(ctx, e, data, fn, fnCtx) { return typeof ctx === 'object' && ctx.jquery? @@ -1294,6 +1137,7 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ * @param {String} e Event name * @param {Function} [fn] Handler * @param {Object} [fnCtx] Handler context + * @returns {Function} this */ un : function(ctx, e, fn, fnCtx) { return typeof ctx === 'object' && ctx.jquery? @@ -1309,7 +1153,7 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ * @param {Object} [data] Additional information that the handler gets as e.data * @param {Function} fn Handler * @param {Object} [fnCtx] Handler context - * @returns {BEMDOM} this + * @returns {Function} this */ _liveCtxBind : function(ctx, e, data, fn, fnCtx) { if(typeof e === 'object') { @@ -1363,6 +1207,7 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ * @param {String|Object} e Event name * @param {Function} [fn] Handler * @param {Object} [fnCtx] Handler context + * @returns {Function} this */ _liveCtxUnbind : function(ctx, e, fn, fnCtx) { if(typeof e === 'object' && functions.isFunction(fn)) { // mod change event @@ -1467,20 +1312,187 @@ var DOM = BEM.decl('i-bem__dom',/** @lends BEMDOM.prototype */{ /** * Returns a block on a DOM element and initializes it if necessary - * @param {String} blockName Block name + * @param {Function} Block Block * @param {Object} params Block parameters * @returns {BEMDOM} */ -$.fn.bem = function(blockName, params) { - return initBlock(blockName, this, params, true); +$.fn.bem = function(Block, params) { + return initBlock(Block.getName(), this, params, true); +}; + +var BEMDOM = /** @exports */{ + + /** + * Scope + * @type jQuery + */ + scope : null, + + /** + * Document shortcut + * @type jQuery + */ + doc : doc, + + /** + * Window shortcut + * @type jQuery + */ + win : win, + + /** + * Base BEMDOM block + * @type Function + */ + Block : Block, + + /** + * Declares DOM-based block and creates a block class + * @param {String} blockName Block name + * @param {Function|Array[Function]} [baseBlocks] base block + mixes + * @param {Object} [props] Methods + * @param {Object} [staticProps] Static methods + * @returns {Function} Block class + */ + declBlock : function(blockName, baseBlocks, props, staticProps) { + if(!baseBlocks || (typeof baseBlocks === 'object' && !Array.isArray(baseBlocks))) { + staticProps = props; + props = baseBlocks; + baseBlocks = Block; + } + + return BEM.declBlock(blockName, baseBlocks, props, staticProps); + }, + + declMix : BEM.declMix, + + /** + * Initializes blocks on a fragment of the DOM tree + * @param {jQuery|String} [ctx=scope] Root DOM node + * @returns {jQuery} ctx Initialization context + */ + init : function(ctx) { + ctx = typeof ctx === 'string'? + $(ctx) : + ctx || BEMDOM.scope; + + var uniqInitId = identify(); + findDomElem(ctx, BEM_SELECTOR).each(function() { + initBlocks($(this), uniqInitId); + }); + + BEM._runInitFns(); + + return ctx; + }, + + /** + * Destroys blocks on a fragment of the DOM tree + * @param {jQuery} ctx Root DOM node + * @param {Boolean} [excludeSelf=false] Exclude the main domElem + */ + destruct : function(ctx, excludeSelf) { + var _ctx; + if(excludeSelf) { + storeDomNodeParents(_ctx = ctx.children()); + ctx.empty(); + } else { + storeDomNodeParents(_ctx = ctx); + ctx.remove(); + } + + reverse.call(findDomElem(_ctx, BEM_SELECTOR)).each(function(_, domNode) { + var params = getParams(domNode); + objects.each(params, function(blockParams) { + if(blockParams.uniqId) { + var block = uniqIdToBlock[blockParams.uniqId]; + block? + removeDomNodeFromBlock(block, domNode) : + delete uniqIdToDomElems[blockParams.uniqId]; + } + }); + delete domElemToParams[identify(domNode)]; + }); + + // flush parent nodes storage that has been filled above + domNodesToParents = {}; + }, + + /** + * Replaces a fragment of the DOM tree inside the context, destroying old blocks and intializing new ones + * @param {jQuery} ctx Root DOM node + * @param {jQuery|String} content New content + * @returns {jQuery} Updated root DOM node + */ + update : function(ctx, content) { + this.destruct(ctx, true); + return this.init(ctx.html(content)); + }, + + /** + * Changes a fragment of the DOM tree including the context and initializes blocks. + * @param {jQuery} ctx Root DOM node + * @param {jQuery|String} content Content to be added + * @returns {jQuery} New content + */ + replace : function(ctx, content) { + var prev = ctx.prev(), + parent = ctx.parent(); + + this.destruct(ctx); + + return this.init(prev.length? + $(content).insertAfter(prev) : + $(content).prependTo(parent)); + }, + + /** + * Adds a fragment of the DOM tree at the end of the context and initializes blocks + * @param {jQuery} ctx Root DOM node + * @param {jQuery|String} content Content to be added + * @returns {jQuery} New content + */ + append : function(ctx, content) { + return this.init($(content).appendTo(ctx)); + }, + + /** + * Adds a fragment of the DOM tree at the beginning of the context and initializes blocks + * @param {jQuery} ctx Root DOM node + * @param {jQuery|String} content Content to be added + * @returns {jQuery} New content + */ + prepend : function(ctx, content) { + return this.init($(content).prependTo(ctx)); + }, + + /** + * Adds a fragment of the DOM tree before the context and initializes blocks + * @param {jQuery} ctx Contextual DOM node + * @param {jQuery|String} content Content to be added + * @returns {jQuery} New content + */ + before : function(ctx, content) { + return this.init($(content).insertBefore(ctx)); + }, + + /** + * Adds a fragment of the DOM tree after the context and initializes blocks + * @param {jQuery} ctx Contextual DOM node + * @param {jQuery|String} content Content to be added + * @returns {jQuery} New content + */ + after : function(ctx, content) { + return this.init($(content).insertAfter(ctx)); + } }; // Set default scope after DOM ready $(function() { - DOM.scope = $('body'); + BEMDOM.scope = $('body'); }); -provide(DOM); +provide(BEMDOM); }); @@ -1491,8 +1503,8 @@ var origDefine = modules.define; modules.define = function(name, deps, decl) { origDefine.apply(modules, arguments); - name !== 'i-bem__dom_init' && arguments.length > 2 && ~deps.indexOf('i-bem__dom') && - modules.define('i-bem__dom_init', [name], function(provide, _, prev) { + name !== 'i-bem-dom__init' && arguments.length > 2 && ~deps.indexOf('i-bem-dom') && + modules.define('i-bem-dom__init', [name], function(provide, _, prev) { provide(prev); }); }; diff --git a/common.blocks/i-bem-dom/i-bem-dom.spec.js b/common.blocks/i-bem-dom/i-bem-dom.spec.js new file mode 100644 index 000000000..70bd930f7 --- /dev/null +++ b/common.blocks/i-bem-dom/i-bem-dom.spec.js @@ -0,0 +1,1081 @@ +modules.define( + 'spec', + ['i-bem', 'i-bem-dom', 'objects', 'jquery', 'sinon', 'BEMHTML'], + function(provide, BEM, BEMDOM, objects, $, sinon, BEMHTML) { + +describe('i-bem-dom', function() { + describe('getMod', function() { + it('should return properly extracted mod from html', function() { + var Block = BEMDOM.declBlock('block'), + rootNode; + + [ + { + cls : '', + val : '' + }, + { + cls : 'block_m1_v1', + val : 'v1' + }, + { + cls : 'block_m1_v1 bla-block_m1_v2', + val : 'v1' + }, + { + cls : 'bla-block_m1_v2 block_m1_v1', + val : 'v1' + }, + { + cls : 'block_m1', + val : true + } + ].forEach(function(data) { + (rootNode = $('
')).bem(Block).getMod('m1') + .should.be.eql(data.val); + BEMDOM.destruct(rootNode); + }); + + delete BEM.blocks['block']; + }); + }); + + describe('getMods', function() { + it('should return properly extracted block mods from html', function() { + var Block = BEMDOM.declBlock('block'), + rootNode; + + [ + { + cls : '', + mods : { js : 'inited' } + }, + { + cls : 'block_m1_v1', + mods : { js : 'inited', m1 : 'v1' } + }, + { + cls : 'block_m1_v1 block_m2_v2 bla-block_m4_v3 block_m4_v4', + mods : { js : 'inited', m1 : 'v1', m2 : 'v2', m4 : 'v4' } + }, + { + cls : 'bla-block_m1_v1 block_m2_v2 block_m3_v3 bla-block_m3_v4 block_m4', + mods : { js : 'inited', m2 : 'v2', m3 : 'v3', m4 : true } + } + ].forEach(function(data) { + (rootNode = $('
')).bem(Block).getMods() + .should.be.eql(data.mods); + BEMDOM.destruct(rootNode); + }); + + delete BEM.blocks['block']; + }); + + it('should return properly extracted elem mods from html', function() { + var Block = BEMDOM.declBlock('block'), + rootNode; + + [ + { + cls : 'block__e1_m1_v1', + mods : { m1 : 'v1' } + }, + { + cls : 'block__e1_m1_v1 block__e1_m2_v2 bla-block__e1_m4_v3 block__e1_m4_v4', + mods : { m1 : 'v1', m2 : 'v2', m4 : 'v4' } + }, + { + cls : 'bla-block__e1_m1_v1 block__e1_m2_v2 block__e1_m3_v3 bla-block__e1_m3_v4 block__e1_m4', + mods : { m2 : 'v2', m3 : 'v3', m4 : true } + } + ].forEach(function(data) { + var block = (rootNode = $('
')).bem(Block); + block.getMods(block.elem('e1')).should.be.eql(data.mods); + BEMDOM.destruct(rootNode); + }); + + delete BEM.blocks['block']; + }); + }); + + describe('setMod', function() { + it('should properly set CSS classes', function() { + var Block = BEMDOM.declBlock('block'), + rootNode; + + [ + { + beforeCls : 'block i-bem', + afterCls : 'block i-bem block_js_inited block_m1_v1', + mods : { m1 : 'v1' } + }, + { + beforeCls : 'block i-bem block_m6 block_m7_v7', + afterCls : 'block i-bem block_js_inited block_m1_v1 block_m2_v2 block_m3 block_m4_v4 block_m5', + mods : { m1 : 'v1', m2 : 'v2', m3 : true, m4 : 'v4', m5 : true, m6 : false, m7 : '' } + } + ].forEach(function(data) { + var block = (rootNode = $('
')).bem(Block); + + objects.each(data.mods, function(modVal, modName) { + modName === 'm3'? + block.setMod(modName) : + block.setMod(modName, modVal); + }); + + block.domElem[0].className.should.be.equal(data.afterCls); + + BEMDOM.destruct(rootNode); + }); + + delete BEM.blocks['block']; + }); + + it('should properly set elem CSS classes', function() { + var Block = BEMDOM.declBlock('block'), + rootNode; + + [ + { + beforeCls : 'block__elem', + afterCls : 'block__elem block__elem_m1_v1', + mods : { m1 : 'v1' } + }, + { + beforeCls : 'block__elem block__elem_m6 block__elem_m7_v7', + afterCls : 'block__elem block__elem_m1_v1 block__elem_m2_v2 block__elem_m3 block__elem_m4_v4 block__elem_m5', + mods : { m1 : 'v1', m2 : 'v2', m3 : true, m4 : 'v4', m5 : true, m6 : false, m7 : '' } + } + ].forEach(function(data) { + var block = (rootNode = $('
')).bem(Block), + elem = block.elem('elem'); + + objects.each(data.mods, function(modVal, modName) { + modName === 'm3'? + block.setMod(elem, modName) : + block.setMod(elem, modName, modVal); + }); + + elem[0].className.should.be.equal(data.afterCls); + + BEMDOM.destruct(rootNode); + }); + + delete BEM.blocks['block']; + }); + }); + + describe('elemify', function() { + var rootNode, instance; + beforeEach(function() { + var Block = BEMDOM.declBlock('block'); + rootNode = BEMDOM.init(BEMHTML.apply({ + block : 'block', + js : true, + content : { elem : 'e1', mix : { elem : 'e2' } } + })); + instance = rootNode.bem(Block); + }); + afterEach(function() { + BEMDOM.destruct(rootNode); + delete BEM.blocks['block']; + }); + + it('shouldn\'t change given elem', function() { + var elem1 = instance.elem('e1'); + instance.elemify(elem1, 'e2'); + instance.__self._extractElemNameFrom(elem1).should.be.equal('e1'); + }); + + it('should return', function() { + var elem = instance.elemify(instance.elem('e1'), 'e2'); + instance.__self._extractElemNameFrom(elem).should.be.equal('e2'); + }); + }); + + describe('findBlocksInside', function() { + function getBlockIds(blocks) { + return blocks.map(function(block) { + return block.params.id; + }); + } + + var rootNode, rootBlock, B1Block; + beforeEach(function() { + var RootBlock = BEMDOM.declBlock('root'); + B1Block = BEMDOM.declBlock('b1'); + rootNode = $(BEMHTML.apply( + { + block : 'root', + content : { + block : 'b1', + js : { id : '1' }, + content : [ + { block : 'b2' }, + { + block : 'b1', + mods : { m1 : 'v1' }, + js : { id : '2' } + }, + { + block : 'b3', + content : { + block : 'b1', + mods : { m1 : 'v2' }, + js : { id : '3' }, + content : { + block : 'b1', + mods : { m1 : true }, + js : { id : '4' } + } + } + } + ] + } + })); + rootBlock = BEMDOM.init(rootNode).bem(RootBlock); + }); + + afterEach(function() { + BEMDOM.destruct(rootNode); + delete BEM.blocks['b-root']; + delete BEM.blocks['b1']; + }); + + it('should find all blocks by block', function() { + getBlockIds(rootBlock.findBlocksInside(B1Block)).should.be.eql(['1', '2', '3', '4']); + }); + + it('should find all blocks by block, modName and modVal', function() { + getBlockIds(rootBlock.findBlocksInside({ block : B1Block, modName : 'm1', modVal : 'v1' })) + .should.be.eql(['2']); + }); + + it('should find all blocks by block and boolean mod', function() { + getBlockIds(rootBlock.findBlocksInside({ block : B1Block, modName : 'm1', modVal : true })) + .should.be.eql(['4']); + }); + }); + + describe('BEMDOM.init', function() { + var spy, rootNode; + beforeEach(function() { + spy = sinon.spy(); + }); + + afterEach(function() { + BEMDOM.destruct(rootNode); + delete BEM.blocks['block']; + }); + + it('should init block', function() { + BEMDOM.declBlock('block', { + onSetMod : { + js : { + inited : spy + } + } + }); + + rootNode = BEMDOM.init(BEMHTML.apply({ + tag : 'div', + content : { block : 'block', js : true } + })); + + spy.should.have.been.called; + }); + + it('should init block with multiple DOM nodes', function(done) { + BEMDOM.declBlock('block', { + onSetMod : { + js : { + inited : function() { + this.domElem.length.should.be.equal(2); + done(); + } + } + } + }); + + rootNode = BEMDOM.init(BEMHTML.apply({ + tag : 'div', + content : [ + { block : 'block', js : { id : 'id' } }, + { block : 'block', js : { id : 'id' } } + ] + })); + }); + + it('shouldn\'t init live block', function() { + BEMDOM.declBlock('block', { + onSetMod : { + js : { + inited : spy + } + } + }, { + live : true + }); + + rootNode = BEMDOM.init(BEMHTML.apply({ + tag : 'div', + content : { block : 'block', js : true } + })); + + BEMDOM.init(rootNode); + spy.should.not.have.been.called; + }); + + it('should allow to pass string', function() { + BEMDOM.declBlock('block', { + onSetMod : { + js : { + inited : spy + } + } + }); + + rootNode = BEMDOM.init(BEMHTML.apply({ + tag : 'div', + content : { block : 'block', js : true } + })); + + spy.should.have.been.called; + }); + }); + + describe('BEMDOM.destruct', function() { + var spy, rootNode; + beforeEach(function() { + spy = sinon.spy(); + }); + + afterEach(function() { + delete BEM.blocks['block']; + }); + + it('should destruct block only if it has no dom nodes', function() { + BEMDOM.declBlock('block', { + onSetMod : { + js : { + '' : spy + } + } + }); + + rootNode = BEMDOM.init(BEMHTML.apply({ + tag : 'div', + content : [ + { block : 'block', js : { id : 'block' } }, + { block : 'block', js : { id : 'block' } } + ] + })); + + BEMDOM.destruct(rootNode.find('.block :eq(0)')); + spy.should.not.have.been.called; + + BEMDOM.destruct(rootNode.find('.block')); + spy.should.have.been.called; + + BEMDOM.destruct(rootNode); + }); + + it('should destruct implicitly inited block', function() { + var Block = BEMDOM.declBlock('block', { + onSetMod : { + js : { + '' : spy + } + } + }); + + rootNode = BEMDOM.init(BEMHTML.apply({ + tag : 'div', + content : { block : 'block' } + })); + + var blockNode = rootNode.find('.block'); + blockNode.bem(Block); + BEMDOM.destruct(blockNode); + spy.should.have.been.called; + }); + }); + + describe('BEMDOM.update', function() { + it('should properly update tree', function() { + var spyBlock1Destructed = sinon.spy(), + spyBlock2Inited = sinon.spy(); + + BEMDOM.declBlock('block1', { + onSetMod : { + js : { + '' : spyBlock1Destructed + } + } + }); + BEMDOM.declBlock('block2', { + onSetMod : { + js : { + inited : spyBlock2Inited + } + } + }); + + var rootNode = BEMDOM.init(BEMHTML.apply({ + tag : 'div', + content : { block : 'block1', js : true } })); + + BEMDOM.update(rootNode, BEMHTML.apply({ block : 'block2', js : true })); + + spyBlock1Destructed.called.should.be.true; + spyBlock2Inited.called.should.be.true; + + BEMDOM.destruct(rootNode); + delete BEM.blocks['block1']; + delete BEM.blocks['block2']; + }); + + it('should allow to pass simple string', function() { + var domElem = $('
'); + BEMDOM.update(domElem, 'simple string'); + domElem.html().should.be.equal('simple string'); + }); + }); + + describe('BEMDOM.replace', function() { + it('should properly replace tree', function() { + var spyBlock1Destructed = sinon.spy(), + spyBlock2Inited = sinon.spy(); + + BEMDOM.declBlock('block1', { + onSetMod : { + js : { + '' : spyBlock1Destructed + } + } + }); + BEMDOM.declBlock('block2', { + onSetMod : { + js : { + inited : spyBlock2Inited + } + } + }); + + var rootNode = BEMDOM.init(BEMHTML.apply({ + tag : 'div', + content : { block : 'block1', js : true } + })); + + BEMDOM.replace(rootNode.find('.block1'), BEMHTML.apply({ block : 'block2', js : true })); + + spyBlock1Destructed.should.have.been.calledOnce; + spyBlock2Inited.should.have.been.calledOnce; + + rootNode.html().should.be.equal('
'); + + BEMDOM.destruct(rootNode); + + rootNode = BEMDOM.init(BEMHTML.apply({ + tag : 'div', + content : [{ tag : 'p' }, { block : 'block1', js : true }, { tag : 'p' }] + })); + + BEMDOM.replace(rootNode.find('.block1'), BEMHTML.apply({ block : 'block2', js : true })); + + spyBlock1Destructed.should.have.been.calledTwice; + spyBlock2Inited.should.have.been.calledTwice; + + rootNode.html().should.be.equal('

'); + + delete BEM.blocks['block1']; + delete BEM.blocks['block2']; + }); + }); + + describe('params', function() { + it('should properly join params', function() { + var Block = BEMDOM.declBlock('block', { + getDefaultParams : function() { + return { p1 : 1 }; + } + }); + + BEMDOM.declBlock('block2', { + onSetMod : { + 'js' : { + 'inited' : function() { + var params = this.findBlockOn(Block).params; + params.p1.should.be.equal(1); + params.p2.should.be.equal(2); + params.p3.should.be.equal(3); + } + } + } + }); + + var rootNode = BEMDOM.init(BEMHTML.apply({ + tag : 'div', + content : [ + { block : 'block', js : { id : 'bla', p2 : 2 }, mix : { block : 'block2', js : true } }, + { block : 'block', js : { id : 'bla', p3 : 3 } } + ] + })); + + BEMDOM.destruct(rootNode); + delete BEM.blocks['block']; + delete BEM.blocks['block2']; + }); + }); + + describe('emit', function() { + it('should emit context event with target', function() { + var Block = BEMDOM.declBlock('block', { + onSetMod : { + 'js' : { + 'inited' : function() { + this.emit('event'); + } + } + } + }), + rootNode = $('
'), + spy = sinon.spy(); + + BEM.blocks['block'].on(rootNode, 'event', spy); + BEMDOM.update(rootNode, BEMHTML.apply({ block : 'block', js : true })); + + var block = rootNode.find('.block').bem(Block); + + spy.should.have.been.calledOnce; + spy.args[0][0].target.should.be.equal(block); + + delete BEM.blocks['block']; + }); + }); + + describe('containsDomElem', function() { + var domElem, block, block2; + beforeEach(function() { + var Block = BEMDOM.declBlock('block'), + Block2 = BEMDOM.declBlock('block2'); + + domElem = $(BEMHTML.apply([ + { + block : 'block', + js : { id : '1' }, + content : [ + { elem : 'e1' }, + { elem : 'e2' } + ] + }, + { + block : 'block', + js : { id : '1' }, + content : [ + { elem : 'e1' }, + { elem : 'e2', content : { elem : 'e2-1' } } + ] + }, + { + block : 'block2' + } + ])); + + BEMDOM.init(domElem); + block = domElem.filter('.block').bem(Block); + block2 = domElem.filter('.block2').bem(Block2); + }); + + afterEach(function() { + BEMDOM.destruct(domElem); + delete BEM.blocks['block']; + delete BEM.blocks['block2']; + }); + + it('should properly checks for nested dom elem', function() { + block.containsDomElem(block.elem('e2-1')).should.be.true; + block.containsDomElem(block2.domElem).should.be.false; + }); + + it('should properly checks for nested dom elem with given context', function() { + block.containsDomElem(block.elem('e1'), block.elem('e2-1')).should.be.false; + block.containsDomElem(block.elem('e2'), block.elem('e2-1')).should.be.true; + }); + }); + + describe('DOM events', function() { + var Block, block, spy1, spy2, spy3, spy4, spy5, + data = { data : 'data' }; + + beforeEach(function() { + spy1 = sinon.spy(); + spy2 = sinon.spy(); + spy3 = sinon.spy(); + spy4 = sinon.spy(); + spy5 = sinon.spy(); + }); + + afterEach(function() { + BEMDOM.destruct(block.domElem); + delete BEM.blocks['block']; + }); + + describe('block domElem events', function() { + beforeEach(function() { + Block = BEMDOM.declBlock('block', { + onSetMod : { + 'js' : { + 'inited' : function() { + this + .bindTo('click', spy1) + .bindTo('click', spy2) + .bindTo('click', data, spy3) + .bindTo({ 'click' : spy4 }, data); + } + } + } + }); + block = BEMDOM.init(BEMHTML.apply({ block : 'block' })).bem(Block); + }); + + it('should properly bind handlers', function() { + block.domElem.trigger('click'); + + spy1.should.have.been.called; + spy2.should.have.been.called; + + spy3.args[0][0].data.should.have.been.equal(data); + spy4.args[0][0].data.should.have.been.equal(data); + }); + + it('should properly unbind all handlers', function() { + block.unbindFrom('click'); + block.domElem.trigger('click'); + + spy1.should.not.have.been.called; + spy2.should.not.have.been.called; + }); + + it('should properly unbind specified handler', function() { + block.unbindFrom('click', spy1); + block.domElem.trigger('click'); + + spy1.should.not.have.been.called; + spy2.should.have.been.called; + }); + }); + + describe('block elems (as string) events', function() { + var spy3; + + beforeEach(function() { + spy3 = sinon.spy(); + Block = BEMDOM.declBlock('block', { + onSetMod : { + 'js' : { + 'inited' : function() { + this + .bindTo('e1', 'click', spy1) + .bindTo('e2', 'click', spy2) + .bindTo('e2', 'click', spy3) + .bindTo('e2', 'click', data, spy4) + .bindTo('e2', { 'click' : spy5 }, data); + } + } + } + }); + block = BEMDOM.init(BEMHTML.apply({ block : 'block', content : [{ elem : 'e1' }, { elem : 'e2' }] })) + .bem(Block); + }); + + it('should properly bind handlers', function() { + block.elem('e2').trigger('click'); + + spy1.should.not.have.been.called; + spy2.should.have.been.called; + spy3.should.have.been.called; + + spy4.args[0][0].data.should.have.been.equal(data); + spy5.args[0][0].data.should.have.been.equal(data); + }); + + it('should properly unbind all handlers', function() { + block.unbindFrom('e2', 'click'); + block.elem('e2').trigger('click'); + + spy1.should.not.have.been.called; + spy2.should.not.have.been.called; + spy3.should.not.have.been.called; + }); + + it('should properly unbind specified handler', function() { + block.unbindFrom('e2', 'click', spy2); + block.elem('e2').trigger('click'); + + spy1.should.not.have.been.called; + spy2.should.not.have.been.called; + spy3.should.have.been.called; + }); + }); + + describe('block elems (as $) events', function() { + var spy3; + + beforeEach(function() { + spy3 = sinon.spy(); + Block = BEMDOM.declBlock('block', { + onSetMod : { + 'js' : { + 'inited' : function() { + this + .bindTo(this.elem('e1'), 'click', spy1) + .bindTo(this.elem('e2'), 'click', spy2) + .bindTo(this.elem('e2'), 'click', spy3) + .bindTo(this.elem('e2'), 'click', data, spy4) + .bindTo(this.elem('e2'), { 'click' : spy5 }, data); + } + } + } + }); + block = BEMDOM.init(BEMHTML.apply({ block : 'block', content : [{ elem : 'e1' }, { elem : 'e2' }] })) + .bem(Block); + }); + + it('should properly bind handlers', function() { + block.elem('e2').trigger('click'); + + spy1.should.not.have.been.called; + spy2.should.have.been.called; + spy3.should.have.been.called; + + spy4.args[0][0].data.should.have.been.equal(data); + spy5.args[0][0].data.should.have.been.equal(data); + }); + + it('should properly unbind all handlers', function() { + block.unbindFrom('e2', 'click'); + block.elem('e2').trigger('click'); + + spy1.should.not.have.been.called; + spy2.should.not.have.been.called; + spy3.should.not.have.been.called; + }); + + it('should properly unbind specified handler', function() { + block.unbindFrom('e2', 'click', spy2); + block.elem('e2').trigger('click'); + + spy1.should.not.have.been.called; + spy2.should.not.have.been.called; + spy3.should.have.been.called; + }); + }); + + describe('document events', function() { + beforeEach(function() { + Block = BEMDOM.declBlock('block', { + onSetMod : { + 'js' : { + 'inited' : function() { + this + .bindToDoc('click', spy1) + .bindToDoc('click', spy2) + .bindToDoc('click', data, spy3) + .bindToDoc({ 'click' : spy4 }, data); + } + } + } + }); + block = BEMDOM.init(BEMHTML.apply({ block : 'block' })).bem(Block); + }); + + it('should properly bind handlers', function() { + BEMDOM.doc.trigger('click'); + + spy1.should.have.been.called; + spy2.should.have.been.called; + + spy3.args[0][0].data.should.have.been.equal(data); + spy4.args[0][0].data.should.have.been.equal(data); + }); + + it('should properly unbind all handlers', function() { + block.unbindFromDoc('click'); + BEMDOM.doc.trigger('click'); + + spy1.should.not.have.been.called; + spy2.should.not.have.been.called; + }); + + it('should properly unbind specified handler', function() { + block.unbindFromDoc('click', spy1); + BEMDOM.doc.trigger('click'); + + spy1.should.not.have.been.called; + spy2.should.have.been.called; + }); + + it('should properly unbind all handlers on block destruct', function() { + BEMDOM.destruct(block.domElem); + BEMDOM.doc.trigger('click'); + + spy1.should.not.have.been.called; + spy2.should.not.have.been.called; + }); + }); + + describe('window events', function() { + beforeEach(function() { + Block = BEMDOM.declBlock('block', { + onSetMod : { + 'js' : { + 'inited' : function() { + this + .bindToWin('resize', spy1) + .bindToWin('resize', spy2) + .bindToWin('resize', data, spy3) + .bindToWin({ 'resize' : spy4 }, data); + } + } + } + }); + block = BEMDOM.init(BEMHTML.apply({ block : 'block' })).bem(Block); + }); + + it('should properly bind handlers', function() { + BEMDOM.win.trigger('resize'); + + spy1.should.have.been.called; + spy2.should.have.been.called; + + spy3.args[0][0].data.should.have.been.equal(data); + spy4.args[0][0].data.should.have.been.equal(data); + }); + + it('should properly unbind all handlers', function() { + block.unbindFromWin('resize'); + BEMDOM.win.trigger('resize'); + + spy1.should.not.have.been.called; + spy2.should.not.have.been.called; + }); + + it('should properly unbind specified handler', function() { + block.unbindFromWin('resize', spy1); + BEMDOM.win.trigger('resize'); + + spy1.should.not.have.been.called; + spy2.should.have.been.called; + }); + + it('should properly unbind all handlers on block destruct', function() { + BEMDOM.destruct(block.domElem); + BEMDOM.win.trigger('resize'); + + spy1.should.not.have.been.called; + spy2.should.not.have.been.called; + }); + }); + }); + + describe('closestElem', function() { + it('should return the closest element', function() { + var Block = BEMDOM.declBlock('block'), + rootNode = $(BEMHTML.apply({ + block : 'block', + js : true, + content : { + elem : 'elem1', + content : { + elem : 'elem2' + } + } + })), + block = rootNode.bem(Block), + closest = block.closestElem(block.elem('elem2'), 'elem1'); + + closest[0].should.be.equal(block.elem('elem1')[0]); + + BEMDOM.destruct(rootNode); + delete BEM.blocks['block']; + }); + }); + + describe('liveInitOnBlockInsideEvent', function() { + it('should init and call handler on live initialization', function() { + var spyInit = sinon.spy(), + spyHandler = sinon.spy(), + Block2 = BEMDOM.declBlock('block2', {}, {}), + Block1 = BEMDOM.declBlock('block1', { + onSetMod : { + js : { + inited : spyInit + } + } + }, { + live : function() { + this.liveInitOnBlockInsideEvent('event', Block2, spyHandler); + } + }), + rootNode = BEMDOM.init(BEMHTML.apply({ + block : 'block1', + js : true, + content : { + block : 'block2', + js : true + } + })), + block = rootNode.find('.block2').bem(Block2); + + spyInit.called.should.be.false; + spyHandler.called.should.be.false; + + block.emit('event'); + + spyInit.called.should.be.true; + spyHandler.called.should.be.true; + + BEMDOM.destruct(rootNode); + delete BEM.blocks['block1']; + delete BEM.blocks['block2']; + }); + }); + + describe('modules.define patching', function() { + it('should provide BEMDOM block', function(done) { + var name = 'b' + Math.random(), + spy = sinon.spy(); + + modules.define(name, ['i-bem-dom'], function(provide, BEMDOM) { + spy(); + provide(BEMDOM.declBlock(this.name, {})); + }); + + modules.define(name, function(provide, Prev) { + spy(); + Prev.should.be.eql(BEM.blocks[this.name]); + provide(BEMDOM.declBlock(this.name, {})); + }); + + modules.require([name], function(Block) { + spy.should.have.been.calledTwice; + Block.should.be.eql(BEM.blocks[name]); + done(); + }); + }); + }); + + describe('mod change events', function() { + var block; + beforeEach(function() { + block = $(BEMHTML.apply( + { + block : 'block', + content : [ + { elem : 'e1', mods : { 'mod1' : 'val1' } }, + { elem : 'e1', mods : { 'mod1' : 'val1' } }, + { elem : 'e2', mods : { 'mod1' : 'val1' } } + ] + })) + .bem(BEMDOM.declBlock('block')); + }); + + afterEach(function() { + delete BEM.blocks['block']; + }); + + it('should propagate destructing event', function() { + var spy = sinon.spy(), + Block1 = BEMDOM.declBlock('block1', { + onSetMod : { + 'js' : { + 'inited' : function() { + Block2.on(this.domElem, { modName : 'js', modVal : '' }, spy); + } + } + } + }), + Block2 = BEMDOM.declBlock('block2'), + domElem = $(BEMHTML.apply({ + block : 'block1', + content : { + block : 'block2' + } + })).appendTo('body'); + + BEMDOM.init(domElem); + + var block1 = domElem.bem(Block1), + block2 = block1.findBlockInside(Block2); + + BEMDOM.destruct(block2.domElem); + + spy.should.have.been.called; + + delete BEM.blocks['block2']; + delete BEM.blocks['block1']; + }); + + describe('elems', function() { + it('should emit event on elem mod change with correct arguments', function() { + var spy1 = sinon.spy(), + spy2 = sinon.spy(), + spy3 = sinon.spy(), + spy4 = sinon.spy(), + elem = block.elem('e1'); + + block + .on({ elem : 'e1', modName : 'mod1', modVal : '*' }, spy1) + .on({ elem : 'e1', modName : 'mod1', modVal : 'val2' }, spy2) + .on({ elem : 'e1', modName : 'mod1', modVal : 'val3' }, spy3) + .on({ elem : 'e2', modName : 'mod1', modVal : 'val2' }, spy4) + .setMod(elem, 'mod1', 'val2'); + + spy1.should.have.been.called.twice; + spy2.should.have.been.called.twice; + spy3.should.not.have.been.called; + spy4.should.not.have.been.called; + + var eventData = spy1.args[0][1]; + eventData.modName.should.be.equal('mod1'); + eventData.modVal.should.be.equal('val2'); + eventData.oldModVal.should.be.equal('val1'); + eventData.elem[0].should.be.eql(elem[0]); + spy1.args[1][1].elem[0].should.be.eql(elem[1]); + }); + + it('should emit live event on elem mod change with correct arguments', function() { + var spy1 = sinon.spy(), + spy2 = sinon.spy(), + spy3 = sinon.spy(), + spy4 = sinon.spy(), + elem = block.elem('e1'); + + BEM.blocks['block'] + .on({ elem : 'e1', modName : 'mod1', modVal : '*' }, spy1) + .on({ elem : 'e1', modName : 'mod1', modVal : 'val2' }, spy2) + .on({ elem : 'e1', modName : 'mod1', modVal : 'val3' }, spy3) + .on({ elem : 'e2', modName : 'mod1', modVal : 'val2' }, spy4); + + block.setMod(elem, 'mod1', 'val2'); + + spy1.should.have.been.called.twice; + spy2.should.have.been.called.twice; + spy3.should.not.have.been.called; + spy4.should.not.have.been.called; + + var eventData = spy1.args[0][1]; + eventData.modName.should.be.equal('mod1'); + eventData.modVal.should.be.equal('val2'); + eventData.oldModVal.should.be.equal('val1'); + eventData.elem[0].should.be.eql(elem[0]); + spy1.args[1][1].elem[0].should.be.eql(elem[1]); + }); + }); + }); +}); + +provide(); + +}); diff --git a/common.blocks/i-bem/__dom/_elem-instances/i-bem__dom_elem-instances.deps.js b/common.blocks/i-bem/__dom/_elem-instances/i-bem__dom_elem-instances.deps.js deleted file mode 100644 index 3eee2bd20..000000000 --- a/common.blocks/i-bem/__dom/_elem-instances/i-bem__dom_elem-instances.deps.js +++ /dev/null @@ -1,8 +0,0 @@ -({ - mustDeps : [ - { - block : 'i-bem', - mods : { 'elem-instances' : true } - } - ] -}) diff --git a/common.blocks/i-bem/__dom/_elem-instances/i-bem__dom_elem-instances.js b/common.blocks/i-bem/__dom/_elem-instances/i-bem__dom_elem-instances.js deleted file mode 100644 index 96f719fcf..000000000 --- a/common.blocks/i-bem/__dom/_elem-instances/i-bem__dom_elem-instances.js +++ /dev/null @@ -1,320 +0,0 @@ -/** - * @module i-bem__dom - */ - -modules.define( - 'i-bem__dom', - ['i-bem', 'i-bem__internal', 'jquery'], - function(provide, BEM, INTERNAL, $, BEMDOM) { - -var buildClass = INTERNAL.buildClass, - NAME_PATTERN = INTERNAL.NAME_PATTERN, - MOD_DELIM = INTERNAL.MOD_DELIM, - ELEM_DELIM = INTERNAL.ELEM_DELIM, - blocks = BEM.blocks, - slice = Array.prototype.slice; - -/** - * @class BEMDOM - * @augments BEMDOM - * @exports - */ -provide(BEMDOM.decl(/** @lends BEMDOM.prototype */{ - - /** - * Delegates native getMod helper to element's instance - * @protected - * @param {jQuery} [elem] Nested element - * @param {String} modName Modifier name - * @returns {String} Modifier value - */ - getMod : function(elem, modName) { - var elemClass; - - if(elem && modName && blocks[elemClass = this.__self._buildElemClass(elem)]) { - return this.__base.call(this.findBlockOn(elem, elemClass), modName); - } - return this.__base(elem, modName); - }, - - /** - * Delegates native getMods helper to element's instance - * @protected - * @param {jQuery} [elem] Nested element - * @param {String} [modName1, ..., modNameN] Modifier names - * @returns {Object} Hash of modifier values - */ - getMods : function(elem) { - var elemClass; - - if(elem && typeof elem !== 'string' && blocks[elemClass = this.__self._buildElemClass(elem)]) { - return this.__base.apply(this.findBlockOn(elem, elemClass), slice.call(arguments, 1)); - } - return this.__base.apply(this, arguments); - }, - - /** - * Delegates native setMod helper to element's instances - * @protected - * @param {jQuery} [elem] Nested element - * @param {String} modName Modifier name - * @param {String} modVal Modifier value - * @returns {BEM} - */ - setMod : function(elem, modName, modVal) { - var elemClass; - - if(elem && typeof modVal !== 'undefined' && blocks[elemClass = this.__self._buildElemClass(elem)]) { - this - .findBlocksOn(elem, elemClass) - .forEach(function(instance) { - this.__base.call(instance, modName, modVal); - }, this); - return this; - } - return this.__base(elem, modName, modVal); - }, - - /** - * Returns and initializes (if necessary) the own block of current element - * @returns {BEMDOM} - */ - block : function() { - return this._block || (this._block = this.findBlockOutside(this.__self._blockName)); - }, - - /** - * Executes handlers for setting modifiers - * If element sets modifier to itself, it executes onElemSetMod handlers of the own block - * @private - * @param {String} prefix - * @param {String} elemName Element name - * @param {String} modName Modifier name - * @param {String} modVal Modifier value - * @param {Array} modFnParams Handler parameters - */ - _callModFn : function(prefix, elemName, modName, modVal, modFnParams) { - var result = this.__base.apply(this, arguments), - selfElemName = this.__self._elemName; - - if(selfElemName) { - this.__base.call( - this.block(), - prefix, - elemName || selfElemName, - modName, - modVal, - elemName? modFnParams : [this.domElem].concat(modFnParams) - ) === false && (result = false); - } - return result; - }, - - /** - * Filters results of findElem helper execution in strict mode - * @param {jQuery} res DOM elements - * @returns {jQuery} DOM elements - */ - _filterFindElemResults : function(res) { - var _self = this.__self, - blockSelector = '.' + _self._blockName, - domElem = _self._elemName? this.domElem.closest(blockSelector) : this.domElem; - return res.filter(function() { - return domElem.index($(this).closest(blockSelector)) > -1; - }); - }, - - /** - * Lazy search (caches results) for the first instance of defined element and intializes it (if necessary) - * @param {String|jQuery} elem Element - * @param {String} [modName] Modifier name - * @param {String} [modVal] Modifier value - * @returns {BEMDOM} - */ - elemInstance : function() { - return this._elemInstances(arguments, 'elem', 'findBlockOn'); - }, - - /** - * Lazy search (caches results) for instances of defined elements and intializes it (if necessary) - * @param {String|jQuery} elem Element - * @param {String} [modName] Modifier name - * @param {String} [modVal] Modifier value - * @returns {BEMDOM[]} - */ - elemInstances : function() { - return this._elemInstances(arguments, 'elem', 'findBlocksOn'); - }, - - /** - * Finds the first instance of defined element and intializes it (if necessary) - * @param {jQuery} [ctx=this.domElem] Element where search is being performed - * @param {String|jQuery} elem Element - * @param {String} [modName] Modifier name - * @param {String} [modVal] Modifier value - * @param {Boolean} [strictMode=false] - * @returns {BEMDOM} - */ - findElemInstance : function() { - return this._elemInstances(arguments, 'findElem', 'findBlockOn'); - }, - - /** - * Finds instances of defined elements and intializes it (if necessary) - * @param {jQuery} [ctx=this.domElem] Element where search is being performed - * @param {String|jQuery} elem Element - * @param {String} [modName] Modifier name - * @param {String} [modVal] Modifier value - * @param {Boolean} [strictMode=false] - * @returns {BEMDOM[]} - */ - findElemInstances : function() { - return this._elemInstances(arguments, 'findElem', 'findBlocksOn'); - }, - - _elemInstances : function(args, findElemMethod, findBlockMethod) { - var elem = args[0], - isString = typeof elem === 'string', - elemClass; - - if(args.length === 1 && !isString) { - elemClass = this.__self._buildElemClass(elem); - } else { - elemClass = buildClass(this.__self._blockName, args[isString? 0 : 1]); - elem = this[findElemMethod].apply(this, args); - } - return this[findBlockMethod](elem, elemClass); - }, - - /** - * Finds elements outside the context or current element - * @param {jQuery} [ctx=this.domElem] context (current element by default) - * @param {String} elemName Element name - * @returns {jQuery} DOM elements - */ - closestElem : function(ctx, elemName) { - if(!elemName) { - elemName = ctx; - ctx = this.domElem; - } - return this.__base(ctx, elemName); - }, - - /** - * Finds instance of defined element outside the context or current element - * @param {jQuery} [ctx=this.domElem] context (current element by default) - * @param {String} elemName Element name - * @returns {BEMDOM} - */ - closestElemInstance : function(ctx, elemName) { - return this.findBlockOn( - this.closestElem.apply(this, arguments), - buildClass(this.__self._blockName, elemName || ctx)); - }, - - /** - * Finds instances of defined elements outside the context or current element - * @param {jQuery} [ctx=this.domElem] context (current element by default) - * @param {String} elemName Element name - * @returns {BEMDOM[]} - */ - closestElemInstances : function(ctx, elemName) { - return this.findBlocksOn( - this.closestElem.apply(this, arguments), - buildClass(this.__self._blockName, elemName || ctx)); - } - -}, /** @lends BEMDOM */{ - - /** - * Auto-declarator for elements - * @protected - * @param {Object} name Instance name - * @param {Object} [props] Methods - * @param {Object} [staticProps] Static methods - * @param {Object} [_autoDecl] Auto-declaration flag - * @returns {Function} - */ - decl : function(name, props, staticProps, _autoDecl) { - if(_autoDecl) { - var names = name.split(ELEM_DELIM); - return this.__base({ block : names[0], elem : names[1] }, props, staticProps); - } else { - return this.__base.apply(this, arguments); - } - }, - - /** - * Helper for live initialization for an own block's event - * @protected - * @param {String} event Event name - * @param {Function} [callback] Handler to be called after successful initialization in the new element's context - * @returns {Function} this - */ - liveInitOnBlockEvent : function(event, callback) { - return (typeof callback === 'string')? - this.__base.apply(this, arguments) : - this._liveInitOnOwnBlockEvent(event, callback); - }, - - _liveInitOnOwnBlockEvent : function(event, callback) { - var name = this._elemName; - blocks[this._blockName].on(event, function(e) { - var args = arguments, - elems = e.target.findElemInstances(name, true); - - callback && elems.forEach(function(elem) { - callback.apply(elem, args); - }); - }); - return this; - }, - - /** - * Builds a CSS class corresponding to the element's instance with extraction it's name from the specified DOM element - * @private - * @param {jQuery} elem Element - * @returns {String} - */ - _buildElemClass : function(elem) { - return buildClass(this._blockName, this._extractElemNameFrom(elem)); - }, - - /** - * Builds a CSS class corresponding to the block/element and modifier - * @param {String} [elem] Element name - * @param {String} [modName] Modifier name - * @param {String} [modVal] Modifier value - * @returns {String} - */ - buildClass : function(elem, modName, modVal) { - return this._elemName && elem && (modVal || !modName)? - buildClass(this._blockName, elem, modName, modVal) : - buildClass(this._name, elem, modName, modVal); - }, - - /** - * Builds a prefix for the CSS class of a DOM element or nested element of the block, based on modifier name - * @private - * @param {String} modName Modifier name - * @param {jQuery|String} [elem] Element - * @returns {String} - */ - _buildModClassPrefix : function(modName, elem) { - return (elem? - this._blockName + ELEM_DELIM + (typeof elem === 'string'? elem : this._extractElemNameFrom(elem)) : - this._name) + - MOD_DELIM + modName; - }, - - /** - * Builds a regular expression for extracting names of elements nested in a block - * @private - * @returns {RegExp} - */ - _buildElemNameRE : function() { - return new RegExp(this._blockName + ELEM_DELIM + '(' + NAME_PATTERN + ')(?:\\s|$)'); - } -})); - -}); diff --git a/common.blocks/i-bem/__dom/_elem-instances/i-bem__dom_elem-instances.ru.desc.wiki b/common.blocks/i-bem/__dom/_elem-instances/i-bem__dom_elem-instances.ru.desc.wiki deleted file mode 100644 index 363fd9046..000000000 --- a/common.blocks/i-bem/__dom/_elem-instances/i-bem__dom_elem-instances.ru.desc.wiki +++ /dev/null @@ -1,249 +0,0 @@ -Подключение данного модификатора позволяет работать с элементами почти так же, как и с обычными блоками, т.е. любой (но не обязательно каждый) элемент может иметь свой ##BEM##-инстанс. API инстансов элементов аналогично API блоков с некоторыми различиями, описанными ниже. - -=== Декларация - -Декларация элемента - -%%hl js -BEMDOM.decl({ block: 'menu', elem: 'item' }, { /* properties */ }, { /* static properties */ }); -%% - -Декларация модификатора элемента: - -%%hl js -BEMDOM.decl({ - block: 'menu', - elem: 'item', - modName: 'state', - modVal: 'current' -}, { - /* properties */ -}, { - /* static properties */ -}); -%% - -Модификаторы элементов работают так же, как модификаторы блоков. - -=== BEM-инстансы элементов - -Для доступа к ##BEM##-инстансам элементов используется хелпер ##findElemInstances##, API которого аналогично хелперу ##findElem##: - -%%hl js -// поиск всех вложенных элементов 'item' -this.findElemInstances('item'); - -// строгий поиск всех вложенных элементов 'item' -this.findElemInstances('item', true); - -// поиск всех вложенных модификаторов элементов 'item' -this.findElemInstances('item', 'state', 'current'); - -// строгий поиск всех вложенных модификаторов элементов 'item' -this.findElemInstances('item', 'state', 'current', true); - -// поиск всех элементов 'item' внутри указанного контекста -this.findElemInstances(ctx, 'item'); - -// строгий поиск всех элементов 'item' внутри указанного контекста -this.findElemInstances(ctx, 'item', true); - -// поиск всех модификаторов элементов 'item' внутри указанного контекста -this.findElemInstances(ctx, 'item', 'state', 'current'); - -// строгий поиск всех модификаторов элементов 'item' внутри указанного контекста -this.findElemInstances(ctx, 'item', 'state', 'current', true); -%% - -При необходимости, инстансы найденных элементов инициализируются. - -Строгий поиск подразумевает фильтрацию элементов вложенных блоков с таким же именем, как у текущего блока: - -%%hl js -{ - block: 'menu', - content: [ - { - elem: 'item' // (1) - }, - { - block: 'menu', - content: { - elem: 'item' // (2) - } - } - ] -} -%% -%%hl js -// this => block 'menu' -this.findElemInstances('item'); // найдет инстансы (1) и (2) -this.findElemInstances('item', true); // найдет только инстанс (1) -%% - -По аналогии с хелпером ##elem##, кэширующим результаты своей работы, для минимизации количества операций с ##DOM## рекомендуется использовать кэширующий поиск ##BEM##-инстансов элементов: - -%%hl js -// кэширующий поиск всех вложенных элементов 'item' -this.elemInstances('item'); - -// кэширующий поиск всех вложенных модификаторов элемента 'item' -this.elemInstances('item', 'state', 'current'); -%% - -Также с помощью этого хелпера можно вернуть инстансы элементов, расположенных на ##DOM##-нодах указанной ##jQuery## коллекции: - -%%hl js -this.elemInstances(domElem); -%% - -Для поиска одного (первого) ##BEM##-инстанса элемента есть дополнительные формы этих хелперов с аналогичным API: - -%%hl js -// поиск одного вложенного элемента 'item' -this.findElemInstance('item'); - -// кэширующий поиск одного вложенного элемента 'item' -this.elemInstance('item'); - -// вернуть инстанс элемента, расположенного на первой DOM-ноде указанной jQuery-коллекции -this.elemInstance(domElem); -%% - -Если необходимо вернуть ##BEM##-инстанс элемента, на ##DOM##-ноде которого подмешаны другие элементы того же блока: - -%%hl js -this.elemInstance(this.elemify(domElem, 'item')); -%% - -=== Поиск снаружи контекста - -Существует хелпер для поиска элемента снаружи указанного контекста: - -%%hl js -{ - block: 'menu', - content: { - elem: 'item', - content: { - elem: 'link' - } - } -} -%% -%%hl js -// this => block 'menu' -this.closestElem(this.elem('link'), 'item'); -%% - -При поиске от имени ##BEM##-инстанса элемента можно не указывать контекст поиска, тогда поиск будет производиться относительно ##DOM##-ноды этого элемента: - -%%hl js -// this => element 'menu__link' -this.closestElem('item'); -%% - -По аналогии с хелперами ##elem## и ##findElem##, хелпер ##closestElem## возвращает ##jQuery##-коллекцию. Для доступа к ##BEM##-инстансам элементов снаружи контекста используются хелперы ##closestElemInstance## и ##closestElemInstances##: - -%%hl js -// this => block 'menu' -this.closestElemInstance(this.elem('link'), 'item'); -this.closestElemInstances(this.elem('link'), 'item'); -%% -%%hl js -// this => element 'menu__link' -this.closestElemInstance('item'); -%% - -=== Доступ к родительскому блоку - -По аналогии с методом ##elem## для получения элемента блока существует метод ##block## для получения блока элемента. - -%%hl js -// this => element 'menu__item' -this.block() // эквивалентно this.findBlockOutside('menu') с кэшированием результата поиска -%% - -=== Поиск элементов и блоков - -Элемент ищет только вложенные в него элементы: - -%%hl js -{ - block: 'menu', - content: [ - { - elem: 'item', - content: { - elem: 'link' // (1) - } - }, - { - elem: 'link' // (2) - } - ] -} -%% - -%%hl js -// this => element 'menu__item' -this.findElem('link'); // будет найден только элемент (1). -%% - -Если нужно найти все элементы ##'link'##, поиск нужно производить от имени блока-родителя: - -%%hl js -// this => element 'menu__item' -this.block().findElem('link'); // будут найдены элементы (1) и (2). -%% - -Все это справедливо также для хелпера ##elem##, хелперов поиска блоков (##findBlockInside## и др.) и описанных выше хелперов для поиска ##BEM##-инстансов элементов. - -=== Реакция на установку модификаторов - -Блок делегирует установку модификаторов тем элементам, у которых есть свои ##BEM##-декларации. -Поэтому обработчики ##onSetMod## элемента выполнятся, если родительский блок установит этому элементу соответствующие модификаторы: - -%%hl js -// this => element 'menu__item' -onSetMod: { - 'state': { - 'current': function() { - // код обработчика - } - } -} -%% -%%hl js -// this => block 'menu' -this.setMod(this.elem('item'), 'state', 'current'); // выполнится задекларированный выше обработчик -%% - -Обработчики ##onElemSetMod## блока выполнятся, если инстанс элемента сам установит себе соответствующие модификаторы: - -%%hl js -// this => block 'menu' -onElemSetMod: { - 'item': { - 'state': { - 'current': function() { - // код обработчика - } - } - } -} -%% -%%hl js -// this => element 'menu__item' -this.setMod('state', 'current'); // выполнится задекларированный выше обработчик -%% - -Обработчики ##onElemSetMod## в контексте инстанса элемента не используются. - -=== Отложенная инициализация и live-события - -Рекомендуется по возможности всегда использовать отложенную инициализацию ##BEM##-инстансов элементов. Они, как и обычные блоки, поддерживают все хелперы для работы с ##live##-событиями. Кроме того, хелпер ##liveInitOnBlockEvent## имеет дополнительную форму для инициализации по событию родительского блока - для этого просто опускается второй параметр (имя блока): - -%%hl js -this.liveInitOnBlockEvent('switch', function() { /* обработчик */ }); -%% \ No newline at end of file diff --git a/common.blocks/i-bem/__dom/_elem-instances/i-bem__dom_elem-instances.ru.md b/common.blocks/i-bem/__dom/_elem-instances/i-bem__dom_elem-instances.ru.md deleted file mode 100644 index d28206985..000000000 --- a/common.blocks/i-bem/__dom/_elem-instances/i-bem__dom_elem-instances.ru.md +++ /dev/null @@ -1,249 +0,0 @@ -Подключение данного модификатора позволяет работать с элементами почти так же, как и с обычными блоками, т.е. любой (но не обязательно каждый) элемент может иметь свой BEM-инстанс. API инстансов элементов аналогично API блоков с некоторыми различиями, описанными ниже. - -### Декларация - -Декларация элемента - -```javascript -BEMDOM.decl({ block: 'menu', elem: 'item' }, { /* properties */ }, { /* static properties */ }); -``` - -Декларация модификатора элемента: - -```javascript -BEMDOM.decl({ - block: 'menu', - elem: 'item', - modName: 'state', - modVal: 'current' -}, { - /* properties */ -}, { - /* static properties */ -}); -``` - -Модификаторы элементов работают так же, как модификаторы блоков. - -### BEM-инстансы элементов - -Для доступа к BEM-инстансам элементов используется хелпер ```findElemInstances```, API которого аналогично хелперу ```findElem```: - -```javascript -// поиск всех вложенных элементов 'item' -this.findElemInstances('item'); - -// строгий поиск всех вложенных элементов 'item' -this.findElemInstances('item', true); - -// поиск всех вложенных модификаторов элементов 'item' -this.findElemInstances('item', 'state', 'current'); - -// строгий поиск всех вложенных модификаторов элементов 'item' -this.findElemInstances('item', 'state', 'current', true); - -// поиск всех элементов 'item' внутри указанного контекста -this.findElemInstances(ctx, 'item'); - -// строгий поиск всех элементов 'item' внутри указанного контекста -this.findElemInstances(ctx, 'item', true); - -// поиск всех модификаторов элементов 'item' внутри указанного контекста -this.findElemInstances(ctx, 'item', 'state', 'current'); - -// строгий поиск всех модификаторов элементов 'item' внутри указанного контекста -this.findElemInstances(ctx, 'item', 'state', 'current', true); -``` - -При необходимости, инстансы найденных элементов инициализируются. - -Строгий поиск подразумевает фильтрацию элементов вложенных блоков с таким же именем, как у текущего блока: - -```javascript -{ - block: 'menu', - content: [ - { - elem: 'item' // (1) - }, - { - block: 'menu', - content: { - elem: 'item' // (2) - } - } - ] -} -``` -```javascript -// this => block 'menu' -this.findElemInstances('item'); // найдет инстансы (1) и (2) -this.findElemInstances('item', true); // найдет только инстанс (1) -``` - -По аналогии с хелпером ```elem```, кэширующим результаты своей работы, для минимизации количества операций с DOM рекомендуется использовать кэширующий поиск BEM-инстансов элементов: - -```javascript -// кэширующий поиск всех вложенных элементов 'item' -this.elemInstances('item'); - -// кэширующий поиск всех вложенных модификаторов элемента 'item' -this.elemInstances('item', 'state', 'current'); -``` - -Также с помощью этого хелпера можно вернуть инстансы элементов, расположенных на DOM-нодах указанной jQuery-коллекции: - -```javascript -this.elemInstances(domElem); -``` - -Для поиска одного (первого) BEM-инстанса элемента есть дополнительные формы этих хелперов с аналогичным API: - -```javascript -// поиск одного вложенного элемента 'item' -this.findElemInstance('item'); - -// кэширующий поиск одного вложенного элемента 'item' -this.elemInstance('item'); - -// вернуть инстанс элемента, расположенного на первой DOM-ноде указанной jQuery-коллекции -this.elemInstance(domElem); -``` - -Если необходимо вернуть BEM-инстанс элемента, на DOM-ноде которого подмешаны другие элементы того же блока: - -```javascript -this.elemInstance(this.elemify(domElem, 'item')); -``` - -### Поиск снаружи контекста - -Существует хелпер для поиска элемента снаружи указанного контекста: - -```javascript -{ - block: 'menu', - content: { - elem: 'item', - content: { - elem: 'link' - } - } -} -``` -```javascript -// this => block 'menu' -this.closestElem(this.elem('link'), 'item'); -``` - -При поиске от имени BEM-инстанса элемента можно не указывать контекст поиска, тогда поиск будет производиться относительно DOM-ноды этого элемента: - -```javascript -// this => element 'menu__link' -this.closestElem('item'); -``` - -По аналогии с хелперами ```elem``` и ```findElem```, хелпер ```closestElem``` возвращает jQuery-коллекцию. Для доступа к BEM-инстансам элементов снаружи контекста используются хелперы ```closestElemInstance``` и ```closestElemInstances```: - -```javascript -// this => block 'menu' -this.closestElemInstance(this.elem('link'), 'item'); -this.closestElemInstances(this.elem('link'), 'item'); -``` -```javascript -// this => element 'menu__link' -this.closestElemInstance('item'); -``` - -### Доступ к родительскому блоку - -По аналогии с методом `elem` для получения элемента блока существует метод `block` для получения блока элемента. - -```javascript -// this => element 'menu__item' -this.block() // эквивалентно this.findBlockOutside('menu') с кэшированием результата поиска -``` - -### Поиск элементов и блоков - -Элемент ищет только вложенные в него элементы: - -```javascript -{ - block: 'menu', - content: [ - { - elem: 'item', - content: { - elem: 'link' // (1) - } - }, - { - elem: 'link' // (2) - } - ] -} -``` - -```javascript -// this => element 'menu__item' -this.findElem('link'); // будет найден только элемент (1). -``` - -Если нужно найти все элементы ```'link'```, поиск нужно производить от имени блока-родителя: - -```javascript -// this => element 'menu__item' -this.block().findElem('link'); // будут найдены элементы (1) и (2). -``` - -Все это справедливо также для хелпера ```elem```, хелперов поиска блоков (```findBlockInside``` и др.) и описанных выше хелперов для поиска BEM-инстансов элементов. - -### Реакция на установку модификаторов - -Блок делегирует установку модификаторов тем элементам, у которых есть свои BEM-декларации. -Поэтому обработчики ```onSetMod``` элемента выполнятся, если родительский блок установит этому элементу соответствующие модификаторы: - -```javascript -// this => element 'menu__item' -onSetMod: { - 'state': { - 'current': function() { - // код обработчика - } - } -} -``` -```javascript -// this => block 'menu' -this.setMod(this.elem('item'), 'state', 'current'); // выполнится задекларированный выше обработчик -``` - -Обработчики ```onElemSetMod``` блока выполнятся, если инстанс элемента сам установит себе соответствующие модификаторы: - -```javascript -// this => block 'menu' -onElemSetMod: { - 'item': { - 'state': { - 'current': function() { - // код обработчика - } - } - } -} -``` -```javascript -// this => element 'menu__item' -this.setMod('state', 'current'); // выполнится задекларированный выше обработчик -``` - -Обработчики ```onElemSetMod``` в контексте инстанса элемента не используются. - -### Отложенная инициализация и live-события - -Рекомендуется по возможности всегда использовать отложенную инициализацию BEM-инстансов элементов. Они, как и обычные блоки, поддерживают все хелперы для работы с live-событиями. Кроме того, хелпер ```liveInitOnBlockEvent``` имеет дополнительную форму для инициализации по событию родительского блока - для этого просто опускается второй параметр (имя блока): - -```javascript -this.liveInitOnBlockEvent('switch', function() { /* обработчик */ }); -``` \ No newline at end of file diff --git a/common.blocks/i-bem/__dom/_elem-instances/i-bem__dom_elem-instances.spec.js b/common.blocks/i-bem/__dom/_elem-instances/i-bem__dom_elem-instances.spec.js deleted file mode 100644 index a58035e35..000000000 --- a/common.blocks/i-bem/__dom/_elem-instances/i-bem__dom_elem-instances.spec.js +++ /dev/null @@ -1,382 +0,0 @@ -modules.define( - 'spec', - ['i-bem__dom', 'jquery', 'sinon', 'BEMHTML'], - function(provide, DOM, $, sinon, BEMHTML) { - -describe('i-bem__dom_elem-instances', function() { - describe('elemInstance', function() { - it('should return the instance of element', function() { - DOM.decl('block', {}, {}); - DOM.decl({ block : 'block', elem : 'elem' }, {}, {}); - - var rootNode = $(BEMHTML.apply({ - block : 'block', - js : true, - content : { - elem : 'elem', - js : true - } - })), - block = rootNode.bem('block'), - elem = block.elemInstance('elem'); - - elem.should.be.instanceOf(DOM.blocks['block__elem']); - elem.__self.getName(true).should.be.equal('elem'); - - DOM.destruct(rootNode); - delete DOM.blocks['block']; - delete DOM.blocks['block__elem']; - }); - }); - - describe('block', function() { - it('should return instance of the own block', function() { - DOM.decl('block', {}, {}); - DOM.decl({ block : 'block', elem : 'elem' }, {}, {}); - - var rootNode = $(BEMHTML.apply({ - block : 'block', - js : true, - content : { - elem : 'elem', - js : true - } - })), - block = rootNode.bem('block'), - elem = block.elemInstance('elem'); - - elem.block().should.be.equal(block); - - DOM.destruct(rootNode); - delete DOM.blocks['block']; - delete DOM.blocks['block__elem']; - }); - }); - - describe('closestElem', function() { - it('should return the closest element', function() { - DOM.decl('block', {}, {}); - DOM.decl({ block : 'block', elem : 'elem2' }, {}, {}); - - var rootNode = $(BEMHTML.apply({ - block : 'block', - js : true, - content : { - elem : 'elem1', - content : { - elem : 'elem2', - js : true - } - } - })), - block = rootNode.bem('block'), - elem2 = block.elemInstance('elem2'), - closest = elem2.closestElem('elem1'); - - closest[0].should.be.equal(block.elem('elem1')[0]); - - DOM.destruct(rootNode); - delete DOM.blocks['block']; - delete DOM.blocks['block__elem2']; - }); - }); - - describe('mods', function() { - it('should update element\'s modifier properly', function() { - DOM.decl('block', {}, {}); - DOM.decl({ block : 'block', elem : 'elem' }, {}, {}); - - var rootNode = $(BEMHTML.apply({ - block : 'block', - js : true, - content : { - elem : 'elem', - elemMods : { mod : 'val1' }, - mix : { block : 'i-bem' }, - js : true - } - })), - block = rootNode.bem('block'), - elem = block.elemInstance('elem'); - - elem.hasMod('mod', 'val1').should.be.true; - - block.setMod(block.elem('elem'), 'mod', 'val2'); - elem.hasMod('mod', 'val2').should.be.true; - - DOM.destruct(rootNode); - delete DOM.blocks['block']; - delete DOM.blocks['block__elem']; - }); - - it('should call block\'s onElemSetMod handler when element updates it\'s own modifier', function() { - var spy = sinon.spy(); - DOM.decl('block', { - onElemSetMod : { - elem : { - mod : { - val : spy - } - } - } - }, {}); - DOM.decl({ block : 'block', elem : 'elem' }, {}, {}); - - var rootNode = $(BEMHTML.apply({ - block : 'block', - js : true, - content : { - elem : 'elem', - mix : { block : 'i-bem' }, - js : true - } - })), - block = rootNode.bem('block'), - elem = block.elemInstance('elem'); - - elem.setMod('mod', 'val'); - spy.called.should.be.true; - - DOM.destruct(rootNode); - delete DOM.blocks['block']; - delete DOM.blocks['block__elem']; - }); - - it('should call block\'s onElemSetMod handler when element updates modifier of another element', function() { - var spy1 = sinon.spy(), - spy2 = sinon.spy(); - - DOM.decl('block', { - onElemSetMod : { - elem1 : { - mod : { - val : spy1 - } - }, - elem2 : { - mod : { - val : spy2 - } - } - } - }, {}); - DOM.decl({ block : 'block', elem : 'elem1' }, {}, {}); - - var rootNode = $(BEMHTML.apply({ - block : 'block', - js : true, - content : { - elem : 'elem1', - mix : { block : 'i-bem' }, - js : true, - content : { elem : 'elem2' } - } - })), - block = rootNode.bem('block'), - elem1 = block.elemInstance('elem1'), - elem2 = block.elem('elem2'); - - elem1.setMod(elem2, 'mod', 'val'); - - spy1.called.should.be.false; - spy2.called.should.be.true; - - DOM.destruct(rootNode); - delete DOM.blocks['block']; - delete DOM.blocks['block__elem']; - }); - }); - - describe('findElem', function() { - it('should filter nested block\'s elements in strict mode', function() { - DOM.decl('block', {}, {}); - DOM.decl({ block : 'block', elem : 'elem' }, {}, {}); - - var rootNode = $(BEMHTML.apply({ - block : 'block', - js : true, - content : { - elem : 'elem', - mix : { block : 'i-bem' }, - js : true, - content : [ - { elem : 'nested' }, - { - block : 'block', - content : { elem : 'nested' } - } - ] - } - })), - block = rootNode.bem('block'), - elem = block.elemInstance('elem'), - nested = elem.findElem('nested', true); - - nested.length.should.be.equal(1); - nested[0].should.be.equal(elem.domElem[0].children[0]); - - DOM.destruct(rootNode); - delete DOM.blocks['block']; - delete DOM.blocks['block__elem']; - }); - }); - - describe('elemParams', function() { - it('should extract element\'s parameters properly', function() { - DOM.decl('block', {}, {}); - DOM.decl({ block : 'block', elem : 'elem1' }, {}, {}); - - var rootNode = $(BEMHTML.apply({ - block : 'block', - js : true, - content : { - elem : 'elem1', - mix : { block : 'i-bem' }, - js : true, - content : [ - { - elem : 'elem2', - js : { p1 : 'v1' } - } - ] - } - })), - block = rootNode.bem('block'), - elem = block.elemInstance('elem1'), - elemParams = elem.elemParams('elem2'); - - elemParams.p1.should.be.equal('v1'); - - DOM.destruct(rootNode); - delete DOM.blocks['block']; - delete DOM.blocks['block__elem1']; - }); - }); - - describe('decl', function() { - it('should declare element properly on initialization', function() { - DOM.decl('block', {}, {}); - - var rootNode = $(BEMHTML.apply({ - block : 'block', - js : true, - content : { - elem : 'elem' - } - })), - block = rootNode.bem('block'), - elem = block.elemInstance('elem'); - - elem.__self.should.be.equal(DOM.blocks['block__elem']); - elem.__self.getName(true).should.be.equal('elem'); - - DOM.destruct(rootNode); - delete DOM.blocks['block']; - delete DOM.blocks['block__elem']; - }); - - it('should declare element\'s modifier properly', function() { - var E1 = DOM.decl({ block : 'block', elem : 'e1' }, {}, {}), - e2Class = E1.buildClass('e2'); - - E1.decl({ modName : 'mod', modVal : 'val' }, {}, {}); - - E1.buildClass('e2').should.be.equal(e2Class); - - delete DOM.blocks['block']; - delete DOM.blocks['block__e1']; - }); - - it('should inherit from itself properly', function() { - var Block = DOM.decl('block'), - method = function() {}; - - Block.decl({ method : method }); - - method.should.be.equal(Block.prototype.method); - - delete DOM.blocks['block']; - }); - }); - - describe('liveInitOnBlockEvent', function() { - it('should init and call handler on live initialization', function() { - var spyInit = sinon.spy(), - spyHandler = sinon.spy(); - - DOM.decl('block', {}, {}); - DOM.decl({ block : 'block', elem : 'elem' }, { - onSetMod : { - js : { - inited : spyInit - } - } - }, { - live : function() { - this.liveInitOnBlockEvent('event', spyHandler); - } - }); - - var rootNode = DOM.init($(BEMHTML.apply({ - block : 'block', - js : true, - content : { - elem : 'elem', - js : true, - mix : { block : 'i-bem' } - } - }))), - block = rootNode.bem('block'); - - spyInit.called.should.be.false; - spyHandler.called.should.be.false; - - block.emit('event'); - - spyInit.called.should.be.true; - spyHandler.called.should.be.true; - - DOM.destruct(rootNode); - delete DOM.blocks['block']; - delete DOM.blocks['block__elem']; - }); - }); - - describe('destruct', function() { - it('should destruct element\'s instance properly', function() { - var spy = sinon.spy(); - - DOM.decl('block', {}, {}); - DOM.decl({ block : 'block', elem : 'elem' }, { - onSetMod : { - js : { - '' : spy - } - } - }); - - var rootNode = DOM.init($(BEMHTML.apply({ - block : 'block', - js : true, - content : { - elem : 'elem', - js : true, - mix : { block : 'i-bem' } - } - }))); - - spy.called.should.be.false; - - DOM.destruct(rootNode); - - spy.called.should.be.true; - - delete DOM.blocks['block']; - delete DOM.blocks['block__elem']; - }); - }); -}); - -provide(); - -}); diff --git a/common.blocks/i-bem/__dom/_init/i-bem__dom_init.spec.js b/common.blocks/i-bem/__dom/_init/i-bem__dom_init.spec.js deleted file mode 100644 index 3674d1f6f..000000000 --- a/common.blocks/i-bem/__dom/_init/i-bem__dom_init.spec.js +++ /dev/null @@ -1,23 +0,0 @@ -modules.define( - 'spec', - ['i-bem__dom'], - function(provide, BEMDOM) { - -describe('i-bem__dom_init', function() { - it('block should exist on init', function(done) { - var name = 'b' + Math.random(); - - modules.define(name, ['i-bem__dom'], function(provide, BEMDOM) { - provide(BEMDOM.decl(this.name, {})); - }); - - modules.require(['i-bem__dom_init'], function() { - BEMDOM.blocks.should.have.property(name); - done(); - }); - }); -}); - -provide(); - -}); diff --git a/common.blocks/i-bem/__dom/_init/i-bem__dom_init_auto.deps.js b/common.blocks/i-bem/__dom/_init/i-bem__dom_init_auto.deps.js deleted file mode 100644 index 7909b11f3..000000000 --- a/common.blocks/i-bem/__dom/_init/i-bem__dom_init_auto.deps.js +++ /dev/null @@ -1,3 +0,0 @@ -({ - shouldDeps : { mod : 'init' } -}) diff --git a/common.blocks/i-bem/__dom/i-bem__dom.ru.title.txt b/common.blocks/i-bem/__dom/i-bem__dom.ru.title.txt deleted file mode 100644 index c199ba71f..000000000 --- a/common.blocks/i-bem/__dom/i-bem__dom.ru.title.txt +++ /dev/null @@ -1 +0,0 @@ -Хелперы для работы с DOM-представлением diff --git a/common.blocks/i-bem/__dom/i-bem__dom.spec.js b/common.blocks/i-bem/__dom/i-bem__dom.spec.js deleted file mode 100644 index e0d12de6e..000000000 --- a/common.blocks/i-bem/__dom/i-bem__dom.spec.js +++ /dev/null @@ -1,995 +0,0 @@ -modules.define( - 'spec', - ['i-bem__dom', 'objects', 'jquery', 'sinon', 'BEMHTML'], - function(provide, DOM, objects, $, sinon, BEMHTML) { - -describe('i-bem__dom', function() { - describe('getMod', function() { - it('should return properly extracted mod from html', function() { - DOM.decl('block', {}); - - var rootNode; - [ - { - cls : '', - val : '' - }, - { - cls : 'block_m1_v1', - val : 'v1' - }, - { - cls : 'block_m1_v1 bla-block_m1_v2', - val : 'v1' - }, - { - cls : 'bla-block_m1_v2 block_m1_v1', - val : 'v1' - }, - { - cls : 'block_m1', - val : true - } - ].forEach(function(data) { - (rootNode = $('
')).bem('block').getMod('m1') - .should.be.eql(data.val); - DOM.destruct(rootNode); - }); - - delete DOM.blocks['block']; - }); - }); - - describe('getMods', function() { - it('should return properly extracted block mods from html', function() { - DOM.decl('block', {}); - - var rootNode; - [ - { - cls : '', - mods : { js : 'inited' } - }, - { - cls : 'block_m1_v1', - mods : { js : 'inited', m1 : 'v1' } - }, - { - cls : 'block_m1_v1 block_m2_v2 bla-block_m4_v3 block_m4_v4', - mods : { js : 'inited', m1 : 'v1', m2 : 'v2', m4 : 'v4' } - }, - { - cls : 'bla-block_m1_v1 block_m2_v2 block_m3_v3 bla-block_m3_v4 block_m4', - mods : { js : 'inited', m2 : 'v2', m3 : 'v3', m4 : true } - } - ].forEach(function(data) { - (rootNode = $('
')).bem('block').getMods() - .should.be.eql(data.mods); - DOM.destruct(rootNode); - }); - - delete DOM.blocks['block']; - }); - - it('should return properly extracted elem mods from html', function() { - DOM.decl('block', {}); - - var rootNode; - [ - { - cls : 'block__e1_m1_v1', - mods : { m1 : 'v1' } - }, - { - cls : 'block__e1_m1_v1 block__e1_m2_v2 bla-block__e1_m4_v3 block__e1_m4_v4', - mods : { m1 : 'v1', m2 : 'v2', m4 : 'v4' } - }, - { - cls : 'bla-block__e1_m1_v1 block__e1_m2_v2 block__e1_m3_v3 bla-block__e1_m3_v4 block__e1_m4', - mods : { m2 : 'v2', m3 : 'v3', m4 : true } - } - ].forEach(function(data) { - var block = (rootNode = $('
')).bem('block'); - block.getMods(block.elem('e1')).should.be.eql(data.mods); - DOM.destruct(rootNode); - }); - - delete DOM.blocks['block']; - }); - }); - - describe('setMod', function() { - it('should properly set CSS classes', function() { - DOM.decl('block', {}); - - var rootNode; - [ - { - beforeCls : 'block i-bem', - afterCls : 'block i-bem block_js_inited block_m1_v1', - mods : { m1 : 'v1' } - }, - { - beforeCls : 'block i-bem block_m6 block_m7_v7', - afterCls : 'block i-bem block_js_inited block_m1_v1 block_m2_v2 block_m3 block_m4_v4 block_m5', - mods : { m1 : 'v1', m2 : 'v2', m3 : true, m4 : 'v4', m5 : true, m6 : false, m7 : '' } - } - ].forEach(function(data) { - var block = (rootNode = $('
')).bem('block'); - - objects.each(data.mods, function(modVal, modName) { - modName === 'm3'? - block.setMod(modName) : - block.setMod(modName, modVal); - }); - - block.domElem[0].className.should.be.equal(data.afterCls); - - DOM.destruct(rootNode); - }); - - delete DOM.blocks['block']; - }); - - it('should properly set elem CSS classes', function() { - DOM.decl('block', {}); - - var rootNode; - [ - { - beforeCls : 'block__elem', - afterCls : 'block__elem block__elem_m1_v1', - mods : { m1 : 'v1' } - }, - { - beforeCls : 'block__elem block__elem_m6 block__elem_m7_v7', - afterCls : 'block__elem block__elem_m1_v1 block__elem_m2_v2 block__elem_m3 block__elem_m4_v4 block__elem_m5', - mods : { m1 : 'v1', m2 : 'v2', m3 : true, m4 : 'v4', m5 : true, m6 : false, m7 : '' } - } - ].forEach(function(data) { - var block = (rootNode = $('
')).bem('block'), - elem = block.elem('elem'); - - objects.each(data.mods, function(modVal, modName) { - modName === 'm3'? - block.setMod(elem, modName) : - block.setMod(elem, modName, modVal); - }); - - elem[0].className.should.be.equal(data.afterCls); - - DOM.destruct(rootNode); - }); - - delete DOM.blocks['block']; - }); - }); - - describe('elemify', function() { - var rootNode, instance; - beforeEach(function() { - DOM.decl('block', {}); - rootNode = DOM.init($(BEMHTML.apply({ - block : 'block', - js : true, - content : { elem : 'e1', mix : { elem : 'e2' } } }))); - instance = rootNode.bem('block'); - }); - afterEach(function() { - DOM.destruct(rootNode); - delete DOM.blocks['block']; - }); - - it('shouldn\'t change given elem', function() { - var elem1 = instance.elem('e1'); - instance.elemify(elem1, 'e2'); - instance.__self._extractElemNameFrom(elem1).should.be.equal('e1'); - }); - - it('should return', function() { - var elem = instance.elemify(instance.elem('e1'), 'e2'); - instance.__self._extractElemNameFrom(elem).should.be.equal('e2'); - }); - }); - - describe('findBlocksInside', function() { - function getBlockIds(blocks) { - return blocks.map(function(block) { - return block.params.id; - }); - } - - var rootNode, rootBlock; - beforeEach(function() { - rootNode = $(BEMHTML.apply( - { - block : 'root', - content : { - block : 'b1', - js : { id : '1' }, - content : [ - { block : 'b2' }, - { - block : 'b1', - mods : { m1 : 'v1' }, - js : { id : '2' } - }, - { - block : 'b3', - content : { - block : 'b1', - mods : { m1 : 'v2' }, - js : { id : '3' }, - content : { - block : 'b1', - mods : { m1 : true }, - js : { id : '4' } - } - } - } - ] - } - })); - rootBlock = DOM.init(rootNode).bem('root'); - }); - - afterEach(function() { - DOM.destruct(rootNode); - delete DOM.blocks['b-root']; - delete DOM.blocks['b1']; - }); - - it('should find all blocks by name', function() { - getBlockIds(rootBlock.findBlocksInside('b1')).should.be.eql(['1', '2', '3', '4']); - }); - - it('should find all blocks by name, modName and modVal', function() { - getBlockIds(rootBlock.findBlocksInside({ block : 'b1', modName : 'm1', modVal : 'v1' })) - .should.be.eql(['2']); - }); - - it('should find all blocks by name and boolean mod', function() { - getBlockIds(rootBlock.findBlocksInside({ block : 'b1', modName : 'm1', modVal : true })) - .should.be.eql(['4']); - }); - }); - - describe('DOM.init', function() { - it('should init block', function() { - var spy = sinon.spy(); - DOM.decl('block', { - onSetMod : { - js : { - inited : spy - } - } - }); - - var rootNode = DOM.init($(BEMHTML.apply({ - tag : 'div', - content : { block : 'block', js : true } }))); - - spy.called.should.be.true; - - DOM.destruct(rootNode); - delete DOM.blocks['block']; - }); - - it('shouldn\'t init live block', function() { - var spy = sinon.spy(); - DOM.decl('block', { - onSetMod : { - js : { - inited : spy - } - } - }, { - live : true - }); - - var rootNode = DOM.init($(BEMHTML.apply({ - tag : 'div', - content : { block : 'block', js : true } }))); - - DOM.init(rootNode); - spy.called.should.be.false; - - DOM.destruct(rootNode); - delete DOM.blocks['block']; - }); - - it('should allow to pass string', function() { - var spy = sinon.spy(); - DOM.decl('block', { - onSetMod : { - js : { - inited : spy - } - } - }); - - var rootNode = DOM.init(BEMHTML.apply({ - tag : 'div', - content : { block : 'block', js : true } })); - - spy.called.should.be.true; - - DOM.destruct(rootNode); - delete DOM.blocks['block']; - }); - }); - - describe('DOM.destruct', function() { - it('should destruct block only if it has no dom nodes', function() { - var spy = sinon.spy(); - DOM.decl('block', { - onSetMod : { - js : { - '' : spy - } - } - }); - - var rootNode = DOM.init($(BEMHTML.apply({ - tag : 'div', - content : [ - { block : 'block', js : { id : 'block' } }, - { block : 'block', js : { id : 'block' } } - ] - }))); - - DOM.destruct(rootNode.find('.block :eq(0)')); - spy.called.should.be.false; - - DOM.destruct(rootNode.find('.block')); - spy.called.should.be.true; - - DOM.destruct(rootNode); - delete DOM.blocks['block']; - }); - - it('should destruct implicitly inited block', function() { - var spy = sinon.spy(); - DOM.decl('imp-block', { - onSetMod : { - js : { - '' : spy - } - } - }); - - var blockNode = DOM.init($(BEMHTML.apply({ block : 'imp-block' }))); - blockNode.bem('imp-block'); - DOM.destruct(blockNode); - spy.should.have.been.calledOnce; - - delete DOM.blocks['imp-block']; - }); - }); - - describe('DOM.update', function() { - it('should update tree', function() { - var spyBlock1Destructed = sinon.spy(), - spyBlock2Inited = sinon.spy(); - - DOM.decl('block1', { - onSetMod : { - js : { - '' : spyBlock1Destructed - } - } - }); - DOM.decl('block2', { - onSetMod : { - js : { - inited : spyBlock2Inited - } - } - }); - - var rootNode = DOM.init($(BEMHTML.apply({ - tag : 'div', - content : { block : 'block1', js : true } }))); - - DOM.update(rootNode, BEMHTML.apply({ block : 'block2', js : true })); - - spyBlock1Destructed.called.should.be.true; - spyBlock2Inited.called.should.be.true; - - DOM.destruct(rootNode); - delete DOM.blocks['block1']; - delete DOM.blocks['block2']; - }); - - it('should allow to pass simple string', function() { - var domElem = $('
'); - DOM.update(domElem, 'simple string'); - domElem.html().should.be.equal('simple string'); - }); - }); - - describe('DOM.replace', function() { - it('should properly replace tree', function() { - var spyBlock1Destructed = sinon.spy(), - spyBlock2Inited = sinon.spy(); - - DOM.decl('block1', { - onSetMod : { - js : { - '' : spyBlock1Destructed - } - } - }); - DOM.decl('block2', { - onSetMod : { - js : { - inited : spyBlock2Inited - } - } - }); - - var rootNode = DOM.init($(BEMHTML.apply({ - tag : 'div', - content : { block : 'block1', js : true } }))); - - DOM.replace(rootNode.find('.block1'), BEMHTML.apply({ block : 'block2', js : true })); - - spyBlock1Destructed.should.have.been.calledOnce; - spyBlock2Inited.should.have.been.calledOnce; - - rootNode.html().should.be.equal('
'); - - DOM.destruct(rootNode); - - rootNode = DOM.init($(BEMHTML.apply({ - tag : 'div', - content : [{ tag : 'p' }, { block : 'block1', js : true }, { tag : 'p' }] }))); - - DOM.replace(rootNode.find('.block1'), BEMHTML.apply({ block : 'block2', js : true })); - - spyBlock1Destructed.should.have.been.calledTwice; - spyBlock2Inited.should.have.been.calledTwice; - - rootNode.html().should.be.equal('

'); - - delete DOM.blocks['block1']; - delete DOM.blocks['block2']; - }); - }); - - describe('params', function() { - it('should properly join params', function() { - DOM.decl('block', { - getDefaultParams : function() { - return { p1 : 1 }; - } - }); - - DOM.decl('block2', { - onSetMod : { - 'js' : { - 'inited' : function() { - var params = this.findBlockOn('block').params; - params.p1.should.be.equal(1); - params.p2.should.be.equal(2); - params.p3.should.be.equal(3); - } - } - } - }); - - var rootNode = DOM.init($(BEMHTML.apply({ - tag : 'div', - content : [ - { block : 'block', js : { id : 'bla', p2 : 2 }, mix : { block : 'block2', js : true } }, - { block : 'block', js : { id : 'bla', p3 : 3 } } - ] - }))); - - DOM.destruct(rootNode); - delete DOM.blocks['block']; - delete DOM.blocks['block2']; - }); - }); - - describe('emit', function() { - it('should emit context event with target', function() { - DOM.decl('block', { - onSetMod : { - 'js' : { - 'inited' : function() { - this.emit('event'); - } - } - } - }); - - var rootNode = $('
'), - spy = sinon.spy(); - - DOM.blocks['block'].on(rootNode, 'event', spy); - DOM.update(rootNode, BEMHTML.apply({ block : 'block', js : true })); - - var block = rootNode.find('.block').bem('block'); - - spy.should.have.been.calledOnce; - spy.args[0][0].target.should.be.equal(block); - - delete DOM.blocks['block']; - }); - }); - - describe('containsDomElem', function() { - var domElem, block, block2; - beforeEach(function() { - DOM.decl('block'); - DOM.decl('block2'); - - domElem = $(BEMHTML.apply([ - { - block : 'block', - js : { id : '1' }, - content : [ - { elem : 'e1' }, - { elem : 'e2' } - ] - }, - { - block : 'block', - js : { id : '1' }, - content : [ - { elem : 'e1' }, - { elem : 'e2', content : { elem : 'e2-1' } } - ] - }, - { - block : 'block2' - } - ])); - - DOM.init(domElem); - block = domElem.filter('.block').bem('block'); - block2 = domElem.filter('.block2').bem('block2'); - }); - - afterEach(function() { - DOM.destruct(domElem); - delete DOM.blocks['block']; - delete DOM.blocks['block2']; - }); - - it('should properly checks for nested dom elem', function() { - block.containsDomElem(block.elem('e2-1')).should.be.true; - block.containsDomElem(block2.domElem).should.be.false; - }); - - it('should properly checks for nested dom elem with given context', function() { - block.containsDomElem(block.elem('e1'), block.elem('e2-1')).should.be.false; - block.containsDomElem(block.elem('e2'), block.elem('e2-1')).should.be.true; - }); - }); - - describe('DOM events', function() { - var block, spy1, spy2, spy3, spy4, spy5, spy6, spy7, spy8, spy9, - data = { data : 'data' }, - win = DOM.win, - doc = DOM.doc; - - beforeEach(function() { - spy1 = sinon.spy(); - spy2 = sinon.spy(); - spy3 = sinon.spy(); - spy4 = sinon.spy(); - spy5 = sinon.spy(); - spy6 = sinon.spy(); - spy7 = sinon.spy(); - spy8 = sinon.spy(); - spy9 = sinon.spy(); - - DOM.decl('block', { - bindToClick : function() { - this - .bindTo('click', this._handler1) - .bindTo('click', this._handler2) - .bindTo('elem', 'click', this._handler3) - .bindTo(this.elem('elem'), 'click', this._handler4) - .bindTo(this.elem('elem2'), 'click', this._handler5) - .bindToWin('resize', this._handler6) - .bindToWin('resize', this._handler7) - .bindToDoc('mouseup', this._handler8) - .bindToDoc('mouseup', this._handler9) - // bind with data - .bindTo('dblclick', data, this._handler1) - .bindTo('elem', 'dblclick', data, this._handler2) - .bindTo(this.elem('elem'), 'dblclick', data, this._handler3) - .bindToWin('winevent', data, this._handler4) - .bindToDoc('docevent', data, this._handler5) - // bind with data and event object - .bindTo({ 'mousedown' : this._handler1 }, data) - .bindTo('elem', { 'mousedown' : this._handler2 }, data) - .bindTo(this.elem('elem'), { 'mousedown' : this._handler3 }, data) - .bindToWin({ 'winevent2' : this._handler4 }, data) - .bindToDoc({ 'docevent2' : this._handler5 }, data); - }, - - _handler1 : spy1, - _handler2 : spy2, - _handler3 : spy3, - _handler4 : spy4, - _handler5 : spy5, - _handler6 : spy6, - _handler7 : spy7, - _handler8 : spy8, - _handler9 : spy9, - - unbindAllFromDomElem : function() { - this.unbindFrom('click'); - }, - - unbindHandler1FromDomElem : function() { - this.unbindFrom('click', this._handler1); - }, - - unbindAllFromElemByString : function() { - this.unbindFrom('elem', 'click'); - }, - - unbindAllFromElemByElem : function() { - this.unbindFrom(this.elem('elem'), 'click'); - }, - - unbindHandler3FromElemByString : function() { - this.unbindFrom('elem', 'click', this._handler3); - }, - - unbindClick4FromElemByElem : function() { - this.unbindFrom(this.elem('elem'), 'click', this._handler4); - }, - - unbindHandler6FromWin : function() { - this.unbindFromWin('resize', this._handler6); - }, - - unbindHandler8FromDoc : function() { - this.unbindFromDoc('mouseup', this._handler8); - } - }); - - block = DOM.init($(BEMHTML.apply({ block : 'block', content : { elem : 'elem' } }))).bem('block'); - block.bindToClick(); - }); - - afterEach(function() { - DOM.destruct(block.domElem); - delete DOM.blocks['block']; - }); - - it('should properly bind to block-self DOM elem', function() { - block.domElem.click(); - spy1.should.have.been.calledOnce; - spy2.should.have.been.calledOnce; - spy3.should.not.have.been.called; - spy4.should.not.have.been.called; - spy5.should.not.have.been.called; - }); - - it('should properly unbind to block-self DOM elem', function() { - block.unbindAllFromDomElem(); - block.domElem.click(); - spy1.should.not.have.been.called; - spy2.should.not.have.been.called; - }); - - it('should unbind from block-self DOM elem specified function only', function() { - block.unbindHandler1FromDomElem(); - block.domElem.click(); - spy1.should.not.have.been.called; - spy2.should.have.been.calledOnce; - }); - - it('should properly bind to block elem', function() { - block.elem('elem').click(); - spy3.should.have.been.calledOnce; - spy4.should.have.been.calledOnce; - spy5.should.not.have.been.called; - }); - - it('should properly unbind from block elem by string', function() { - block.unbindAllFromElemByString(); - block.elem('elem').click(); - spy3.should.not.have.been.called; - spy4.should.not.have.been.called; - }); - - it('should properly unbind from block elem by elem', function() { - block.unbindAllFromElemByElem(); - block.elem('elem').click(); - spy3.should.not.have.been.called; - spy4.should.not.have.been.called; - }); - - it('should properly unbind specified function from block elem by elem', function() { - block.unbindHandler3FromElemByString(); - block.elem('elem').click(); - spy3.should.not.have.been.called; - spy4.should.have.been.calledOnce; - }); - - it('should properly unbind specified function from block elem by string', function() { - block.unbindClick4FromElemByElem(); - block.elem('elem').click(); - spy3.should.have.been.calledOnce; - spy4.should.not.have.been.called; - }); - - it('should properly bind to window event', function() { - win.trigger('resize'); - spy6.should.have.been.calledOnce; - spy7.should.have.been.calledOnce; - }); - - it('should properly unbind from window event', function() { - block.unbindFromWin('resize'); - win.trigger('resize'); - spy6.should.not.have.been.called; - spy7.should.not.have.been.called; - }); - - it('should properly unbind specified function from window event', function() { - block.unbindHandler6FromWin(); - win.trigger('resize'); - spy6.should.not.have.been.called; - spy7.should.have.been.calledOnce; - }); - - it('should properly bind to document event', function() { - doc.trigger('mouseup'); - spy8.should.have.been.calledOnce; - spy9.should.have.been.calledOnce; - }); - - it('should properly unbind from document event', function() { - block.unbindFromWin('resize'); - doc.trigger('resize'); - spy8.should.not.have.been.called; - spy9.should.not.have.been.called; - }); - - it('should properly unbind specified function from document event', function() { - block.unbindHandler8FromDoc(); - doc.trigger('mouseup'); - spy8.should.not.have.been.called; - spy9.should.have.been.calledOnce; - }); - - it('should properly bind with aditional event data', function() { - block.domElem.dblclick(); - block.elem('elem').dblclick(); - win.trigger('winevent'); - doc.trigger('docevent'); - spy1.args[0][0].data.should.have.been.equal(data); - spy2.args[0][0].data.should.have.been.equal(data); - spy3.args[0][0].data.should.have.been.equal(data); - spy4.args[0][0].data.should.have.been.equal(data); - spy5.args[0][0].data.should.have.been.equal(data); - }); - - it('should properly bind with aditional event data when use event object', function() { - block.domElem.mousedown(); - block.elem('elem').mousedown(); - win.trigger('winevent2'); - doc.trigger('docevent2'); - spy1.args[0][0].data.should.have.been.equal(data); - spy2.args[0][0].data.should.have.been.equal(data); - spy3.args[0][0].data.should.have.been.equal(data); - spy4.args[0][0].data.should.have.been.equal(data); - spy5.args[0][0].data.should.have.been.equal(data); - }); - }); - - describe('closestElem', function() { - it('should return the closest element', function() { - DOM.decl('block', {}, {}); - - var rootNode = $(BEMHTML.apply({ - block : 'block', - js : true, - content : { - elem : 'elem1', - content : { - elem : 'elem2' - } - } - })), - block = rootNode.bem('block'), - closest = block.closestElem(block.elem('elem2'), 'elem1'); - - closest[0].should.be.equal(block.elem('elem1')[0]); - - DOM.destruct(rootNode); - delete DOM.blocks['block']; - }); - }); - - describe('liveInitOnBlockInsideEvent', function() { - it('should init and call handler on live initialization', function() { - var spyInit = sinon.spy(), - spyHandler = sinon.spy(); - - DOM.decl('block1', { - onSetMod : { - js : { - inited : spyInit - } - } - }, { - live : function() { - this.liveInitOnBlockInsideEvent('event', 'block2', spyHandler); - } - }); - DOM.decl('block2', {}, {}); - - var rootNode = DOM.init($(BEMHTML.apply({ - block : 'block1', - js : true, - content : { - block : 'block2', - js : true - } - }))), - block = rootNode.find('.block2').bem('block2'); - - spyInit.called.should.be.false; - spyHandler.called.should.be.false; - - block.emit('event'); - - spyInit.called.should.be.true; - spyHandler.called.should.be.true; - - DOM.destruct(rootNode); - delete DOM.blocks['block1']; - delete DOM.blocks['block2']; - }); - }); - - describe('modules.define patching', function() { - it('should provide BEMDOM block', function(done) { - var name = 'b' + Math.random(), - spy = sinon.spy(); - - modules.define(name, ['i-bem__dom'], function(provide, BEMDOM) { - spy(); - provide(BEMDOM.decl(this.name, {})); - }); - - modules.define(name, function(provide, Prev) { - spy(); - Prev.should.be.eql(DOM.blocks[this.name]); - provide(Prev.decl(this.name, {})); - }); - - modules.require([name], function(Block) { - spy.should.have.been.calledTwice; - Block.should.be.eql(DOM.blocks[name]); - done(); - }); - }); - }); - - describe('mod change events', function() { - var block; - beforeEach(function() { - block = $(BEMHTML.apply( - { - block : 'block', - content : [ - { elem : 'e1', mods : { 'mod1' : 'val1' } }, - { elem : 'e1', mods : { 'mod1' : 'val1' } }, - { elem : 'e2', mods : { 'mod1' : 'val1' } } - ] - })) - .bem('block'); - }); - - afterEach(function() { - delete DOM.blocks['block']; - }); - - it('should propagate destructing event', function() { - var spy = sinon.spy(); - - DOM.decl('block1', { - onSetMod : { - 'js' : { - 'inited' : function() { - DOM.blocks['block2'].on(this.domElem, { modName : 'js', modVal : '' }, spy); - } - } - } - }); - - DOM.decl('block2'); - - var domElem = $(BEMHTML.apply({ - block : 'block1', - content : { - block : 'block2' - } - })).appendTo('body'); - - DOM.init(domElem); - - var block1 = domElem.bem('block1'), - block2 = block1.findBlockInside('block2'); - - DOM.destruct(block2.domElem); - - spy.should.have.been.called; - - delete DOM.blocks['block2']; - delete DOM.blocks['block1']; - }); - - describe('elems', function() { - it('should emit event on elem mod change with correct arguments', function() { - var spy1 = sinon.spy(), - spy2 = sinon.spy(), - spy3 = sinon.spy(), - spy4 = sinon.spy(), - elem = block.elem('e1'); - - block - .on({ elem : 'e1', modName : 'mod1', modVal : '*' }, spy1) - .on({ elem : 'e1', modName : 'mod1', modVal : 'val2' }, spy2) - .on({ elem : 'e1', modName : 'mod1', modVal : 'val3' }, spy3) - .on({ elem : 'e2', modName : 'mod1', modVal : 'val2' }, spy4) - .setMod(elem, 'mod1', 'val2'); - - spy1.should.have.been.called.twice; - spy2.should.have.been.called.twice; - spy3.should.not.have.been.called; - spy4.should.not.have.been.called; - - var eventData = spy1.args[0][1]; - eventData.modName.should.be.equal('mod1'); - eventData.modVal.should.be.equal('val2'); - eventData.oldModVal.should.be.equal('val1'); - eventData.elem[0].should.be.eql(elem[0]); - spy1.args[1][1].elem[0].should.be.eql(elem[1]); - }); - - it('should emit live event on elem mod change with correct arguments', function() { - var spy1 = sinon.spy(), - spy2 = sinon.spy(), - spy3 = sinon.spy(), - spy4 = sinon.spy(), - elem = block.elem('e1'); - - DOM.blocks['block'] - .on({ elem : 'e1', modName : 'mod1', modVal : '*' }, spy1) - .on({ elem : 'e1', modName : 'mod1', modVal : 'val2' }, spy2) - .on({ elem : 'e1', modName : 'mod1', modVal : 'val3' }, spy3) - .on({ elem : 'e2', modName : 'mod1', modVal : 'val2' }, spy4); - - block.setMod(elem, 'mod1', 'val2'); - - spy1.should.have.been.called.twice; - spy2.should.have.been.called.twice; - spy3.should.not.have.been.called; - spy4.should.not.have.been.called; - - var eventData = spy1.args[0][1]; - eventData.modName.should.be.equal('mod1'); - eventData.modVal.should.be.equal('val2'); - eventData.oldModVal.should.be.equal('val1'); - eventData.elem[0].should.be.eql(elem[0]); - spy1.args[1][1].elem[0].should.be.eql(elem[1]); - }); - }); - }); -}); - -provide(); - -}); diff --git a/common.blocks/i-bem/_elem-instances/i-bem_elem-instances.deps.js b/common.blocks/i-bem/_elem-instances/i-bem_elem-instances.deps.js deleted file mode 100644 index df07fafea..000000000 --- a/common.blocks/i-bem/_elem-instances/i-bem_elem-instances.deps.js +++ /dev/null @@ -1,8 +0,0 @@ -({ - mustDeps : [ - { - block : 'i-bem', - elem : 'internal' - } - ] -}) diff --git a/common.blocks/i-bem/_elem-instances/i-bem_elem-instances.js b/common.blocks/i-bem/_elem-instances/i-bem_elem-instances.js deleted file mode 100644 index 42b0078e5..000000000 --- a/common.blocks/i-bem/_elem-instances/i-bem_elem-instances.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @module i-bem - */ - -modules.define( - 'i-bem', - ['i-bem__internal', 'inherit'], - function(provide, INTERNAL, inherit, BEM) { - -var buildClass = INTERNAL.buildClass; - -/** - * @class BEM - * @augments BEM - * @exports - */ -provide(BEM.decl(null, /** @lends BEM */{ - - /** - * Declares elements and creates an elements class - * @protected - * @param {Object} decl Element description - * @param {String} decl.block Block name - * @param {String} decl.elem Element name - * @param {String} [decl.baseBlock] Name of the parent block - * @param {Array} [decl.baseMix] Mixed block names - * @param {String} [decl.modName] Modifier name - * @param {String|Array} [decl.modVal] Modifier value - * @param {Object} [props] Methods - * @param {Object} [staticProps] Static methods - * @returns {Function} - */ - decl : function(decl, props, staticProps) { - var block; - if(decl.elem) { - typeof decl.block === 'undefined' && (decl.block = this._blockName); - block = this.__base( - { - block : buildClass(decl.block, decl.elem), - baseBlock : decl.baseBlock, - baseMix : decl.baseMix, - modName : decl.modName, - modVal : decl.modVal - }, - props, - staticProps); - block._blockName = decl.block; - block._elemName = decl.elem; - } else { - block = this.__base.apply(this, arguments); - block._elemName || (block._blockName = block._name); - } - return block; - }, - - /** - * Factory method for creating an instance of the element named - * @param {Object} desc Description - * @param {Object} [params] Instance parameters - * @returns {BEM} - */ - create : function(desc, params) { - return desc.elem? - new BEM.blocks[buildClass(desc.block, desc.elem)](desc.mods, params) : - this.__base(desc, params); - }, - - /** - * Returns the name of the current instance - * @protected - * @param {Boolean} [shortName] return the short name of the current instance - * @returns {String} - */ - getName : function(shortName) { - return shortName? (this._elemName || this._blockName) : this._name; - } -})); - -}); diff --git a/common.blocks/i-bem/_elem-instances/i-bem_elem-instances.spec.js b/common.blocks/i-bem/_elem-instances/i-bem_elem-instances.spec.js deleted file mode 100644 index 2b2d548b5..000000000 --- a/common.blocks/i-bem/_elem-instances/i-bem_elem-instances.spec.js +++ /dev/null @@ -1,73 +0,0 @@ -modules.define('spec', ['i-bem', 'sinon'], function(provide, BEM, sinon) { - -describe('i-bem_elem-instances', function() { - describe('decl', function() { - it('should return element', function() { - var block = BEM.decl({ block : 'block', elem : 'elem' }, {}); - block.should.be.equal(BEM.blocks['block__elem']); - delete BEM.blocks['block__elem']; - }); - - it('with mod should apply method only if element has mod', function() { - var baseMethodSpy = sinon.spy(), - modsMethodSpy = sinon.spy(); - - BEM.decl({ block : 'block', elem : 'elem' }, { - method : baseMethodSpy - }); - BEM.decl({ block : 'block', elem : 'elem', modName : 'mod1', modVal : 'val1' }, { - method : modsMethodSpy - }); - - var instance = BEM.create({ block : 'block', elem : 'elem', mods : { 'mod1' : 'val1' } }); - - instance.method(); - baseMethodSpy.called.should.be.false; - modsMethodSpy.called.should.be.true; - - instance.setMod('mod1', 'val2'); - instance.method(); - baseMethodSpy.called.should.be.true; - modsMethodSpy.callCount.should.be.equal(1); - - delete BEM.blocks['block__elem']; - }); - - it('should declare element by block properly', function() { - var Block = BEM.decl('block'), - Elem = Block.decl({ elem : 'elem' }); - - Elem.getName().should.be.equal('block__elem'); - - delete BEM.blocks['block']; - BEM.blocks['block__elem']? - delete BEM.blocks['block__elem'] : - delete BEM.blocks['undefined__elem']; - }); - }); - - describe('create', function() { - it('should return instance of element', function() { - var elem = BEM.decl({ block : 'block', elem : 'elem' }, {}), - instance = BEM.create({ block : 'block', elem : 'elem' }); - - instance.should.be.instanceOf(elem); - delete BEM.blocks['block__elem']; - }); - }); - - describe('getName', function() { - it('should return correct full and short names of element', function() { - var elem = BEM.decl({ block : 'block', elem : 'elem' }, {}); - - elem.getName().should.be.equal('block__elem'); - elem.getName(true).should.be.equal('elem'); - - delete BEM.blocks['block__elem']; - }); - }); -}); - -provide(); - -}); diff --git a/common.blocks/i-bem/i-bem.spec.js b/common.blocks/i-bem/i-bem.spec.js index e941a1fd9..71fcaa160 100644 --- a/common.blocks/i-bem/i-bem.spec.js +++ b/common.blocks/i-bem/i-bem.spec.js @@ -6,89 +6,73 @@ describe('i-bem', function() { delete BEM.blocks['block']; }); - it('should return block', function() { - var block = BEM.decl('block', {}); - block.should.be.equal(BEM.blocks['block']); + it('should enable to declare block', function() { + var Block = BEM.declBlock('block', {}); + + Block.should.be.equal(BEM.blocks['block']); + (new Block()).should.be.instanceOf(BEM.Block); }); - it('should allow inheritance', function() { - var block = BEM.decl('block', {}), - block2 = block.decl('block2', {}); + it('should enable to inherit block', function() { + var Block = BEM.declBlock('block', {}), + Block2 = BEM.declBlock('block2', Block, {}); - (new block2()).should.be.instanceOf(block); - (new block2()).should.be.instanceOf(block2); + (new Block2()).should.be.instanceOf(Block); + (new Block2()).should.be.instanceOf(Block2); delete BEM.blocks['block2']; }); - it('should allow inheritance from itself', function() { - var block = BEM.decl('block', {}), - block2 = block.decl({}); - - block2.should.be.equal(block); - }); - - it('should allow to define only modName and modVal', function() { - var block = BEM.decl('block', {}), - block2 = block.decl({ modName : 'm1', modVal : 'v1' }, {}); + it('should enable to inherit to itself', function() { + var Block = BEM.declBlock('block', {}), + Block2 = BEM.declBlock('block', {}); - block2.should.be.equal(block); - block2.getName().should.be.equal('block'); + Block2.should.be.equal(Block); }); - it('should allow use block class as baseBlock', function() { - var block = BEM.decl('block', {}), - block2 = block.decl({ block : 'block2', baseBlock : block }, {}); - - (new block2()).should.be.instanceOf(block); - (new block2()).should.be.instanceOf(block2); + it('should enable to declare modifier', function() { + var Block = BEM.declBlock('block', {}), + Block2 = Block.declMod({ modName : 'm1', modVal : 'v1' }, {}); - delete BEM.blocks['block2']; + Block2.should.be.equal(Block); }); it('should apply method only if block has mod', function() { var baseMethodSpy = sinon.spy(), - modsMethodSpy = sinon.spy(); - - BEM.decl('block', { - method : baseMethodSpy - }); - BEM.decl({ block : 'block', modName : 'mod1', modVal : 'val1' }, { - method : modsMethodSpy - }); - - var instance = BEM.create({ block : 'block', mods : { 'mod1' : 'val1' } }); + modsMethodSpy = sinon.spy(), + Block = BEM + .declBlock('block', { method : baseMethodSpy }) + .declMod({ modName : 'mod1', modVal : 'val1' }, { method : modsMethodSpy }), + instance = new Block({ mod1 : 'val1' }); instance.method(); - baseMethodSpy.called.should.be.false; - modsMethodSpy.called.should.be.true; + + baseMethodSpy.should.not.have.been.called; + modsMethodSpy.should.have.been.calledOnce; instance.setMod('mod1', 'val2'); instance.method(); - baseMethodSpy.called.should.be.true; - modsMethodSpy.callCount.should.be.equal(1); + + baseMethodSpy.should.have.been.calledOnce; + modsMethodSpy.should.have.been.calledOnce; }); it('should apply method only if block has boolean mod', function() { var baseMethodSpy = sinon.spy(), - modsMethodSpy = sinon.spy(); - - BEM.decl('block', { - method : baseMethodSpy - }); - - BEM.decl({ block : 'block', modName : 'mod1', modVal : true }, { - method : modsMethodSpy - }); - - var instance = BEM.create({ block : 'block', mods : { 'mod1' : true } }); + modsMethodSpy = sinon.spy(), + Block = BEM + .declBlock('block', { method : baseMethodSpy }) + .declMod({ modName : 'mod1', modVal : true }, { method : modsMethodSpy }), + instance = new Block({ mod1 : true }); instance.method(); + baseMethodSpy.should.not.have.been.called; modsMethodSpy.should.have.been.calledOnce; instance.delMod('mod1'); instance.method(); + baseMethodSpy.should.have.been.calledOnce; modsMethodSpy.should.have.been.calledOnce; }); @@ -96,10 +80,10 @@ describe('i-bem', function() { describe('create', function() { it('should return instance of block', function() { - var block = BEM.decl('block', {}), - instance = BEM.create('block'); + var Block = BEM.declBlock('block', {}), + instance = Block.create(); - instance.should.be.instanceOf(block); + instance.should.be.instanceOf(Block); delete BEM.blocks['block']; }); }); @@ -107,8 +91,9 @@ describe('i-bem', function() { describe('mods', function() { var block; beforeEach(function() { - BEM.decl('block', {}); - block = BEM.create({ block : 'block', mods : { mod1 : 'val1', mod2 : true, mod3 : false } }); + block = BEM + .declBlock('block', {}) + .create({ mods : { mod1 : 'val1', mod2 : true, mod3 : false } }); }); afterEach(function() { delete BEM.blocks['block']; @@ -140,15 +125,18 @@ describe('i-bem', function() { it('should update boolean mod value', function() { block .setMod('mod1', true) - .getMod('mod1').should.be.true; + .getMod('mod1') + .should.be.true; block .setMod('mod1', false) - .getMod('mod1').should.be.equal(''); + .getMod('mod1') + .should.be.equal(''); block .setMod('mod1') - .getMod('mod1').should.be.true; + .getMod('mod1') + .should.be.true; }); }); @@ -272,7 +260,7 @@ describe('i-bem', function() { spyMod2Val1 = sinon.spy(), spyMod2Val2 = sinon.spy(); - BEM.decl('block', { + BEM.declBlock('block', { beforeSetMod : { 'mod1' : { 'val1' : function() { @@ -282,7 +270,7 @@ describe('i-bem', function() { } }); - BEM.decl('block', { + BEM.declBlock('block', { beforeSetMod : { 'mod1' : function() { order.push(3); @@ -294,14 +282,14 @@ describe('i-bem', function() { } }); - BEM.decl('block', { + BEM.declBlock('block', { beforeSetMod : function(modName) { this.__base.apply(this, arguments); modName === 'mod1' && order.push(2); } }); - BEM.decl('block', { + BEM.declBlock('block', { beforeSetMod : { 'mod1' : { '*' : function() { @@ -321,7 +309,7 @@ describe('i-bem', function() { } }); - var block = BEM.create({ block : 'block', mods : { mod1 : 'val0', mod2 : 'val0' } }); + var block = BEM.blocks['block'].create({ mods : { mod1 : 'val0', mod2 : 'val0' } }); block.setMod('mod1', 'val1'); order.should.be.eql([1, 2, 3, 4, 5, 6]); @@ -331,46 +319,51 @@ describe('i-bem', function() { }); it('should call callbacks before set mod', function(done) { - BEM.decl('block', { - beforeSetMod : { - 'mod1' : { - 'val1' : function() { - this.hasMod('mod1', 'val1').should.be.false; - done(); + BEM + .declBlock('block', { + beforeSetMod : { + 'mod1' : { + 'val1' : function() { + this.hasMod('mod1', 'val1').should.be.false; + done(); + } } - } - } - }); - var block = BEM.create({ block : 'block', mods : { mod1 : 'val0' } }); - block.setMod('mod1', 'val1'); + } + }) + .create({ mods : { mod1 : 'val0' } }) + .setMod('mod1', 'val1'); }); it('should set mod after callbacks', function() { - BEM.decl('block', { - beforeSetMod : { - 'mod1' : { - 'val1' : function() {} - } - } - }); - var block = BEM.create({ block : 'block', mods : { mod1 : 'val0' } }); - block.setMod('mod1', 'val1'); - block.hasMod('mod1', 'val1').should.be.true; + BEM + .declBlock('block', { + beforeSetMod : { + 'mod1' : { + 'val1' : function() {} + } + } + }) + .create({ mods : { mod1 : 'val0' } }) + .setMod('mod1', 'val1') + .hasMod('mod1', 'val1') + .should.be.true; }); it('shouldn\'t set mod when callback returns false', function() { - BEM.decl('block', { - beforeSetMod : { - 'mod1' : { - 'val1' : function() { - return false; + BEM + .declBlock('block', { + beforeSetMod : { + 'mod1' : { + 'val1' : function() { + return false; + } } - } - } - }); - var block = BEM.create({ block : 'block', mods : { mod1 : 'val0' } }); - block.setMod('mod1', 'val1'); - block.hasMod('mod1', 'val1').should.be.false; + } + }) + .create({ mods : { mod1 : 'val0' } }) + .setMod('mod1', 'val1') + .hasMod('mod1', 'val1') + .should.be.false; }); }); @@ -385,7 +378,7 @@ describe('i-bem', function() { spyMod2Val1 = sinon.spy(), spyMod2Val2 = sinon.spy(); - BEM.decl('block', { + BEM.declBlock('block', { onSetMod : { 'mod1' : { 'val1' : function() { @@ -395,7 +388,7 @@ describe('i-bem', function() { } }); - BEM.decl('block', { + BEM.declBlock('block', { onSetMod : { 'mod1' : function() { order.push(3); @@ -407,14 +400,14 @@ describe('i-bem', function() { } }); - BEM.decl('block', { + BEM.declBlock('block', { onSetMod : function(modName) { this.__base.apply(this, arguments); modName === 'mod1' && order.push(2); } }); - BEM.decl('block', { + BEM.declBlock('block', { onSetMod : { 'mod1' : { '*' : function() { @@ -434,8 +427,9 @@ describe('i-bem', function() { } }); - var block = BEM.create({ block : 'block', mods : { mod1 : 'val0', mod2 : 'val0' } }); - block.setMod('mod1', 'val1'); + BEM.blocks['block'] + .create({ mods : { mod1 : 'val0', mod2 : 'val0' } }) + .setMod('mod1', 'val1'); order.should.be.eql([1, 2, 3, 4, 5, 6]); spyMod1Val2.should.not.have.been.called; @@ -444,39 +438,42 @@ describe('i-bem', function() { }); it('should call callbacks after set mod', function(done) { - BEM.decl('block', { - onSetMod : { - 'mod1' : { - 'val1' : function() { - this.hasMod('mod1', 'val1').should.be.true; - done(); + BEM + .declBlock('block', { + onSetMod : { + 'mod1' : { + 'val1' : function() { + this.hasMod('mod1', 'val1').should.be.true; + done(); + } } - } - } - }); - var block = BEM.create({ block : 'block', mods : { mod1 : 'val0' } }); - block.setMod('mod1', 'val1'); + } + }) + .create({ mods : { mod1 : 'val0' } }) + .setMod('mod1', 'val1'); }); it('shouldn\'t call callbacks if beforeSetMod cancel set mod', function() { var spy = sinon.spy(); - BEM.decl('block', { - beforeSetMod : { - 'mod1' : { - 'val1' : function() { - return false; + BEM + .declBlock('block', { + beforeSetMod : { + 'mod1' : { + 'val1' : function() { + return false; + } } - } - }, + }, + + onSetMod : { + 'mod1' : { + 'val1' : spy + } + } + }) + .create({ mods : { mod1 : 'val0' } }) + .setMod('mod1', 'val1'); - onSetMod : { - 'mod1' : { - 'val1' : spy - } - } - }); - var block = BEM.create({ block : 'block', mods : { mod1 : 'val0' } }); - block.setMod('mod1', 'val1'); spy.should.not.have.been.called; }); }); @@ -488,7 +485,7 @@ describe('i-bem', function() { spyMod2ValFalse = sinon.spy(), spyMod2Val2 = sinon.spy(); - BEM.decl('block', { + BEM.declBlock('block', { beforeSetMod : { 'mod1' : { 'true' : function(modName, modVal, oldModVal) { @@ -508,7 +505,7 @@ describe('i-bem', function() { } }); - BEM.decl('block', { + BEM.declBlock('block', { beforeSetMod : { 'mod1' : function() { order.push(3); @@ -530,7 +527,7 @@ describe('i-bem', function() { } }); - BEM.decl('block', { + BEM.declBlock('block', { beforeSetMod : function(modName) { this.__base.apply(this, arguments); modName === 'mod1' && order.push(2); @@ -542,7 +539,7 @@ describe('i-bem', function() { } }); - BEM.decl('block', { + BEM.declBlock('block', { beforeSetMod : { 'mod1' : { '*' : function(modName, modVal, oldModVal) { @@ -595,7 +592,7 @@ describe('i-bem', function() { } }); - var block = BEM.create({ block : 'block', mods : { mod1 : false, mod2 : true } }); + var block = BEM.blocks['block'].create({ mods : { mod1 : false, mod2 : true } }); block.setMod('mod1', true); spyMod1Val2.should.not.have.been.called; @@ -612,8 +609,9 @@ describe('i-bem', function() { describe('nextTick', function() { var block; beforeEach(function() { - BEM.decl('block', {}); - block = BEM.create({ block : 'block', mods : { mod1 : 'val1' } }); + block = BEM + .declBlock('block', {}) + .create({ mods : { mod1 : 'val1' } }); }); afterEach(function() { delete BEM.blocks['block']; @@ -649,28 +647,29 @@ describe('i-bem', function() { describe('mod change events', function() { var block; beforeEach(function() { - BEM.decl('block', { - beforeSetMod : { - 'mod3' : function() { - return false; - } - }, - - onSetMod : { - 'js' : { - 'inited' : function() { - this.prop = 'val1'; + block = BEM + .declBlock('block', { + beforeSetMod : { + 'mod3' : function() { + return false; } }, - 'mod1' : { - 'val2' : function() { - this.prop = 'val2'; + onSetMod : { + 'js' : { + 'inited' : function() { + this.prop = 'val1'; + } + }, + + 'mod1' : { + 'val2' : function() { + this.prop = 'val2'; + } } } - } - }); - block = BEM.create({ block : 'block', mods : { mod1 : 'val1', mod2 : true } }); + }) + .create({ mods : { mod1 : 'val1', mod2 : true } }); }); afterEach(function() { delete BEM.blocks['block']; diff --git a/common.blocks/i-bem/i-bem.vanilla.js b/common.blocks/i-bem/i-bem.vanilla.js index d24090c33..3ea99399c 100644 --- a/common.blocks/i-bem/i-bem.vanilla.js +++ b/common.blocks/i-bem/i-bem.vanilla.js @@ -71,16 +71,12 @@ function modFnsToProps(prefix, modFns, props, elemName) { } else { var modName, modVal, modFn; for(modName in modFns) { - if(modFns.hasOwnProperty(modName)) { - modFn = modFns[modName]; - if(functions.isFunction(modFn)) { - props[buildModFnName(prefix, modName, '*', elemName)] = modFn; - } else { - for(modVal in modFn) { - if(modFn.hasOwnProperty(modVal)) { - props[buildModFnName(prefix, modName, modVal, elemName)] = modFn[modVal]; - } - } + modFn = modFns[modName]; + if(functions.isFunction(modFn)) { + props[buildModFnName(prefix, modName, '*', elemName)] = modFn; + } else { + for(modVal in modFn) { + props[buildModFnName(prefix, modName, modVal, elemName)] = modFn[modVal]; } } } @@ -119,30 +115,25 @@ function convertModHandlersToMethods(props) { var elemName; if(props.beforeElemSetMod) { for(elemName in props.beforeElemSetMod) { - if(props.beforeElemSetMod.hasOwnProperty(elemName)) { - modFnsToProps('before', props.beforeElemSetMod[elemName], props, elemName); - } + modFnsToProps('before', props.beforeElemSetMod[elemName], props, elemName); } delete props.beforeElemSetMod; } if(props.onElemSetMod) { for(elemName in props.onElemSetMod) { - if(props.onElemSetMod.hasOwnProperty(elemName)) { - modFnsToProps('after', props.onElemSetMod[elemName], props, elemName); - } + modFnsToProps('after', props.onElemSetMod[elemName], props, elemName); } delete props.onElemSetMod; } } /** - * @class BEM + * @class Block * @description Base block for creating BEM blocks * @augments events:Emitter - * @exports */ -var BEM = inherit(events.Emitter, /** @lends BEM.prototype */ { +var Block = inherit(events.Emitter, /** @lends Block.prototype */ { /** * @constructor * @private @@ -191,7 +182,7 @@ var BEM = inherit(events.Emitter, /** @lends BEM.prototype */ { * @param {Object} [data] Additional data that the handler gets as e.data * @param {Function} fn Handler * @param {Object} [ctx] Handler context - * @returns {BEM} this + * @returns {Block} this */ on : function(e, data, fn, ctx) { if(typeof e === 'object' && (functions.isFunction(data) || functions.isFunction(fn))) { // mod change event @@ -206,7 +197,7 @@ var BEM = inherit(events.Emitter, /** @lends BEM.prototype */ { * @param {String|Object} [e] Event type * @param {Function} [fn] Handler * @param {Object} [ctx] Handler context - * @returns {BEM} this + * @returns {Block} this */ un : function(e, fn, ctx) { if(typeof e === 'object' && functions.isFunction(fn)) { // mod change event @@ -221,7 +212,7 @@ var BEM = inherit(events.Emitter, /** @lends BEM.prototype */ { * @protected * @param {String} e Event name * @param {Object} [data] Additional information - * @returns {BEM} this + * @returns {Block} this */ emit : function(e, data) { var isModJsEvent = false; @@ -345,7 +336,7 @@ var BEM = inherit(events.Emitter, /** @lends BEM.prototype */ { * @param {Object} [elem] Nested element * @param {String} modName Modifier name * @param {String} modVal Modifier value - * @returns {BEM} this + * @returns {Block} this */ setMod : function(elem, modName, modVal) { if(typeof modVal === 'undefined') { @@ -439,7 +430,7 @@ var BEM = inherit(events.Emitter, /** @lends BEM.prototype */ { * @param {String} modVal1 First modifier value * @param {String} [modVal2] Second modifier value * @param {Boolean} [condition] Condition - * @returns {BEM} this + * @returns {Block} this */ toggleMod : function(elem, modName, modVal1, modVal2, condition) { if(typeof elem === 'string') { // if this is a block @@ -478,7 +469,7 @@ var BEM = inherit(events.Emitter, /** @lends BEM.prototype */ { * @protected * @param {Object} [elem] Nested element * @param {String} modName Modifier name - * @returns {BEM} this + * @returns {Block} this */ delMod : function(elem, modName) { if(!modName) { @@ -548,7 +539,7 @@ var BEM = inherit(events.Emitter, /** @lends BEM.prototype */ { * Executes given callback on next turn eventloop in block's context * @protected * @param {Function} fn callback - * @returns {BEM} this + * @returns {Block} this */ nextTick : function(fn) { var _this = this; @@ -557,104 +548,55 @@ var BEM = inherit(events.Emitter, /** @lends BEM.prototype */ { }); return this; } -}, /** @lends BEM */{ - - _name : 'i-bem', - +}, /** @lends Block */{ /** - * Storage for block declarations (hash by block name) - * @type Object + * Factory method for creating an instance + * @param {Object} [desc] + * @param {Object} [desc.mods] modifiers + * @param {Object} [desc.params] params + * @returns {Block} */ - blocks : blocks, + create : function(desc) { + desc || (desc = {}); + return new this(desc.mods, desc.params); + }, /** - * Declares blocks and creates a block class - * @param {String|Object} decl Block name (simple syntax) or description - * @param {String} decl.block|decl.name Block name - * @param {String} [decl.baseBlock] Name of the parent block - * @param {Array} [decl.baseMix] Mixed block names - * @param {String} [decl.modName] Modifier name - * @param {String|Array} [decl.modVal] Modifier value - * @param {Object} [props] Methods - * @param {Object} [staticProps] Static methods + * Declares modifier + * @param {Object} mod + * @param {String} mod.modName + * @param {String|Boolean|Array} [mod.modVal] + * @param {Object} props + * @param {Object} [staticProps] * @returns {Function} */ - decl : function(decl, props, staticProps) { - // string as block - typeof decl === 'string' && (decl = { block : decl }); - // inherit from itself - if(arguments.length <= 2 && - typeof decl === 'object' && - (!decl || (typeof decl.block !== 'string' && typeof decl.modName !== 'string'))) { - staticProps = props; - props = decl; - decl = {}; - } - typeof decl.block === 'undefined' && (decl.block = this.getName()); - - var baseBlock; - if(typeof decl.baseBlock === 'undefined') { - baseBlock = blocks[decl.block] || this; - } else if(typeof decl.baseBlock === 'string') { - baseBlock = blocks[decl.baseBlock]; - if(!baseBlock) - throw('baseBlock "' + decl.baseBlock + '" for "' + decl.block + '" is undefined'); - } else { - baseBlock = decl.baseBlock; - } - - convertModHandlersToMethods(props || (props = {})); - - if(decl.modName) { - var checkMod = buildCheckMod(decl.modName, decl.modVal); - objects.each(props, function(prop, name) { - functions.isFunction(prop) && - (props[name] = function() { - var method; - if(checkMod(this)) { - method = prop; - } else { - var baseMethod = baseBlock.prototype[name]; - baseMethod && baseMethod !== prop && - (method = this.__base); - } - return method? - method.apply(this, arguments) : - undef; - }); - }); - } - - if(staticProps && typeof staticProps.live === 'boolean') { - var live = staticProps.live; - staticProps.live = function() { - return live; - }; - } - - var block, baseBlocks = baseBlock; - if(decl.baseMix) { - baseBlocks = [baseBlocks]; - decl.baseMix.forEach(function(mixedBlock) { - if(!blocks[mixedBlock]) { - throw('mix block "' + mixedBlock + '" for "' + decl.block + '" is undefined'); - } - baseBlocks.push(blocks[mixedBlock]); - }); - } - - decl.block === baseBlock.getName()? - // makes a new "live" if the old one was already executed - (block = inherit.self(baseBlocks, props, staticProps))._processLive(true) : - (block = blocks[decl.block] = inherit(baseBlocks, props, staticProps))._name = decl.block; + declMod : function(mod, props, staticProps) { + props && convertModHandlersToMethods(props); + + var checkMod = buildCheckMod(mod.modName, mod.modVal), + basePtp = this.prototype; + + objects.each(props, function(prop, name) { + functions.isFunction(prop) && + (props[name] = function() { + var method; + if(checkMod(this)) { + method = prop; + } else { + var baseMethod = basePtp[name]; + baseMethod && baseMethod !== prop && + (method = this.__base); + } + return method? + method.apply(this, arguments) : + undef; + }); + }); - return block; + return inherit.self(this, props, staticProps); }, - declMix : function(block, props, staticProps) { - convertModHandlersToMethods(props || (props = {})); - return blocks[block] = inherit(props, staticProps); - }, + _name : 'i-bem', /** * Processes a block's live properties @@ -666,18 +608,6 @@ var BEM = inherit(events.Emitter, /** @lends BEM.prototype */ { return false; }, - /** - * Factory method for creating an instance of the block named - * @param {String|Object} block Block name or description - * @param {Object} [params] Block parameters - * @returns {BEM} - */ - create : function(block, params) { - typeof block === 'string' && (block = { block : block }); - - return new blocks[block.block](block.mods, params); - }, - /** * Returns the name of the current block * @returns {String} @@ -729,7 +659,63 @@ var BEM = inherit(events.Emitter, /** @lends BEM.prototype */ { * @param {Object} elem Nested element * @returns {String|undefined} */ - _extractElemNameFrom : function(elem) {}, + _extractElemNameFrom : function(elem) {} +}); + +provide(/** @exports */{ + /** + * Base BEM block + * @type Function + */ + Block : Block, + + /** + * Storage for block declarations (hash by block name) + * @type Object + */ + blocks : blocks, + + /** + * Declares block and creates a block class + * @param {String} blockName Block name + * @param {Function|Array[Function]} [baseBlocks] base block + mixes + * @param {Object} [props] Methods + * @param {Object} [staticProps] Static methods + * @returns {Function} Block class + */ + declBlock : function(blockName, baseBlocks, props, staticProps) { + if(!baseBlocks || (typeof baseBlocks === 'object' && !Array.isArray(baseBlocks))) { + staticProps === props; + props = baseBlocks; + baseBlocks = blocks[blockName] || Block; + } + + props && convertModHandlersToMethods(props); + + if(staticProps && typeof staticProps.live === 'boolean') { + var live = staticProps.live; + staticProps.live = function() { + return live; + }; + } + + var BaseBlock = Array.isArray(baseBlocks)? + baseBlocks[0] : + baseBlocks, + blockCls; + + blockName === BaseBlock.getName()? + // makes a new "live" if the old one was already executed + (blockCls = inherit.self(baseBlocks, props, staticProps))._processLive(true) : + (blockCls = blocks[blockName] = inherit(baseBlocks, props, staticProps))._name = blockName; + + return blockCls; + }, + + declMix : function(blockName, props, staticProps) { + convertModHandlersToMethods(props || (props = {})); + return blocks[blockName] = inherit(props, staticProps); + }, /** * Executes the block init functions @@ -749,6 +735,4 @@ var BEM = inherit(events.Emitter, /** @lends BEM.prototype */ { } }); -provide(BEM); - });