Forum

Methodology

Toolbox

Platform

Community

API

Choosing an engine, compiling and applying templates

BEMHTML engine

var bemxjst = require('bem-xjst');
var bemhtml = bemxjst.bemhtml;

// Add a template
var templates = bemhtml.compile(function() {
    block('quote').tag()('q');
});

// Add data
var bemjson = { block: 'quote', content: 'I came, I saw, I templated.' };

// Apply templates
var html = templates.apply(bemjson);

The resulting html contains the string:

<q class="quote">I came, I saw, I templated.</q>

BEMTREE engine

var bemxjst = require('bem-xjst');
var bemtree = bemxjst.bemtree;

// Add a template
var templates = bemtree.compile(function() {
    block('phone').content()({ mask: '8-800-×××-××-××', mandatory: true });

    block('page').content()([
        { block: 'header' },
        { block: 'body' },
        { block: 'footer' }
    ]);
});

// Add data
var bemjson = [ { block: 'phone' }, { block: 'page' } ];

// Apply templates
var result = templates.apply(bemjson);
// 'result' contains:
[
    {
        block: 'phone',
        content: {
            mask: '8-800-×××-××-××',
            mandatory: true
        }
    },
    {
        block: 'page',
        content: [
            { block: 'header' },
            { block: 'body' },
            { block: 'footer' }
        ]
    }
]

Adding templates

To add templates to the templates instance, use the compile method.

var bemxjst = require('bem-xjst');

// Instantiating the 'templates' class
var templates = bemxjst.bemhtml.compile(function() {
    block('header').tag()('h1');
});

// Add data
var bemjson = { block: 'header', content: 'Documentation' };

var html = templates.apply(bemjson);
// html: '<h1 class="header">Documentation</h1>'

// Add templates to the created instance of the 'templates' class
templates.compile(function() {
    block('header').tag()('h2');
});

html = templates.apply(bemjson);
// Now the HTML tag is h2 instead of h1:
// html: '<h2 class="header">Documentation</h2>'

If you need to bundle all the templates, the most efficient way is to use the generate method.

Settings

Delimiters in names of BEM entities

var bemxjst = require('bem-xjst');
var templates = bemxjst.bemhtml.compile(function() {
    // This example doesn’t add any templates.
    // HTML will be rendered using the default behavior of the template engine.
    }, {
        // Setting up BEM naming
        naming: {
            elem: '__',
            mod: '_'
        }
    });

var bemjson = {
    block: 'page',
    mods: { theme: 'gray' },
    content: {
        elem: 'head',
        elemMods: { type: 'short' }
    }
};

var html = templates.apply(bemjson);

The resulting html contains the string:

<div class="page page_theme_gray"><div class="page__head page__head_type_short"></div></div>

You can find more information in naming conventions article.

Support JS-instances for elements (bem-core v4+)

bem-xjst have elemJsInstances option for support JS instances for elems (bem-core v4+).

var bemxjst = require('bem-xjst');
var templates = bemxjst.bemhtml.compile(function() {
    // In this example we will add no templates.
    // Default behaviour is used for HTML rendering.
    }, {
        // Turn on support for JS instances for elems
        elemJsInstances: true
    });

var bemjson = {
    block: 'b',
    elem: 'e',
    js: true
};

var html = templates.apply(bemjson);

Result with v6.2.x:

<div class="b__e" data-bem='{"b__e":{}}'></div>

Result with v6.3.0:

<div class="b__e i-bem" data-bem='{"b__e":{}}'></div>

Notice that i-bem was added.

XHTML option

xhtml option allow you to ommit closing slash in void HTML elements (only have a start tag).

Default value is true. But in nex major version we invert it.

Example for v6.2.0:

var bemxjst = require('bem-xjst');
var templates = bemxjst.bemhtml.compile(function() {
    // In this example we didn’t add templates
    // bem-xjst will render by default
    }, {
        // Turn off XHTML
        xhtml: false
    });

var bemjson = { tag: 'br' };
var html = templates.apply(bemjson);

Result of templating:

<br>

Optional End Tags

With option omitOptionalEndTags template engine will ommit optional end tags. The option is turn off by default.

You can find list of optional end tags in specifications: HTML4 and HTML5.

var bemxjst = require('bem-xjst');
var templates = bemxjst.bemhtml.compile(function() {
    // In this example we will add no templates.
    // Default behaviour is used for HTML rendering.
    }, {
        // Turn off optional end tags
        omitOptionalEndTags: true
    });

var bemjson = {
    tag: 'table',
    content: {
        tag: 'tr',
        content: [
            { tag: 'th', content: 'table header' },
            { tag: 'td', content: 'table cell' }
        ]
    }
};

var html = templates.apply(bemjson);

Result of templating:

<table><tr><th>table header<td>table cell</table>

Unquoted attributes

HTML specification allows us ommit unnececary quotes in some cases. See HTML4 and HTML5 specs.

You can use unquotedAttrs option to do so.

var bemxjst = require('bem-xjst');
var templates = bemxjst.bemhtml.compile(function() {
    // In this example we will add no templates.
    // Default behaviour is used for HTML rendering.
    }, {
        // allow unqouted attributes if it’s possible
        unquotedAttrs: true
    });

var bemjson = { block: 'b', attrs: { name: 'test' } };

var html = templates.apply(bemjson);

Result of templating:

<div class=b name=test></div>

Escaping

You can set escapeContent option to true to escape string values of content field with xmlEscape.

Example

var bemxjst = require('bem-xjst');
var templates = bemxjst.bemhtml.compile(function() {
    // In this example we will add no templates.
    // Default behaviour is used for HTML rendering.
    }, {
        // Turn on content escaping
        escapeContent: true
    });

var bemjson = {
    block: 'danger',
    // Danger UGC content
    content: '&nbsp;<script src="alert()"></script>'
};

var html = templates.apply(bemjson);

Result of templating:

<div class="danger">&amp;nbsp;&lt;script src="alert()"&gt;&lt;/script&gt;</div>

If you want avoid escaping in content use special value: { html: '…' }.

Example

var bemxjst = require('bem-xjst');
var templates = bemxjst.bemhtml.compile(function() {
    // In this example we will add no templates.
    // Default behaviour is used for HTML rendering.
    }, {
        // Turn on content escaping
        escapeContent: true
    });

var bemjson = {
    block: 'trusted',
    // Trusted and safe content
    content: {
        html: 'I <3 you!'
    }
};

var html = templates.apply(bemjson);

In this case content.html will be rendered as is:

<div class="trusted">I <3 you!</div>

Notice that in content.html expected only string type.

Runtime linting

By turning on runtimeLint option you can get warnings about wrong templates or input data. About these warnings you can read migration guide.

var bemxjst = require('bem-xjst');
var bemhtml = bemxjst.bemhtml;

var templates = bemhtml.compile(function() {
  block('b').content()('yay');

  block('mods-changes').def()(function() {
    this.ctx.mods.one = 2;
    return applyNext();
  });
}, { runtimeLint: true });

var html = templates.apply([
  { block: 'b' },

  // boolean attributes
  { block: 'b', attrs: { one: true, two: 'true' } },

  // mods for elem
  { block: 'c', elem: 'e', mods: { test: 'opa' } },

  // Присвоения в this.ctx.mods
  { block: 'mods-changes', mods: { one: '1', two: '2' } }
]);

As usual you get result of applying templates in html variable. But in addition of this you can catch wargings in STDERR:

BEM-XJST WARNING: boolean attribute value: true in BEMJSON: { block: 'b', attrs: { one: true, two: 'true' } }

Notice what bem-xjst behaviour changed: https://github.com/bem/bem-xjst/releases/tag/v4.3.3

BEM-XJST WARNING: mods for elem in BEMJSON: { block: 'c', elem: 'e', mods: { test: 'opa' } }

Notice what bem-xjst behaviour changed: https://github.com/bem/bem-xjst/releases/tag/v5.0.0

BEM-XJST WARNING: looks like someone changed ctx.mods in BEMJSON: { block: 'mods-changes', mods: { one: 2, two: '2' } } old value of ctx.mod.one was 1

Notice that you should change this.mods instead of this.ctx.mods in templates

Production mode

You can use option production to render whole BEMJSON even if one template contains error.

Example

var template = bemxjst.compile(function() {
  block('b1').attrs()(function() {
    var attrs = applyNext();
    attrs.undef.foo = 'bar';
    return attrs;
  });
}, { production: true });
var html = template.apply({ block: 'page', content: { block: 'b1' } });

html will equals <div class="page"></div>.

Also in production mode bem-xjst will produce error messages to STDERR.

$node index.js 1> stdout.txt 2> stderr.txt

$ cat stdout.txt
<div class="page"></div>

$ cat stderr.txt
BEMXJST ERROR: cannot render block b1, elem undefined, mods {}, elemMods {} [TypeError: Cannot read property 'undef' of undefined]

exportName option

You can use exportName option for choose name of variable where engine will be exported. By default it’s BEMHTML or BEMTREE.

For example read next.

Using thirdparty libraries

BEMTREE and BEMHTML allows you using thirdparty libraries as well as a global dependencies and different modular systems.

For example:

{
    requires: {
        'lib-name': {
            globals: 'libName',           // Variable name from global scope
            ym: 'lib-name',               // Module name from YModules
            commonJS: 'path/to/lib-name'  // path to CommonJS library
        }
    }
}

lib-name module will be accessible in templates body like this:

block('button').content()(function () {
    var lib = this.require('lib-name');

    return lib.hello();
});

It’s necessary to specify each environment you want to expose library to.

E.g. if you specify just global scope the library will only be available as global variable even though some module system will be present in runtime.

{
    requires: {
        'lib-name': {
            globals: 'dependName' // Variable name from global scope
        }
    }
}

Example of using moment.js library:

You don’t need to to provide path to module:

{
    requires: {
        moment: {
            commonJS: 'moment',  // path to CommonJS module, relative bundle file
        }
    }
}

In templates body the module will be acessible as this.require('moment'). You can use the template in any browser or in Node.js:

block('post').elem('data').content()(function() {
    var moment = this.require('moment');

    return moment(this.ctx.date) // Time in ms from server
        .format('YYYY-MM-DD HH:mm:ss');
});

Extending BEMContext

You can extend BEMContext in order to use user-defined functions in the template body.

var bemxjst = require('bem-xjst');
var templates = bemxjst.bemhtml.compile('');

// Extend the context prototype
templates.BEMContext.prototype.hi = function(name) {
    return 'Hello, ' + name;
};

// Add templates
templates.compile(function() {
    block('b').content()(function() {
        return this.hi('templates');
    });
});

// Input data
var bemjson = { block: 'b' };

// Apply templates
var html = templates.apply(bemjson);

The resulting html contains the string:

<div class="b">Hello, templates</div>

Bundling

The generate method generates JavaScript code that can be passed and run in the browser to get the templates object.

var bemxjst = require('bem-xjst');
var bundle = bemxjst.bemhtml.generate(function() {
    // user-defined templates
    // …
});

Now bundle has a string containing the JavaScript code of the BEMHTML core and the user-defined templates.

Read next: Input data

If you notice a mistake or want something to supplement the article, you can always write to us at GitHub, or correct an article using prose.io.