In short, Block-Element-Modifier (BEM) is a way to split project's markup, styles, scripts and images into small independent logical pieces. This helps to maintain, refactor and reuse these pieces in the long run.
Read more at BEM's site.
jQuery BEM plugin is an alternative implementation of just client-side BEM runtime.
First of all, jQuery BEM plugin requires using a certain naming convention. jQuery BEM locates blocks and elements by analyzing class names of page's DOM nodes. So, correct class names are the key to success.
b-
or
l-
.
[a-zA-Z0-9-]+
).
b-block__elem
).
b-block_mod_val
).
b-block__elem_mod_val
).
b-test
. This block has one
modifier named type
with
yo
value. There are two elements. First element is
named elem1
. Second element is named
elem2
. Second element has one modifier named
mood
with happy
value.
<div class="b-test b-test_type_yo">
<span class="b-test__elem1">element1</span>
<span class="b-test__elem2 b-test__elem2_mood_happy">element2</span>
</div>
Finding blocks with jQuery BEM is as easy as finding DOM nodes with jQuery — just use CSS selectors and traversing functions.
jQuery BEM adds new kinds of selectors to find blocks and elements.
%b-block
selects all
blocks with the given name.
%b-block(elem)
selects
all elements with the given name of all blocks with the
given name.
{mod}
selects blocks and
elements that have the specified modifier.
{mod=val}
selects blocks and elements that have the specified
modifier with a value exactly equal to a certain value.
{mod|=val}
selects blocks and elements that have the specified
modifier with a value either equal to a given string or
starting with that string followed by a hyphen (-).
{mod*=val}
selects blocks and elements that have the specified
modifier with a value containing the a given substring.
{mod$=val}
selects blocks and elements that have the specified
modifier with a value ending exactly with a given
string. The comparison is case sensitive.
{mod!=val}
select blocks and elements that either don't have the
specified modifier, or do have the specified modifier
but not with a certain value.
{mod^=val}
selects blocks and elements that have the specified
modifier with a value beginning exactly with a given
string.
These selectors could be used among all other jQuery selectors.
// Find all blocks named b-test.
$('%b-test');
// Find all blocks named b-test that have the specified modifier.
$('%b-test{cool=yes}');
// Find all elements named elem1 of blocks named b-test that have the specified
// modifier.
$('%b-test(elem1){cool=yes}');
// Just another example.
$('div %b-test span[id="123"] %b-test2[value="yo"]{test=yes}{ok=no}');
// And yet another one.
$('%b-button').closest('%b-form{active=yes}');
jQuery BEM extends
jQuery
object with two methods:
.bemMod()
and
.bemCall()
.
.bemMod('mod', 'val')
— set modifier mod to
val for each block or element from
the set of matched DOM elements.
.bemMod({block: 'b-block', mod: 'mod'}, 'val')
— set modifier mod to
val for each block with
b-block name from
the set of matched DOM elements.
.bemMod({block: 'b-block', elem: 'elem1', mod: 'mod'}, 'val')
— set modifier mod to
val for each element named
elem1 that belong to block named
b-block from the set
of matched DOM elements.
.bemCall('meth')
.bemCall({call: 'meth'})
— call method named meth of each
block or element from the set of matched DOM elements.
.bemCall({call: 'meth', block: 'b-block'})
— call method named meth of each
block named b-block from the set of
matched DOM elements.
.bemCall({call: 'meth', block: 'b-block', elem: 'elem1'})
— call method named meth of each
element named elem1 that belong to
block named b-block
from the set of matched DOM elements.
.bemMod('mod')
.bemMod({block: 'b-block', mod: 'mod'})
.bemMod({block: 'b-block', elem: 'elem1', mod: 'mod'})
— get mod modifier
of the first block or the first block named b-block or
the first element named elem1 of the block
named b-block respectively.
Here is an example. Let initial DOM looks like below:
...
<body>
<div class="b-block1">first block</div>
<div class="b-block2 b-block3">two blocks on the same node</div>
<div class="b-block4">
<div class="b-block4__elem1">first element</div>
<div class="b-block4__elem2">second element</div>
</div>
</body>
...
If we execute JavaScript code below:
$('*') // Select all DOM elements.
.bemMod('mod1', 'val1')
.bemMod({block: 'b-block2', mod: 'mod2'}, 'val2')
.bemMod({block: 'b-block4', elem: 'elem2', mod: 'mod3'}, 'val3');
The result will be (class names are wrapped for readability):
...
<body>
<div class="b-block1
b-block1_mod1_val1">first block</div>
<div class="b-block2
b-block2_mod1_val1
b-block2_mod2_val2
b-block3
b-block3_mod1_val1">two blocks on the same node</div>
<div class="b-block4
b-block4_mod1_val1">
<div class="b-block4__elem1
b-block4__elem1_mod1_val1">first element</div>
<div class="b-block4__elem2
b-block4__elem2_mod1_val1
b-block4__elem2_mod3_val3">second element</div>
</div>
</body>
...
There are two kinds of actions jQuery BEM blocks and elements support: modifier change and method call. When a modifier is being changed or a method is being called, jQuery BEM matches declared callbacks against current document state and creates a list of matched callbacks (sorted by order of declaration, from the last one to the first) and calls the first callback from this list.
$.BEM.decl()
and
$.BEM.extend()
methods
are used to declare callbacks. The last one is meant to extend base declaration,
all the callbacks passed to
$.BEM.extend()
method
will be supplied with
$super
as a first
argument.
Inside all the callbacks
this
is referred to
current block object. Initially this object has one property and two methods:
$
property points to
block's jQuery element.
bemMod()
and
bemCall()
methods
should be used to set modifiers and call methods. You can assign additional
properties to this
object, these properties will be accessible from all other callbacks of
current block.
Here is a short example:
$.BEM.decl('b-block')
.onMod('test',
function(mod, val, prev) {
this['something'] = 987654;
console.log('base decl: ' + val);
})
.onCall('meme',
function(arg1, arg2) {
console.log(arg1 + ' ' + arg2 + ' ' + this['something']);
return 'mimi';
});
$.BEM.extend('b-block')
.onMod('test',
function($super, mod, val, prev) {
console.log('extend decl: ' + val);
$super(mod, val, prev);
this['something'] += 1;
})
.onCall('meme',
function($super, arg1, arg2) {
return $super(arg1 + 'aaa', arg2 + 'bbb');
});
// Let's assume we have <div class="b-block"></div> in our document.
var b = $('%b-block');
b.bemMod('test', 'ololo');
> extend decl: ololo
> base decl: ololo
console.log(b.bemCall('meme', 11, 22));
> 11aaa 22bbb 987655
> mimi
Modifier changes and method calls are for already existing blocks. Builders are a special kind of declaration to build blocks that not yet exist.
Some small example is below:
$.BEM.decl('b-block')
.onBuild(
function(details) {
// First argument is a block's metadata.
// In this case:
// {"block": "b-block", "mods": {"mod1": 123, "mod2": "abc"}}
// jQuery BEM has $.BEM.className() helper to create appropriate
// class name from this metadata.
return $('<div class="' + $.BEM.className(details) + '"></div>');
});
$.BEM.extend('b-block')
.onBuild(
function($super, details, arg1, arg2) {
// Calling base declaration.
var b = $super(details);
// Base declaration has returned an empty <div>, put some text in.
b.text(arg1 + ' ' + arg2);
// `this` points to whatever `context` property says during
// $.BEM.build() call. So, append new <div> to <body>.
this.append(b);
// You can return values from method calls and from builders.
return b;
});
var tmp = $.BEM.build({
block: 'b-block',
context: $('body'),
mods: {mod1: 123, mod2: 'abc'}
}, 'Hello', 'world');
// `tmp` is a jQuery object pointing to <div> element appended into <body>:
// <div class="b-block b-block_mod1_123 b-block_mod2_abc">Hello world</div>
It is possible to customize block prefixes. To do that, call
$.BEM.setup({prefixes: '(?:ololo-)'});
.
In this example we've changed default b-
and
l-
prefixes to ololo-
prefix. $.BEM.setup()
should be called before any
declarations. Note that incorrect regular expression could break everything.
Pending.