diff --git a/common.blocks/i-bem-dom/i-bem-dom.js b/common.blocks/i-bem-dom/i-bem-dom.js index c1fa07e9e..71834fca8 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._lazyInitCheck(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,42 @@ function getEntityBase(baseCls, entityName, base) { return base; } +/** + * Extract lazyInit property from staticProps + * @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) { + if(arguments.length < 3) { + lazyInit = mod; + mod = undef; + } + + var rules = entity._lazyInitRules || (entity._lazyInitRules = []); + + rules.push({ + check : mod? entity._buildModValRE(mod.modName, mod.modVal) : entity._buildRE(), + lazyInit : lazyInit + }); +} + /** * @class BemDomEntity * @description Base mix for BEM entities that have DOM representation @@ -770,11 +806,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); - return this.__base.apply(this, arguments); + lazyInit !== null && processLazyInitRule(entity, mod, lazyInit); + + return entity; }, /** @override */ @@ -827,17 +864,29 @@ var BemDomEntity = inherit(/** @lends BemDomEntity.prototype */{ return this.getEntityName() + MOD_DELIM + modName; }, + /** + * Builds a regular expression for check entity on DOM element + * @private + * @returns {RegExp} + */ + _buildRE : function() { + return new RegExp('(\\s|^)' + this.getEntityName() + '?(?=\\s|$)'); + }, + /** * 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 +909,23 @@ var BemDomEntity = inherit(/** @lends BemDomEntity.prototype */{ */ _buildSelector : function(modName, modVal) { return '.' + this._buildClassName(modName, modVal); + }, + + /** + * Check domNode for lazy initialization entity + * @protected + * @returns {?Boolean} + */ + _lazyInitCheck : function(domNode) { + var rules = this._lazyInitRules, rule; + if(!rules) return null; + + var len = rules.length; + while(rule = rules[--len]) { + if(rule.check.test(domNode.className)) return rule.lazyInit; + } + + return null; } }); @@ -960,7 +1026,12 @@ bemDom = /** @exports */{ base = getEntityBase(Block, blockName, base); - return bem.declBlock(blockName, base, props, staticProps); + var lazyInit = extractLazyInitProp(staticProps), + entity = bem.declBlock(blockName, base, props, staticProps); + + lazyInit !== null && processLazyInitRule(entity, lazyInit); + + return entity; }, /** @@ -983,7 +1054,12 @@ bemDom = /** @exports */{ base = getEntityBase(Elem, entityName, base); - return bem.declElem(blockName, elemName, base, props, staticProps); + var lazyInit = extractLazyInitProp(staticProps), + entity = bem.declElem(blockName, elemName, base, props, staticProps); + + lazyInit !== null && processLazyInitRule(entity, lazyInit); + + return entity; }, declMixin : bem.declMixin, 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..3c6ce6241 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,70 @@ 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 be possible to force initialization', function() { spy = sinon.spy();