Skip to content

Commit f711cfd

Browse files
committed
Safe lazyInit decls
1 parent 9ab08cc commit f711cfd

File tree

2 files changed

+149
-19
lines changed

2 files changed

+149
-19
lines changed

common.blocks/i-bem-dom/i-bem-dom.js

+85-9
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ function initEntity(entityName, domElem, params, ignoreLazyInit, callback) {
153153

154154
entityCls._processInit();
155155

156-
if(ignoreLazyInit || params.lazyInit === false || !entityCls.lazyInit && !params.lazyInit) {
156+
if(ignoreLazyInit || params.lazyInit === false || !entityCls._lazyInitCheck(domElem[0]) && !params.lazyInit) {
157157
ignoreLazyInit && domElem.addClass(BEM_CLASS_NAME); // add css class for preventing memory leaks in further destructing
158158

159159
entity = new entityCls(uniqIdToDomElems[uniqId], params, !!ignoreLazyInit);
@@ -341,6 +341,42 @@ function getEntityBase(baseCls, entityName, base) {
341341
return base;
342342
}
343343

344+
/**
345+
* Extract lazyInit property from staticProps
346+
* @param {Object} [staticProps]
347+
* @returns {?Boolean}
348+
*/
349+
function extractLazyInitProp(staticProps) {
350+
if(staticProps && staticProps.lazyInit !== undef) {
351+
var lazyInit = staticProps.lazyInit;
352+
delete staticProps.lazyInit;
353+
return lazyInit;
354+
}
355+
356+
return null;
357+
}
358+
359+
/**
360+
* Processing lazyInit rules for entity
361+
* @param {Function} entity BemDomEntity
362+
* @param {Object} [mod] mod declaration
363+
* @param {Boolean} lazyInit lazyInit behavior
364+
* @returns {?Boolean}
365+
*/
366+
function processLazyInitRule(entity, mod, lazyInit) {
367+
if(arguments.length < 3) {
368+
lazyInit = mod;
369+
mod = undef;
370+
}
371+
372+
var rules = entity._lazyInitRules || (entity._lazyInitRules = []);
373+
374+
rules.push({
375+
check : mod? entity._buildModValRE(mod.modName, mod.modVal) : entity._buildRE(),
376+
lazyInit : lazyInit
377+
});
378+
}
379+
344380
/**
345381
* @class BemDomEntity
346382
* @description Base mix for BEM entities that have DOM representation
@@ -770,11 +806,12 @@ var BemDomEntity = inherit(/** @lends BemDomEntity.prototype */{
770806

771807
/** @override */
772808
declMod : function(mod, props, staticProps) {
773-
if(staticProps && staticProps.lazyInit !== undef) {
774-
throw Error('declMod with lazyInit prop not allowed. Your need use \'lazyInit\' in data-bem params');
775-
}
809+
var lazyInit = extractLazyInitProp(staticProps),
810+
entity = this.__base.apply(this, arguments);
776811

777-
return this.__base.apply(this, arguments);
812+
lazyInit !== null && processLazyInitRule(entity, mod, lazyInit);
813+
814+
return entity;
778815
},
779816

780817
/** @override */
@@ -827,17 +864,29 @@ var BemDomEntity = inherit(/** @lends BemDomEntity.prototype */{
827864
return this.getEntityName() + MOD_DELIM + modName;
828865
},
829866

867+
/**
868+
* Builds a regular expression for check entity on DOM element
869+
* @private
870+
* @returns {RegExp}
871+
*/
872+
_buildRE : function() {
873+
return new RegExp('(\\s|^)' + this.getEntityName() + '?(?=\\s|$)');
874+
},
875+
830876
/**
831877
* Builds a regular expression for extracting modifier values from a DOM element of an entity
832878
* @private
833879
* @param {String} modName Modifier name
880+
* @param {String} [modVal] Modifier value
834881
* @returns {RegExp}
835882
*/
836-
_buildModValRE : function(modName) {
883+
_buildModValRE : function(modName, modVal) {
884+
modVal = (modVal === '*' || modVal === undef)? NAME_PATTERN : modVal;
885+
837886
return new RegExp(
838887
'(\\s|^)' +
839888
this._buildModClassNamePrefix(modName) +
840-
'(?:' + MOD_DELIM + '(' + NAME_PATTERN + '))?(?=\\s|$)');
889+
'(?:' + MOD_DELIM + '(' + modVal + '))?(?=\\s|$)');
841890
},
842891

843892
/**
@@ -860,6 +909,23 @@ var BemDomEntity = inherit(/** @lends BemDomEntity.prototype */{
860909
*/
861910
_buildSelector : function(modName, modVal) {
862911
return '.' + this._buildClassName(modName, modVal);
912+
},
913+
914+
/**
915+
* Check domNode for lazy initialization entity
916+
* @protected
917+
* @returns {?Boolean}
918+
*/
919+
_lazyInitCheck : function(domNode) {
920+
var rules = this._lazyInitRules, rule;
921+
if(!rules) return null;
922+
923+
var len = rules.length;
924+
while(rule = rules[--len]) {
925+
if(rule.check.test(domNode.className)) return rule.lazyInit;
926+
}
927+
928+
return null;
863929
}
864930
});
865931

@@ -960,7 +1026,12 @@ bemDom = /** @exports */{
9601026

9611027
base = getEntityBase(Block, blockName, base);
9621028

963-
return bem.declBlock(blockName, base, props, staticProps);
1029+
var lazyInit = extractLazyInitProp(staticProps),
1030+
entity = bem.declBlock(blockName, base, props, staticProps);
1031+
1032+
lazyInit !== null && processLazyInitRule(entity, lazyInit);
1033+
1034+
return entity;
9641035
},
9651036

9661037
/**
@@ -983,7 +1054,12 @@ bemDom = /** @exports */{
9831054

9841055
base = getEntityBase(Elem, entityName, base);
9851056

986-
return bem.declElem(blockName, elemName, base, props, staticProps);
1057+
var lazyInit = extractLazyInitProp(staticProps),
1058+
entity = bem.declElem(blockName, elemName, base, props, staticProps);
1059+
1060+
lazyInit !== null && processLazyInitRule(entity, lazyInit);
1061+
1062+
return entity;
9871063
},
9881064

9891065
declMixin : bem.declMixin,

common.blocks/i-bem-dom/i-bem-dom.spec.js

+64-10
Original file line numberDiff line numberDiff line change
@@ -164,16 +164,6 @@ describe('i-bem-dom', function() {
164164
block2.should.be.instanceOf(Block1);
165165
elem2.should.be.instanceOf(Elem1);
166166
});
167-
168-
it('should throw error if declMod contains lazyInit static property', function() {
169-
var Block = bemDom.declBlock('block');
170-
171-
function mod() {
172-
Block.declMod({ modName : 'mod' }, null, { lazyInit : true });
173-
}
174-
175-
mod.should.throw(Error, 'declMod with lazyInit prop not allowed. Your need use \'lazyInit\' in data-bem params');
176-
});
177167
});
178168

179169
describe('getMod', function() {
@@ -1675,6 +1665,70 @@ describe('i-bem-dom', function() {
16751665
describe('lazy init', function() {
16761666
var spy;
16771667

1668+
['block', 'elem'].forEach(function(entityType) {
1669+
it('should have different lazyInit for base ' + entityType + ' and modifiers', function() {
1670+
var spy1 = sinon.spy(),
1671+
spy2 = sinon.spy(),
1672+
spy3 = sinon.spy(),
1673+
spy4 = sinon.spy(),
1674+
spy5 = sinon.spy(),
1675+
1676+
Entity = entityType === 'block'? bemDom.declBlock('block', {
1677+
onSetMod : { js : { inited : spy1 } }
1678+
}, {
1679+
lazyInit : true
1680+
}) : bemDom.declElem('block', 'elem', {
1681+
onSetMod : { js : { inited : spy1 } }
1682+
}, {
1683+
lazyInit : true
1684+
});
1685+
1686+
Entity.declMod({ modName : 'm1' }, {
1687+
onSetMod : { js : { inited : spy2 } }
1688+
}, {
1689+
lazyInit : false
1690+
});
1691+
1692+
Entity.declMod({ modName : 'm2', modVal : true }, {
1693+
onSetMod : { js : { inited : spy3 } }
1694+
});
1695+
1696+
Entity.declMod({ modName : 'm3', modVal : '*' }, {
1697+
onSetMod : { js : { inited : spy4 } }
1698+
}, {
1699+
lazyInit : false
1700+
});
1701+
1702+
Entity.declMod({ modName : 'm3', modVal : 'v2' }, {
1703+
onSetMod : { js : { inited : spy5 } }
1704+
}, {
1705+
lazyInit : true
1706+
});
1707+
1708+
rootNode = initDom([
1709+
{ },
1710+
{ m1 : true },
1711+
{ m2 : true },
1712+
{ m3 : 'v1' },
1713+
{ m3 : 'v2' }
1714+
].map(function(mods) {
1715+
var bemjson = entityType === 'block'? { mods : mods }
1716+
: { elem : 'elem', elemMods : mods };
1717+
1718+
bemjson.block = 'block';
1719+
bemjson.js = true;
1720+
1721+
return bemjson;
1722+
}));
1723+
1724+
spy1.should.have.not.been.called;
1725+
spy2.should.have.been.called;
1726+
spy3.should.have.not.been.called;
1727+
spy4.should.have.been.called;
1728+
spy5.should.have.not.been.called;
1729+
});
1730+
});
1731+
16781732
it('should be possible to force initialization', function() {
16791733
spy = sinon.spy();
16801734

0 commit comments

Comments
 (0)