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(() => {
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(() => {
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(() => {
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(() => {
// 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: { name: '--', val: '_' }
}
});
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(() => {
// 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(() => {
// 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(() => {
// 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(() => {
// 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>
singleQuotesForDataAttrs option
By default data-attributes values will be marked with double quotes. For BEMJSON:
{
block: 'b',
attrs: {
id: 'without-changes',
'data-test': '{"reqid":"42"}'
}
}
Result of templating:
<div class="b" id="without-changes" data-test="{"reqid":"42"}"></div>
All double quotes inside data-test
value was escaped. If you want to avoid such behaviour you can use singleQuotesForDataAttrs
option.
var bemxjst = require('bem-xjst');
var templates = bemxjst.bemhtml.compile(() => {
// In this example we will add no templates.
// Default behaviour is used for HTML rendering.
}, {
// use single quotes for data-* attributes
singleQuotesForDataAttrs: true
});
var bemjson = { block: 'b', attrs: {
id: 'without-changes',
'data-test': '{"reqid":"42"}'
} };
var html = templates.apply(bemjson);
Result of templating with option:
<div class="b" id="without-changes" data-test='{"reqid":"42"}'></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(() => {
// 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: ' <script src="alert()"></script>'
};
var html = templates.apply(bemjson);
Result of templating:
<div class="danger">&nbsp;<script src="alert()"></script></div>
If you want avoid escaping in content use special value: { html: '…' }
.
Example
var bemxjst = require('bem-xjst');
var templates = bemxjst.bemhtml.compile(() => {
// 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(() => {
block('b')({ content: 'yay' });
block('mods-changes')({
def: (ctx, json) => {
json.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(() => {
block('b1')({
attrs: () => {
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]
When you use production
option with true
value you can define function for custom error logging. This function will be used instead of regilar console.error
. Custom function will be filled with two arguments:
Object with block, element and modifiers fields where error occurred.
Original JS error.
You can define custom onError
function by extending prototype of BEMContext
. For example:
var bemxjst = require('bem-xjst');
var templates = bemxjst.bemhtml.compile('', { production: true });
templates.BEMContext.prototype.onError = function(context, err) { … };
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 bundling section.
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
commonJS: 'path/to/lib-name', // path to CommonJS library
ym: 'lib-name' // Module name from YModules
}
}
}
lib-name
module will be accessible in templates body like this:
block('button')({
content: (node) => {
var lib = node.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
}
}
}
In other case, if you specify multiple modular systems, template will attempt to get it from them in this order:
global
CommonJS
YModules (if available) If required module was found on some step, next steps will be ignored and template will use that first retrieved module.
Thus, if module available in global variable, its value will be provided inside template, in spite of module avialability in CommonJS/YModules.
Same way, CommonJS module is more prior to YModules one.
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: (node, ctx) => {
var moment = node.require('moment');
return moment(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(() => {
block('b')({
content: (node) => node.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(() => {
// user-defined templates
// …
});
Now bundle
has a string containing the JavaScript code of the BEMHTML core and the user-defined templates.
Source maps
There is options in generate
method to use source maps.
to
{String} — output bundle file namesourceMap.from
{String} — file namesourceMap.prev
{Object} — previous source map object
Example of generating bundle with source maps:
var fs = require('fs'),
bemxjst = require('bem-xjst').bemhtml,
tmpl = 'my-block-1.bemhtml.js',
bundle = 'bundle.bemhtml.js';
var result = bemxjst.generate(fs.readFileSync(tmpl, 'utf8'), {
to: bundle,
sourceMap: { from: tmpl }
});
fs.writeFileSync(bundle, result);
Also see examples about source maps and bem-xjst.
Read next: Input data