diff --git a/common.blocks/i-bem-dom/i-bem-dom.js b/common.blocks/i-bem-dom/i-bem-dom.js index c1fa07e9e..1a5effbf7 100644 --- a/common.blocks/i-bem-dom/i-bem-dom.js +++ b/common.blocks/i-bem-dom/i-bem-dom.js @@ -153,7 +153,7 @@ function initEntity(entityName, domElem, params, ignoreLazyInit, callback) { entityCls._processInit(); - if(ignoreLazyInit || params.lazyInit === false || !entityCls.lazyInit && !params.lazyInit) { + if(ignoreLazyInit || params.lazyInit === false || !entityCls._checkLazyInit(domElem[0]) && !params.lazyInit) { ignoreLazyInit && domElem.addClass(BEM_CLASS_NAME); // add css class for preventing memory leaks in further destructing entity = new entityCls(uniqIdToDomElems[uniqId], params, !!ignoreLazyInit); @@ -341,6 +341,38 @@ function getEntityBase(baseCls, entityName, base) { return base; } +/** + * Extract lazyInit property from static props + * @param {Object} [staticProps] + * @returns {?Boolean} + */ +function extractLazyInitProp(staticProps) { + if(staticProps && staticProps.lazyInit !== undef) { + var lazyInit = staticProps.lazyInit; + delete staticProps.lazyInit; + return lazyInit; + } + + return null; +} + +/** + * Processing lazyInit rules for entity + * @param {Function} entity BemDomEntity + * @param {Object} mod mod declaration + * @param {Boolean} lazyInit lazyInit behavior + * @returns {?Boolean} + */ +function processLazyInitRule(entity, mod, lazyInit) { + var rules = entity._lazyInitRules; + + rules.push({ + check : entity._buildModValRE(mod.modName, mod.modVal), + modName : mod.modName, + lazyInit : lazyInit + }); +} + /** * @class BemDomEntity * @description Base mix for BEM entities that have DOM representation @@ -770,11 +802,12 @@ var BemDomEntity = inherit(/** @lends BemDomEntity.prototype */{ /** @override */ declMod : function(mod, props, staticProps) { - if(staticProps && staticProps.lazyInit !== undef) { - throw Error('declMod with lazyInit prop not allowed. Your need use \'lazyInit\' in data-bem params'); - } + var lazyInit = extractLazyInitProp(staticProps), + entity = this.__base.apply(this, arguments); + + lazyInit !== null && processLazyInitRule(entity, mod, lazyInit); - return this.__base.apply(this, arguments); + return entity; }, /** @override */ @@ -831,13 +864,16 @@ var BemDomEntity = inherit(/** @lends BemDomEntity.prototype */{ * Builds a regular expression for extracting modifier values from a DOM element of an entity * @private * @param {String} modName Modifier name + * @param {String} [modVal] Modifier value * @returns {RegExp} */ - _buildModValRE : function(modName) { + _buildModValRE : function(modName, modVal) { + modVal = (modVal === '*' || modVal === undef)? NAME_PATTERN : modVal; + return new RegExp( '(\\s|^)' + this._buildModClassNamePrefix(modName) + - '(?:' + MOD_DELIM + '(' + NAME_PATTERN + '))?(?=\\s|$)'); + '(?:' + MOD_DELIM + '(' + modVal + '))?(?=\\s|$)'); }, /** @@ -860,6 +896,33 @@ var BemDomEntity = inherit(/** @lends BemDomEntity.prototype */{ */ _buildSelector : function(modName, modVal) { return '.' + this._buildClassName(modName, modVal); + }, + + /** + * Check domNode for lazy initialization entity + * @protected + * @param {HTMLElement} domNode + * @returns {Boolean|undefined} + */ + _checkLazyInit : function(domNode) { + var rules = this._lazyInitRules, + len = rules.length, + rule, + lazyInit, + modName; + + while(rule = rules[--len]) { + if(modName !== rule.modName && rule.check.test(domNode.className)) { + if(lazyInit === undef || lazyInit === true) { + modName = rule.modName; + lazyInit = rule.lazyInit; + } + + if(lazyInit === false) return lazyInit; + } + } + + return lazyInit !== undef? lazyInit : this.lazyInit; } }); @@ -874,6 +937,8 @@ var Block = inherit([bem.Block, BemDomEntity], /** @lends Block.prototype */{ _block : function() { return this; } +}, { + _lazyInitRules : [] }); /** @@ -887,6 +952,8 @@ var Elem = inherit([bem.Elem, BemDomEntity], /** @lends Elem.prototype */{ _block : function() { return this._blockInstance || (this._blockInstance = this.findParentBlock(getEntityCls(this.__self._blockName))); } +}, { + _lazyInitRules : [] }); /** diff --git a/common.blocks/i-bem-dom/i-bem-dom.spec.js b/common.blocks/i-bem-dom/i-bem-dom.spec.js index 1b90c4b5a..30944d71f 100644 --- a/common.blocks/i-bem-dom/i-bem-dom.spec.js +++ b/common.blocks/i-bem-dom/i-bem-dom.spec.js @@ -164,16 +164,6 @@ describe('i-bem-dom', function() { block2.should.be.instanceOf(Block1); elem2.should.be.instanceOf(Elem1); }); - - it('should throw error if declMod contains lazyInit static property', function() { - var Block = bemDom.declBlock('block'); - - function mod() { - Block.declMod({ modName : 'mod' }, null, { lazyInit : true }); - } - - mod.should.throw(Error, 'declMod with lazyInit prop not allowed. Your need use \'lazyInit\' in data-bem params'); - }); }); describe('getMod', function() { @@ -1675,6 +1665,91 @@ describe('i-bem-dom', function() { describe('lazy init', function() { var spy; + ['block', 'elem'].forEach(function(entityType) { + it('should have different lazyInit for base ' + entityType + ' and modifiers', function() { + var spy1 = sinon.spy(), + spy2 = sinon.spy(), + spy3 = sinon.spy(), + spy4 = sinon.spy(), + spy5 = sinon.spy(), + + Entity = entityType === 'block'? bemDom.declBlock('block', { + onSetMod : { js : { inited : spy1 } } + }, { + lazyInit : true + }) : bemDom.declElem('block', 'elem', { + onSetMod : { js : { inited : spy1 } } + }, { + lazyInit : true + }); + + Entity.declMod({ modName : 'm1' }, { + onSetMod : { js : { inited : spy2 } } + }, { + lazyInit : false + }); + + Entity.declMod({ modName : 'm2', modVal : true }, { + onSetMod : { js : { inited : spy3 } } + }); + + Entity.declMod({ modName : 'm3', modVal : '*' }, { + onSetMod : { js : { inited : spy4 } } + }, { + lazyInit : false + }); + + Entity.declMod({ modName : 'm3', modVal : 'v2' }, { + onSetMod : { js : { inited : spy5 } } + }, { + lazyInit : true + }); + + rootNode = initDom([ + { }, + { m1 : true }, + { m2 : true }, + { m3 : 'v1' }, + { m3 : 'v2' } + ].map(function(mods) { + var bemjson = entityType === 'block'? { mods : mods } + : { elem : 'elem', elemMods : mods }; + + bemjson.block = 'block'; + bemjson.js = true; + + return bemjson; + })); + + spy1.should.have.not.been.called; + spy2.should.have.been.called; + spy3.should.have.not.been.called; + spy4.should.have.been.called; + spy5.should.have.not.been.called; + }); + }); + + it('should use force init priority on domNode between multiple mods', function() { + spy = sinon.spy(); + + var Block = bemDom.declBlock('block', { + onSetMod : { js : { inited : spy } } + }, { + lazyInit : false + }); + + Block.declMod({ modName : 'm1' }, null, { lazyInit : false }); + Block.declMod({ modName : 'm2' }, null, { lazyInit : true }); + + rootNode = initDom([ + { block : 'block', mods : { m1 : true, m2 : true }, js : true }, + { block : 'block', mods : { m1 : true }, js : true }, + { block : 'block', mods : { m2 : true }, js : true }, + ]); + + spy.should.have.calledTwice; + }); + it('should be possible to force initialization', function() { spy = sinon.spy();