# What is available in the template body? While traversing input data, bem-xjst builds a context, which contains: * [normalized information about the current BEM entity](#normalized-information-about-the-current-bem-entity) * [the current BEMJSON node](#current-bemjson-node) * [helpers](#helpers) * [user-defined custom fields](#user-defined-custom-fields) * [Data tunneling for child’s templates](#data-tunneling-for-child’s-templates) * [methods for controlling the templating process](#methods-for-controlling-the-templating-process) # Template function When body of template is function, it calls with two arguments: 1. context of template (familiar to us as `this`) 2. current BEMJSON node (familiar to us as `this.ctx`) **Example** ```js block('link')({ attrs: function(node, ctx) { return { // the same as this.ctx.url href: ctx.url, // the same as this.position 'data-position': node.position }; } }); ``` The same arguments available in function of `match()`. ```js match(function(node, ctx) { // the same as this.mods.disabled return !node.mods.disabled && // the same as this.ctx.target ctx.target; }) ``` Moreover, template functions can be arrow functions: ```js match((node, ctx) => ctx.target) addAttrs: (node, ctx) => ({ href: ctx.url }) ``` ## Normalized information about the current BEM entity The template engine normalizes data about the current BEM entity. The current BEMJSON node might have incomplete information about the BEM entity. For example: ```js { block: 'page', content: { elem: 'top' // The node doesn’t have the `block` field. // But the template engine understands which // block context the `top` element is in. } } ``` Fields with normalized data: * `this.block {String}` — the block in the current node, or the block of the BEM entity that provides the context for the current node. * `this.elem {String}` — element in the current node * `this.mods {Object}` — block modifiers that are explicitly defined in the current node * `this.elemMods {Object}` — element modifiers that are explicitly defined in the current node Note that the `this.mods` and `this.elemMods` objects always exist, so checking for their presence in the template body is redundant: ```js block('page').match((node, ctx) => { // Redundant: return node.mods && node.mods.type === 'index' && ctx.weather; // Sufficient: return node.mods.type === 'index' && ctx.weather; })({ def: () => ({ … }) }); ``` ## Current BEMJSON node The current BEMJSON node is available in the `this.ctx` field. ```js { block: 'company', name: 'yandex' } ``` ```js block('link')({ attrs: (node, ctx) => ({ id: ctx.name, name: ctx.name }) }); ``` *Result of templating:* ```html
``` ## Helpers ### Escape methods #### xmlEscape ```js /** * @param {String} str * @returns {String} */ this.xmlEscape(str) ``` Returns the passed `str` string with the following XML symbols escaped: `&`, `<`, `>`. Normaly, expected that `str` is a `String`. But if `str` is `undefined`, `Null` or `NaN` an empty string returned. If `str` is of any other type it will be casted to String before escaping. Usage example: ```js { block: 'button' } ``` Template: ```js block('button')({ def: (node) => node.xmlEscape('&') }); ``` *Result of templating:* ```html <b>&</b> ``` #### attrEscape ```js /** * @param {String} str * @returns {String} */ this.attrEscape(str) ``` Returns the passed `str` string with the following characters for XML and HTML attributes escaped: `"` and `&`. Normaly, expected that `str` is a `String`. But if `str` is `undefined`, `Null` or `NaN` type you get empty string. If `str` is any other type you get native casting from it type to `String` before escaping. #### jsAttrEscape ```js /** * @param {String} str * @returns {String} */ this.jsAttrEscape(str) ``` Returns the passed `str` string with the following characters escaped: `'` and `&`. Normaly, expected that `str` is a `String`. But if `str` is `undefined`, `Null` or `NaN` type you get empty string. If `str` is any other type you get native casting from it type to `String` before escaping. By default, input data from the [`js`](4-data.md#js) field and data from the [`js`](5-templates-syntax.md#js) mode are escaped using this function. ### Position helpers #### this.position The position in the BEM tree (the `this.position` context field) is a natural number corresponding to the sequential number of the current (contextual) BEM entity in relation to its neighbors in the tree (peer entities). When calculating the position: * Numbering applies only to nodes of processed BEMJSON that correspond to BEM entities. * Other nodes are not given a position number. * Positions are numbered starting from 1. Example of position numbering in the input BEM tree: ```js { block: 'page', // this.position === 1 content: [ { block: 'head' }, // this.position === 1 'text', // this.position === undefined { block: 'menu', // this.position === 2 content: [ { elem: 'item' }, // this.position === 1 'text', // this.position === undefined { elem: 'item' }, // this.position === 2 { elem: 'item' } // this.position === 3 ] } ] } ``` The BEM tree may be filled in as templates are executing, by using templates in the [`def`](5-templates-syntax.md#def) or [`content`](5-templates-syntax.md#content) mode. This dynamic modification of the BEM tree is taken into account when calculating positions. The `isLast` function for determining the last BEM entity among peers returns `false` if the last element in the array containing the nodes is not a BEM entity. ```js block('list')({ content: [ { block: 'item1' }, { block: 'item2' }, // this.isLast() === false 'text' ] }); ``` This behavior is explained by the fact that for optimization purposes, BEMHTML does not perform a preliminary traversal of the BEM tree. This means that at the time when the `item2` block is processed, the length of the array is already known (`item2` is not the last element). However, it is not yet known that the last element is not a BEM element and won’t get a position number. In practice, this case shouldn’t generate errors, because the check for the first and last BEM entity is normally applied to automatically generated lists, and it doesn’t make sense to include other types of data in them. #### isFirst ```js /** * @returns {Boolean} */ this.isFirst() ``` Checks whether the node is the first among its peers in the input tree. #### isLast ```js /** * @returns {Boolean} */ this.isLast() ``` Checks whether the node is the last among its peers in the input tree. ### Unique ID generator #### this.generateId() Generates an 'id' for the current node. Usage example: ```js // BEMJSON { block: 'input', label: 'Name', value: 'John Malkovich' } ``` Template ```js block('input')({ content: (node, ctx) => { var id = node.generateId(); return [ { tag: 'label', attrs: { for: id }, content: ctx.label }, { tag: 'input', attrs: { id: id, value: ctx.value } } ]; } }); ``` *Result of templating:* ```html