View on GitHub jQuery BEM

What is BEM?

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.

Documentation Contents

Basic Principles

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.

Finding Blocks and Elements

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.

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}');

Setting Modifiers and Calling Methods

jQuery BEM extends jQuery object with two methods: .bemMod() and .bemCall().

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>
...

JavaScript Declaration

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

Builders

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>

Setup

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.

Example

Pending.