Skip to content

Commit

Permalink
runtime: support mix
Browse files Browse the repository at this point in the history
Fix: #28
  • Loading branch information
indutny committed Apr 27, 2015
1 parent 7eba120 commit de1b33e
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 21 deletions.
10 changes: 7 additions & 3 deletions lib/bemhtml/runtime/entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,14 @@ Entity.prototype.init = function init(block, elem) {
this.elem = elem;

// Class for jsParams
if (this.elem === undefined)
this.jsClass = this.block;
this.jsClass = Entity.key(this.block, this.elem);
};

Entity.key = function key(block, elem) {
if (elem === undefined)
return block;
else
this.jsClass = this.block + '__' + this.elem;
return block + '__' + elem;
};

function contentMode() {
Expand Down
136 changes: 119 additions & 17 deletions lib/bemhtml/runtime/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,16 @@ BEMHTML.prototype.groupEntities = function groupEntities(tree) {
};

BEMHTML.prototype.transformEntities = function transformEntities(entities) {
Object.keys(entities).forEach(function(key) {
var keys = Object.keys(entities);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];

// TODO(indutny): pass this values over
var parts = key.split('__', 2);
var block = parts[0];
var elem = parts[1];
entities[key] = new Entity(this, block, elem, entities[key]);
}, this);
}
return entities;
};

Expand All @@ -125,7 +128,7 @@ BEMHTML.prototype.run = function run(context) {
var res;
if (!context)
res = this.runEmpty();
else if (Array.isArray(context))
else if (utils.isArray(context))
res = this.runMany(context);
else if (utils.isSimple(context))
res = this.runSimple(context);
Expand Down Expand Up @@ -293,7 +296,21 @@ BEMHTML.prototype.render = function render(context,
entity.elem,
ctx.elemMods || ctx.mods);

// TODO(indutny): support mix
var totalMix = mix;
if (ctx.mix) {
if (totalMix)
totalMix = [].concat(totalMix, ctx.mix);
else
totalMix = ctx.mix;
}

if (mix) {
var m = this.renderMix(entity, totalMix, jsParams, addJSInitClass);
out += m.out;
jsParams = out.jsParams;
addJSInitClass = out.addJSInitClass;
}

if (cls)
out += ' ' + cls;
} else {
Expand Down Expand Up @@ -355,26 +372,111 @@ BEMHTML.prototype.renderClose = function renderClose(tag,
return out;
};

BEMHTML.prototype.renderMix = function renderMix(entity,
mix,
jsParams,
addJSInitClass) {
var visited = {};
var context = this.context;
var json = context.ctx;
var js = jsParams;
var addInit = addJSInitClass;

visited[entity.jsClass] = true;

// Transform mix to the single-item array if it's not array
if (!utils.isArray(mix))
mix = [ mix ];

var out = '';
for (var i = 0; i < mix.length; i++) {
var item = mix[i];
var hasItem = (item.block &&
(context.block !== json.block ||
item.block !== context.block)) ||
item.elem;
var block = item.block || item._block || context.block;
var elem = item.elem || item._elem || context.elem;
var key = Entity.key(block, elem);

if (hasItem)
out += ' ' + key;

out += this.buildModsClasses(
block,
item.elem || item._elem || (item.block ? undefined : context.elem),
item.elemMods || item.mods);

if (item.js) {
if (!js)
js = {};

js[key] = item.js === true ? {} : item.js;
if (!addInit)
addInit = block && !item.elem;
}

// Process nested mixes
if (!hasItem || visited[key])
continue;

visited[key] = true;
var nestedEntity = this.entities[key];
if (!nestedEntity)
continue;

var oldBlock = context.block;
var oldElem = context.elem;
var nestedMix = nestedEntity.mix.exec(context);
context.elem = oldElem;
context.block = oldBlock;

if (!nestedMix)
continue;

for (var j = 0; j < nestedMix.length; j++) {
var nestedItem = nestedMix[j];
if (!nestedItem.block &&
!nestedItem.elem ||
!visited[Entity.key(nestedItem.block, nestedItem.elem)]) {
nestedItem._block = block;
nestedItem._elem = elem;
mix = mix.slice(0, i + 1).concat(
nestedItem,
mix.slice(i + 1)
);
}
}
}

return {
out: out,
jsParams: js,
addJSInitClass: addInit
};
};

BEMHTML.prototype.buildModsClasses = function buildModsClasses(block,
elem,
mods) {
if (!mods)
return '';

var res = '';

if (mods) {
var modName;
for (modName in mods) {
if (!mods.hasOwnProperty(modName))
continue;
var modName;
for (modName in mods) {
if (!mods.hasOwnProperty(modName))
continue;

var modVal = mods[modName];
if (!modVal && modVal !== 0) continue;
if (typeof modVal !== 'boolean')
modVal += '';
var modVal = mods[modName];
if (!modVal && modVal !== 0) continue;
if (typeof modVal !== 'boolean')
modVal += '';

res += ' ' + (elem ?
utils.buildElemClass(block, elem, modName, modVal) :
utils.buildBlockClass(block, modName, modVal));
}
res += ' ' + (elem ?
utils.buildElemClass(block, elem, modName, modVal) :
utils.buildBlockClass(block, modName, modVal));
}

return res;
Expand Down
4 changes: 3 additions & 1 deletion lib/bemhtml/runtime/match.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
var utils = require('./utils');

var PropertyMatch = require('./tree').PropertyMatch;
var PropertyAbsent = require('./tree').PropertyAbsent;
var CustomMatch = require('./tree').CustomMatch;
Expand Down Expand Up @@ -55,7 +57,7 @@ function MatchTemplate(mode, template) {
for (var i = 0; i < this.predicates.length; i++) {
var pred = template.predicates[i];
if (pred instanceof PropertyMatch) {
if (Array.isArray(pred.key))
if (utils.isArray(pred.key))
this.predicates[i] = new MatchNested(this, pred);
else
this.predicates[i] = new MatchProperty(this, pred);
Expand Down
43 changes: 43 additions & 0 deletions test/api-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,49 @@ describe('BEMHTML compiler', function() {
}, { tag: false, content: 'ok' }, 'ok');
});

describe('mix', function() {
it('should avoid loops', function() {
test(function() {
block('b1')(
tag()('a'),
mix()([
{ block: 'b2' }
])
);
block('b2')(
tag()('b'),
mix()([
{ mods: { modname: 'modval' } },
{ block: 'b1' }
])
);
}, { block: 'b1' }, '<a class="b1 b2 b2_modname_modval"></a>');
});

it('should support nested mix', function() {
test(function() {
block('b1')(
tag()('a'),
mix()([ { block: 'b2' }, { block: 'b3', elem: 'e3' } ])
);
block('b2')(
tag()('b'),
mix()([ { mods: { modname: 'modval' } } ])
);
block('b3')(
tag()('b'),
elem('e3').mix()([ { mods: { modname: 1 } } ])
);
}, {
block: 'b1',
mods: {
x: 10
}
}, '<a class="b1 b1_x_10 b2 b2_modname_modval b3__e3 b3__e3_modname_1">' +
'</a>');
});
});

describe('position in Context', function() {
test(function() {
block('b1').content()(function() { return this.position; });
Expand Down

0 comments on commit de1b33e

Please sign in to comment.