From ffb89bb72bf638599f4fe21503caa5d7afd239b8 Mon Sep 17 00:00:00 2001 From: miripiruni Date: Mon, 29 Jan 2018 16:43:42 +0300 Subject: [PATCH] bem-xjst: short syntax for simple modes (fix #444) --- docs/en/2-quick-start.md | 2 +- docs/en/3-api.md | 66 ++-- docs/en/5-templates-syntax.md | 205 +++++++------ docs/en/6-templates-context.md | 108 ++++--- docs/en/7-runtime.md | 62 ++-- docs/ru/2-quick-start.md | 2 +- docs/ru/3-api.md | 70 +++-- docs/ru/5-templates-syntax.md | 210 +++++++------ docs/ru/6-templates-context.md | 108 ++++--- docs/ru/7-runtime.md | 63 ++-- lib/bemxjst/tree.js | 89 +++++- test/templates-syntax-test.js | 530 +++++++++++++++++++++++++++++++++ 12 files changed, 1119 insertions(+), 396 deletions(-) diff --git a/docs/en/2-quick-start.md b/docs/en/2-quick-start.md index e9cf014c..7ba01426 100644 --- a/docs/en/2-quick-start.md +++ b/docs/en/2-quick-start.md @@ -21,7 +21,7 @@ var bemhtml = bemxjst.bemhtml; // Add templates using the `compile` method var templates = bemhtml.compile(function() { - block('text').tag()('span'); + block('text')({ tag: 'span' }); }); // Add data in BEMJSON format diff --git a/docs/en/3-api.md b/docs/en/3-api.md index 163ce3cb..d640fd49 100644 --- a/docs/en/3-api.md +++ b/docs/en/3-api.md @@ -27,7 +27,7 @@ var bemhtml = bemxjst.bemhtml; // Add a template var templates = bemhtml.compile(function() { - block('quote').tag()('q'); + block('quote')({ tag: 'q' }); }); // Add data @@ -51,13 +51,15 @@ var bemtree = bemxjst.bemtree; // Add a template var templates = bemtree.compile(function() { - block('phone').content()({ mask: '8-800-×××-××-××', mandatory: true }); + block('phone')({ content: { mask: '8-800-×××-××-××', mandatory: true } }); - block('page').content()([ - { block: 'header' }, - { block: 'body' }, - { block: 'footer' } - ]); + block('page')({ + content: [ + { block: 'header' } + { block: 'body' }, + { block: 'footer' } + ] + }); }); // Add data @@ -94,7 +96,7 @@ var bemxjst = require('bem-xjst'); // Instantiating the 'templates' class var templates = bemxjst.bemhtml.compile(function() { - block('header').tag()('h1'); + block('header')({ tag: 'h1' }); }); // Add data @@ -105,7 +107,7 @@ var html = templates.apply(bemjson); // Add templates to the created instance of the 'templates' class templates.compile(function() { - block('header').tag()('h2'); + block('header')({ tag: 'h2' }); }); html = templates.apply(bemjson); @@ -360,11 +362,13 @@ var bemxjst = require('bem-xjst'); var bemhtml = bemxjst.bemhtml; var templates = bemhtml.compile(function() { - block('b').content()('yay'); + block('b')({ content: 'yay' }); - block('mods-changes').def()(function() { - this.ctx.mods.one = 2; - return applyNext(); + block('mods-changes')({ + default: function() { + this.ctx.mods.one = 2; + return applyNext(); + } }); }, { runtimeLint: true }); @@ -407,10 +411,12 @@ You can use option `production` to render whole BEMJSON even if one template con ```js var template = bemxjst.compile(function() { - block('b1').attrs()(function() { - var attrs = applyNext(); - attrs.undef.foo = 'bar'; - return attrs; + block('b1')({ + attrs: function() { + var attrs = applyNext(); + attrs.undef.foo = 'bar'; + return attrs; + } }); }, { production: true }); var html = template.apply({ block: 'page', content: { block: 'b1' } }); @@ -472,10 +478,12 @@ For example: `lib-name` module will be accessible in templates body like this: ```js -block('button').content()(function () { - var lib = this.require('lib-name'); +block('button')({ + content: function () { + var lib = this.require('lib-name'); - return lib.hello(); + return lib.hello(); + } }); ``` @@ -511,11 +519,13 @@ In templates body the module will be acessible as `this.require('moment')`. You can use the template in any browser or in `Node.js`: ```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'); +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'); + } }); ``` @@ -534,8 +544,10 @@ templates.BEMContext.prototype.hi = function(name) { // Add templates templates.compile(function() { - block('b').content()(function() { - return this.hi('templates'); + block('b')({ + content: function() { + return this.hi('templates'); + } }); }); diff --git a/docs/en/5-templates-syntax.md b/docs/en/5-templates-syntax.md index 32aa777e..7ad334e8 100644 --- a/docs/en/5-templates-syntax.md +++ b/docs/en/5-templates-syntax.md @@ -87,8 +87,8 @@ Templates are applied on the node, both to the block and to the corresponding mo Templates: ```js -block('page').tag()('body'); -block('page').mod('type', 'index').mix()({ block: 'mixed' }); +block('page')({ tag: 'body' }); +block('page').mod('type', 'index')({ mix: { block: 'mixed' } }); ``` Both templates are applied. @@ -117,7 +117,7 @@ Template: ```js block('item') .mod('size', '1') // Notice that value is String - .tag()('small'); + ({ tag: 'small' }); ``` The template are applied. @@ -132,7 +132,7 @@ If second argument of `mod()` was omited then templates with any non-empty value of modifier will be applied. ```js -block('a').mod('size').tag()('span'); +block('a').mod('size')({ tag: 'span' }); ``` Template will be applied to BEMJSON node if block equals to 'a' and 'size' modifier exists (equals neither to `undefined` nor to `''` nor to `false` nor to `null`). @@ -174,8 +174,8 @@ Templates are applied on the node, both to the element and to the corresponding Templates: ```js -block('page').elem('content').tag()('body'); -block('page').elem('content').elemMod('type', 'index').mix()({ block: 'mixed' }); +block('page').elem('content')({ tag: 'body' }); +block('page').elem('content').elemMod('type', 'index')({ mix: { block: 'mixed' } }); ``` Both templates are applied. @@ -299,6 +299,13 @@ block('b').content('test'); block('b').content()('test'); ``` +Much more easy to use object-like shortcut syntax for modes: + +```js +// Object-like shortcut syntax: +block('b')({ content: 'test' }); +``` + For input data: ```js @@ -308,12 +315,12 @@ For input data: And for the template: ```js -block('link')( - tag()('a'), - attrs()(function() { +block('link')({ + tag: 'a', + attrs: function() { return { href: this.ctx.url }; - }) -); + } +}); ``` *Result of templating:* @@ -345,12 +352,12 @@ block('link')( /** * @param {function|Array|Object[]} value */ -def()(value) +default: value ``` -The `def` mode (short for "default") has a special status. It is responsible for generating the result as a whole. This mode defines the list of other modes and the order to go through them, as well as the build procedure for getting the final representation of the HTML element or BEMJSON from the parts generated in the other modes. +The `defautl` mode has a special status. It is responsible for generating the result as a whole. This mode defines the list of other modes and the order to go through them, as well as the build procedure for getting the final representation of the HTML element or BEMJSON from the parts generated in the other modes. -This is a special mode that shouldn’t be used unless truly necessary. A user-defined template that redefines `def` disables calls of the other modes by default. +This is a special mode that shouldn’t be used unless truly necessary. A user-defined template that redefines `defautl` disables calls of the other modes by default. #### tag @@ -358,7 +365,7 @@ This is a special mode that shouldn’t be used unless truly necessary. A user-d /** * @param {Function|String} name */ -tag()(name) +tag: name ``` HTML tag. `false` or `''` tells the BEMHTML engine to skip the HTML tag generation stage. Default: `div`. @@ -369,19 +376,19 @@ HTML tag. `false` or `''` tells the BEMHTML engine to skip the HTML tag generati /** * @param {function|Object} value */ -attrs()(value) +attrs: value ``` Hash with HTML attributes. The attribute values [are escaped using the attrEscape function](6-templates-context.md#attrescape). -You can use `addAttrs()` mode to add attributes. `addAttrs()` is shortcut of `attrs()` mode: +You can use `addAttrs` mode to add attributes. `addAttrs` is shortcut of `attrs` mode: ```js -addAttrs()({ id: 'test', name: 'test' }); +addAttrs: { id: 'test', name: 'test' } // This is equivalent to following: -attrs()(function() { +attrs: function() { var attrs = applyNext() || {}; // Get attrs from previous templates return this.extend(attrs, { id: 'test', name: 'test' }); -}); +} ``` #### content @@ -390,7 +397,7 @@ attrs()(function() { /** * @param {*} value */ -content()(value) +content: value ``` Child nodes. By default, it is taken from the `content` of the current BEMJSON node. @@ -399,11 +406,11 @@ You can use `appendContent` and `prependContent` modes to add child nodes to content. ```js -block('quote')( - prependContent()('“'), // add some things before actual content - appendContent()('”'), // add content to the end - appendContent()({ block: 'link' }) // add more content to the end -); +block('quote')({ + prependContent: '“', // add some things before actual content + appendContent: '”', // add content to the end + appendContent: { block: 'link' } // add more content to the end +}) ``` ```js { block: 'quote', content: 'I came, I saw, I templated.' } @@ -415,24 +422,24 @@ block('quote')(
“I came, I saw, I templated.”
``` -`appendContent()` and `prependContent()` is a shortcuts to `content()` + `applyNext()`: +`appendContent` and `prependContent` is a shortcuts to `content` + `applyNext()`: ```js -// appendContent()('additional content') is the same as: -content()(function() { +// appendContent: 'additional content' is the same as: +content: function() { return [ applyNext(), 'additional content' ]; -}); +} -// prependContent()('additional content') is the same as: -content()(function() { +// prependContent: 'additional content' is the same as: +content: function() { return [ 'additional content', applyNext() ]; -}); +} ``` #### mix @@ -441,7 +448,7 @@ content()(function() { /** * @param {function|Object|Object[]|String} mixed */ -mix()(mixed) +mix: mixed ``` BEM entities to [mix](https://en.bem.info/method/key-concepts/#mix) with the current one. @@ -449,19 +456,19 @@ BEM entities to [mix](https://en.bem.info/method/key-concepts/#mix) with the cur Usage example: ```js -block('link').mix()({ block: 'mixed' }); -block('button').mix()([ { block: 'mixed' }, { block: 'control' } ]); -block('header').mix()(function() { return { block: 'mixed' }; }); +block('link')({ mix: { block: 'mixed' } }); +block('button')({ mix: [ { block: 'mixed' }, { block: 'control' } ] }); +block('header')({ mix: function() { return { block: 'mixed' }; } }); ``` -You can use `addMix()` mode to add mix. `addMix()` is shortcut of `mix()`: +You can use `addMix` mode to add mix. `addMix` is shortcut of `mix`: ```js -addMix()('my_new_mix'); // This is equivalent to following: -mix()(function() { +addMix: 'my_new_mix' // This is equivalent to following: +mix: function() { var mixes = applyNext(); if (!Array.isArray(mixes)) mixes = [ mixes ]; return mixes.concat('my_new_mix'); -}); +} ``` #### mods @@ -470,7 +477,7 @@ mix()(function() { /** * @param {function|Object} mods */ -mods()(mods) +mods: mods ``` Hash for modifiers of block. @@ -478,11 +485,11 @@ Hash for modifiers of block. **Example** ```js -block('link').mods()({ type: 'download' }); -block('link').mods()(function() { return { type: 'download' }; }); +block('link')({ mods: { type: 'download' } }); +block('link')({ mods: function() { return { type: 'download' }; } }); ``` -Value from `mods()` mode rewrite value from BEMJSON. +Value from `mods` mode rewrite value from BEMJSON. By default returns `this.mods`. @@ -491,20 +498,22 @@ By default returns `this.mods`. { block: 'b' } // Template: -block('b').def()(function() { - return apply('mods'); +block('b')({ + default: function() { + return apply('mods'); + } }); ``` The result is `{}`. -You can use `addMods()` mode to add modifiers. `addMods()` is shortcut of `mods()`: +You can use `addMods` mode to add modifiers. `addMods` is shortcut of `mods`: ```js -addMods()({ theme: 'dark' }); // This is equivalent to following: -mods()(function() { +addMods: { theme: 'dark' } // This is equivalent to following: +mods: function() { this.mods = this.extend(applyNext(), { theme: 'dark' }); return this.mods; -}); +} ``` #### elemMods @@ -513,7 +522,7 @@ mods()(function() { /** * @param {function|Object} elemMods */ -elemMods()(elemMods) +elemMods: elemMods ``` Hash for modifiers of element. @@ -521,11 +530,11 @@ Hash for modifiers of element. **Example** ```js -block('link').elemMods()({ type: 'download' }); -block('link').elemMods()(function() { return { type: 'download' }; }); +block('link')({ elemMods: { type: 'download' } }); +block('link')({ elemMods: function() { return { type: 'download' }; } }); ``` -Value from `elemMods()` mode rewrite value from BEMJSON. +Value from `elemMods` mode rewrite value from BEMJSON. By default returns `this.mods`. @@ -534,8 +543,10 @@ By default returns `this.mods`. { block: 'b', elem: 'e' } // Template: -block('b').elem('e').def()(function() { - return apply('mods'); +block('b').elem('e')({ + default: function() { + return apply('mods'); + } }); ``` @@ -545,11 +556,11 @@ You can use addElemMods mode to add modifiers for element. addElemMods is shortcut of elemMods: ```js -addElemMods()({ theme: 'dark' }); // This is equivalent to following: -elemMods()(function() { +addElemMods: { theme: 'dark' } // This is equivalent to following: +elemMods: function() { this.elemMods = this.extend(applyNext(), { theme: 'dark' }); return this.elemMods; -}); +} ``` #### js @@ -558,7 +569,7 @@ elemMods()(function() { /** * @param {function|Boolean|Object} value */ -js()(value) +js: value ``` JavaScript parameters. If the value isn’t falsy, it mixes `i-bem` and adds the @@ -570,7 +581,7 @@ content to Javacript parameters. More information about [i-bem and JavaScript pa /** * @param {function|Boolean} value */ -bem()(value) +bem: value ``` Tells the template engine whether to add classes and JavaScript parameters for the BEM entity and its mixes. Default: `true`. @@ -581,7 +592,7 @@ Tells the template engine whether to add classes and JavaScript parameters for t /** * @param {function|String} value */ -cls()(value) +cls: value ``` Adds an HTML class unrelated to the BEM subject domain. @@ -600,8 +611,8 @@ For replacing the current node (matching the node and rendering some other entit Templates: ```js -block('link').tag()('a'); -block('resource').replace()({ block: 'link' }); +block('link')({ tag: 'a' }); +block('resource')({ replace: { block: 'link' } }); ``` *Result of templating:* @@ -629,11 +640,13 @@ Wrap the current node in additional markup. Template: ```js -block('quote').wrap()(function() { - return { - block: 'wrap', - content: this.ctx - }; +block('quote')({ + wrap: function() { + return { + block: 'wrap', + content: this.ctx + }; + } }); ``` @@ -657,9 +670,11 @@ block('quote').wrap()(function() { Templates: ```js -block('action').extend()({ 'ctx.type': 'Sale', sale: '50%' }); -block('action').content()(function() { - return this.ctx.type + ' ' + this.sale; +block('action')({ + extend: { 'ctx.type': 'Sale', sale: '50%' }, + content: function() { + return this.ctx.type + ' ' + this.sale; + } }); ``` @@ -669,14 +684,14 @@ block('action').content()(function() {
Sale 50%
``` -`extend()` may used as a data proxy to all child nodes. +`extend` may used as a data proxy to all child nodes. **Example** ```js // Templates -block('page').extend()({ meaning: 42 }); -block('*').attrs()(function() { return { life: this.meaning }; }); +block('page')({ extend: { meaning: 42 } }); +block('*')({ attrs: function() { return { life: this.meaning }; } }); ``` ```js @@ -705,25 +720,27 @@ Template: ```js block('control')( - mode('id')('username-control'), // User-defined mode named "id" - content()(function() { - return [ - { - elem: 'label', - attrs: { for: apply('id') } // Calling the user-defined mode - }, - { - elem: 'input', - attrs: { - name: this.ctx.name, - value: this.ctx.value, - id: apply('id'), // Calling the user-defined mode + { + id: 'username-control', // User-defined mode named "id" + content: function() { + return [ + { + elem: 'label', + attrs: { for: apply('id') } // Calling the user-defined mode + }, + { + elem: 'input', + attrs: { + name: this.ctx.name, + value: this.ctx.value, + id: apply('id') // Calling the user-defined mode + } } - } - ]; - }), - elem('input').tag()('input'), - elem('label').tag()('label') + ]; + }, + }, + elem('input')({ tag: 'input' }), + elem('label')({ tag: 'label' }) ); ``` diff --git a/docs/en/6-templates-context.md b/docs/en/6-templates-context.md index c6480a08..abc65a02 100644 --- a/docs/en/6-templates-context.md +++ b/docs/en/6-templates-context.md @@ -40,7 +40,7 @@ block('page').match(function() { // Sufficient: return this.mods.type === 'index' && this.ctx.weather; -}).def()(function() { return … }); +})({ default: function() { return … } }); ``` ## Current BEMJSON node @@ -55,11 +55,13 @@ The current BEMJSON node is available in the `this.ctx` field. ``` ```js -block('link').attr()(function() { - return { - id: this.ctx.name, - name: this.ctx.name - }; +block('link')({ + attrs: function() { + return { + id: this.ctx.name, + name: this.ctx.name + }; + } }); ``` @@ -94,8 +96,10 @@ Usage example: Template: ```js -block('button').def()(function() { - return this.xmlEscape('&'); +block('button')({ + default: function() { + return this.xmlEscape('&'); + } }); ``` @@ -169,13 +173,13 @@ The BEM tree may be filled in as templates are executing, by using templates in 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('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. @@ -220,23 +224,25 @@ Usage example: Template ```js -block('input').content()(function() { - var id = this.generateId(); - - return [ - { - tag: 'label', - attrs: { for: id }, - content: this.ctx.label - }, - { - tag: 'input', - attrs: { - id: id, - value: this.ctx.value +block('input')({ + content: function() { + var id = this.generateId(); + + return [ + { + tag: 'label', + attrs: { for: id }, + content: this.ctx.label + }, + { + tag: 'input', + attrs: { + id: id, + value: this.ctx.value + } } - } - ]; + ]; + } }); ``` @@ -268,10 +274,12 @@ BEMJSON: Template: ```js -block('a').js()(function() { - return { - template: this.reapply({ block: 'b', mods: { m: 'v' } }) - }; +block('a')({ + js: function() { + return { + template: this.reapply({ block: 'b', mods: { m: 'v' } }) + }; + } }); ``` @@ -300,8 +308,10 @@ var templates = bemxjst.bemhtml.compile(function() { }; }); - block('b').content()(function() { - return this.hi('username'); + block('b')({ + content: function() { + return this.hi('username'); + } }); }); @@ -330,15 +340,17 @@ templates.BEMContext.prototype.hi = function(name) { // Adding templates templates.compile(function() { - block('b').content()(function() { - return this.hi('templates'); + block('b')({ + content: function() { + return this.hi('templates'); + } }); }); var bemjson = { block: 'b' }; // Applying templates -var html = templates.apply(bemjson)); +var html = templates.apply(bemjson); ``` As a result, `html` contains the string: @@ -361,14 +373,16 @@ When body of template is function, it calls with two arguments: **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 - }; +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 + }; + } }); ``` @@ -386,8 +400,8 @@ match(function(node, ctx) { Moreover, template functions can be arrow functions: ```js -match((node, ctx) => (!node.mods.disabled && ctx.target)) -addAttrs()((node, ctx) => ({ href: ctx.url })) +match((node, ctx) => ctx.target) +addAttrs: (node, ctx) => { href: ctx.url } ``` Read next: [runtime](7-runtime.md) diff --git a/docs/en/7-runtime.md b/docs/en/7-runtime.md index fc3d6432..18ab1841 100644 --- a/docs/en/7-runtime.md +++ b/docs/en/7-runtime.md @@ -44,8 +44,10 @@ block('*') .match(function() { return this.ctx.counter; }) - .mix()(function() { - return { block: 'counter', js: { id: this.ctx.counter } } + ({ + mix: function() { + return { block: 'counter', js: { id: this.ctx.counter } } + } }) ``` @@ -84,17 +86,17 @@ Used for calling a standard or user-defined mode of the current node. Template: ```js -block('button')( - mode('test')(function() { +block('button')({ + test: function() { return this.tmp + this.ctx.foo; - }), - def()(function() { + }, + default: function() { return apply('test', { tmp: 'ping', 'ctx.foo': 'pong' }); - }) -); + } +}); ``` *Result of templating:* @@ -116,13 +118,15 @@ You can’t use `apply` to call user-defined modes for other blocks. Template: ```js -block('footer').mode('custom')('footer'); -block('header').mode('custom')('header'); -block('header').tag()(function() { - // despite the fact that the second 'apply' argument explicitly - // specifies the 'footer' block, - // the user-defined mode of the 'header' block will be called. - return apply('custom', { block: 'footer' }); +block('footer')({ custom: 'footer' }); +block('header')({ + custom: 'header', + tag: function() { + // despite the fact that the second 'apply' argument explicitly + // specifies the 'footer' block, + // the user-defined mode of the 'header' block will be called. + return apply('custom', { block: 'footer' }); + } }); ``` @@ -143,8 +147,10 @@ value of the corresponding field from current BEMJSON node. Template: ```js -block('animal').content()(function() { - return apply('type'); +block('animal')({ + content: function() { + return apply('type'); + } }); ``` @@ -170,10 +176,12 @@ The `applyNext` construction returns the result of the next highest priority tem **Example** ```js -block('link').tag()('a'); -block('link').tag()(function() { - var res = applyNext(); // res === 'a' - return res; +block('link')({ tag: 'a' }); +block('link')({ + tag: function() { + var res = applyNext(); // res === 'a' + return res; + } }); ``` @@ -197,11 +205,13 @@ Use the `applyCtx` construction for modifying the current fragment of the BEM tr Template: ```js -block('header').def()(function() { - return applyCtx(this.extend(this.ctx, { - block: 'layout', - mix: [{ block: 'header' }].concat(this.ctx.mix || []) - })); +block('header')({ + default: function() { + return applyCtx(this.extend(this.ctx, { + block: 'layout', + mix: [{ block: 'header' }].concat(this.ctx.mix || []) + })); + } }); ``` diff --git a/docs/ru/2-quick-start.md b/docs/ru/2-quick-start.md index 783581b6..30d3c6d2 100644 --- a/docs/ru/2-quick-start.md +++ b/docs/ru/2-quick-start.md @@ -21,7 +21,7 @@ var bemhtml = bemxjst.bemhtml; // Добавляем шаблоны с помощью метода compile var templates = bemhtml.compile(function() { - block('text').tag()('span'); + block('text')({ tag: 'span' }); }); // Добавляем данные в формате BEMJSON diff --git a/docs/ru/3-api.md b/docs/ru/3-api.md index 4e886436..4562a60e 100644 --- a/docs/ru/3-api.md +++ b/docs/ru/3-api.md @@ -26,7 +26,7 @@ var bemhtml = bemxjst.bemhtml; // Добавляем шаблон var templates = bemhtml.compile(function() { - block('quote').tag()('q'); + block('quote')({ tag: 'q' }); }); // Добавляем данные @@ -50,13 +50,17 @@ var bemtree = bemxjst.bemtree; // Добавляем шаблон var templates = bemtree.compile(function() { - block('phone').content()({ mask: '8-800-×××-××-××', mandatory: true }); + block('phone')({ + content: { mask: '8-800-×××-××-××', mandatory: true } + }); - block('page').content()([ - { block: 'header' }, - { block: 'body' }, - { block: 'footer' } - ]); + block('page')({ + content: [ + { block: 'header' }, + { block: 'body' }, + { block: 'footer' } + ] + }); }); // Добавляем данные @@ -93,7 +97,7 @@ var bemxjst = require('bem-xjst'); // Создаём экземпляр класса templates var templates = bemxjst.bemhtml.compile(function() { - block('header').tag()('h1'); + block('header')({ tag: 'h1' }); }); // Добавляем данные @@ -104,7 +108,7 @@ var html = templates.apply(bemjson); // Добавляем шаблоны к уже созданному экземпляру класса templates templates.compile(function() { - block('header').tag()('h2'); + block('header')({ tag: 'h2' }); }); html = templates.apply(bemjson); @@ -348,11 +352,13 @@ var bemxjst = require('bem-xjst'); var bemhtml = bemxjst.bemhtml; var templates = bemhtml.compile(function() { - block('b').content()('yay'); + block('b')({ content: 'yay' }); - block('mods-changes').def()(function() { - this.ctx.mods.one = 2; - return applyNext(); + block('mods-changes')({ + default: function() { + this.ctx.mods.one = 2; + return applyNext(); + } }); }, { runtimeLint: true }); @@ -394,10 +400,12 @@ Notice that you should change this.mods instead of this.ctx.mods in templates ```js var template = bemxjst.compile(function() { - block('b1').attrs()(function() { - var attrs = applyNext(); - attrs.undef.foo = 'bar'; - return attrs; + block('b1')({ + attrs: function() { + var attrs = applyNext(); + attrs.undef.foo = 'bar'; + return attrs; + } }); }, { production: true }); var html = template.apply({ block: 'page', content: { block: 'b1' } }); @@ -465,10 +473,11 @@ templates.BEMContext.prototype.onError = function(context, err) { … }; В шаблонах модули будут доступны с помощью метода `this.require`, например: ```js -block('button').content()(function () { - var lib = this.require('lib-name'); - - return lib.hello(); +block('button')({ + content: function() { + var lib = this.require('lib-name'); + return lib.hello(); + } }); ``` @@ -494,7 +503,7 @@ block('button').content()(function () { { requires: { moment: { - commonJS: 'moment', // Путь к модулю CommonJS относительно собираемого файла + commonJS: 'moment' // Путь к модулю CommonJS относительно собираемого файла } } } @@ -503,11 +512,12 @@ block('button').content()(function () { В шаблонах модуль будет доступен с помощью метода `this.require('moment')`. Код шаблона пишется один раз, одинаково для исполнения в браузере и в `Node.js`: ```js -block('post').elem('data').content()(function() { - var moment = this.require('moment'); // Библиотека `moment` - - return moment(this.ctx.date) // Время в ms, полученное с сервера - .format('YYYY-MM-DD HH:mm:ss'); +block('post').elem('data')({ + content: function() { + var moment = this.require('moment'); // Библиотека `moment` + return moment(this.ctx.date) // Время в ms, полученное с сервера + .format('YYYY-MM-DD HH:mm:ss'); + } }); ``` @@ -526,8 +536,10 @@ templates.BEMContext.prototype.hi = function(name) { // Добавляем шаблоны templates.compile(function() { - block('b').content()(function() { - return this.hi('templates'); + block('b')({ + content: function() { + return this.hi('templates'); + } }); }); diff --git a/docs/ru/5-templates-syntax.md b/docs/ru/5-templates-syntax.md index cf9b8a70..fffc7143 100644 --- a/docs/ru/5-templates-syntax.md +++ b/docs/ru/5-templates-syntax.md @@ -84,8 +84,8 @@ mod(modName, modVal) Шаблоны: ```js -block('page').tag()('body'); -block('page').mod('type', 'index').mix()({ block: 'mixed' }); +block('page')({ tag: 'body' }); +block('page').mod('type', 'index')({ mix: { block: 'mixed' } }); ``` Оба шаблона будут применены. @@ -114,7 +114,7 @@ block('page').mod('type', 'index').mix()({ block: 'mixed' }); ```js block('item') .mod('size', '1') // Здесь тип значения модификатора — String - .tag()('small'); + ({ tag: 'small' }); ``` Шаблон будет применен, так как bem-xjst проверит значения `modVal` на соответствие после приведения их к строке. @@ -129,7 +129,7 @@ block('item') применены шаблоны для соответствующего модификатора с любым значением. ```js -block('a').mod('size').tag()('span'); +block('a').mod('size')({ tag: 'span' }); ``` Шаблон будет применен к узлам BEMJSON-а, у которых блок равен 'a' и присутствует модификатор 'size' (со значением отличным от `undefined`, `''`, @@ -172,8 +172,8 @@ elemMod(elemModName, elemModVal) Шаблоны: ```js -block('page').elem('content').tag()('body'); -block('page').elem('content').elemMod('type', 'index').mix()({ block: 'mixed' }); +block('page').elem('content')({ tag: 'body' }); +block('page').elem('content').elemMod('type', 'index')({ mix: { block: 'mixed' } }); ``` Оба шаблона будут применены. @@ -296,6 +296,14 @@ block('b').content('test'); block('b').content()('test'); ``` +Удобнее всего использовать соркащенный синтаксис и задавать значения каждого режима в виде объекта, где ключами будут +имена режимов, а значениями тело шаблона: + +```js +// Сокращенный синтаксис: +block('b')({ content: 'test' }); +``` + Для входных данных: ```js @@ -305,12 +313,12 @@ block('b').content()('test'); И шаблона: ```js -block('link')( - tag()('a'), - attrs()(function() { +block('link')({ + tag: 'a', + attrs: function() { return { href: this.ctx.url }; - }) -); + } +}); ``` *Результат шаблонизации:* @@ -342,12 +350,12 @@ block('link')( /** * @param {*} value */ -def()(value) +default: value ``` -Особый статус имеет режим `def` (сокращение от default), который отвечает за генерацию результата в целом. В рамках этого режима задан набор и порядок прохождения остальных режимов, а также определена процедура сборки финального представления HTML-элемента или BEMJSON из фрагментов, сгенерированных в остальных режимах. +Особый статус имеет режим `default`, который отвечает за генерацию результата в целом. В рамках этого режима задан набор и порядок прохождения остальных режимов, а также определена процедура сборки финального представления HTML-элемента или BEMJSON из фрагментов, сгенерированных в остальных режимах. -Режим является особым и не стоит использовать его без особой надобности. Пользовательский шаблон, переопределяющий `def`, отключает вызовы остальных режимов по умолчанию. +Режим является особым и не стоит использовать его без особой надобности. Пользовательский шаблон, переопределяющий `default`, отключает вызовы остальных режимов по умолчанию. #### tag @@ -355,7 +363,7 @@ def()(value) /** * @param {Function|String} name */ -tag()(name) +tag: name ``` HTML-тег. `false` или `''` укажет движку BEMHTML пропустить этап генерации HTML-тега. По умолчанию `div`. @@ -366,19 +374,19 @@ HTML-тег. `false` или `''` укажет движку BEMHTML пропус /** * @param {function|Object} value */ -attrs()(value) +attrs: value ``` Хеш с HTML-атрибутами. Значения атрибутов [будут экранированны функцией attrEscape](6-templates-context.md#attrescape). -Для того, чтобы добавить `attrs`, вы можете использовать режим `addAttrs()`, который -является сокращением режима `attrs()` и выглядит более лаконично: +Для того, чтобы добавить `attrs`, вы можете использовать режим `addAttrs`, который +является сокращением режима `attrs` и выглядит более лаконично: ```js -addAttrs()({ id: 'test', name: 'test' }); // Это полностью эквивалентно следующему: -attrs()(function() { +addAttrs: { id: 'test', name: 'test' } // Это полностью эквивалентно следующему: +attrs: function() { var attrs = applyNext() || {}; // атрибуты из предыдущих шаблонов или BEMJSON-а return this.extend(attrs, { id: 'test', name: 'test' }); -}); +} ``` #### content @@ -387,7 +395,7 @@ attrs()(function() { /** * @param {*} value */ -content()(value) +content: value ``` Дочерние узлы. По умолчанию будет взято из поля `content` текущего узла BEMJSON. @@ -397,9 +405,13 @@ content()(value) ```js block('quote')( - prependContent()('«'), - appendContent()('»'), - appendContent()({ block: 'link' }) + { + prependContent: '«', + appendContent: '»' + }, + { + appendContent: function() { return { block: 'link' }; } + } ); ``` @@ -413,24 +425,24 @@ block('quote')(
«Пришел, увидел, отшаблонизировал»
``` -`appendContent()` и `prependContent()` это синтаксический сахар над `content()` + `applyNext()`: +`appendContent` и `prependContent` это синтаксический сахар над `content` + `applyNext`: ```js -// appendContent()('еще') тоже самое что и: -content()(function() { +// appendContent: 'еще' тоже самое что и: +content: function() { return [ applyNext(), 'еще' ]; -}); +} -// prependContent()('еще') тоже самое что и: -content()(function() { +// prependContent: 'еще' тоже самое что и: +content: function() { return [ 'еще', applyNext() ]; -}); +} ``` #### mix @@ -439,7 +451,7 @@ content()(function() { /** * @param {function|Object|Object[]|String} mixed */ -mix()(mixed) +mix: mixed ``` БЭМ-сущности, которые нужно [примиксовать](https://ru.bem.info/method/key-concepts/#Микс) к текущей. @@ -447,20 +459,20 @@ mix()(mixed) Пример использования: ```js -block('link').mix()({ block: 'mixed' }); -block('button').mix()([ { block: 'mixed' }, { block: 'control' } ]); -block('header').mix()(function() { return { block: 'mixed' }; }); +block('link')({ mix: { block: 'mixed' } }); +block('button')({ mix: [ { block: 'mixed' }, { block: 'control' } ] }); +block('header')({ mix: function() { return { block: 'mixed' }; } }); ``` -Для того, чтобы добавить `mix`, вы можете использовать режим `addMix()`, который -является сокращением режима `mix()` и выглядит более лаконично: +Для того, чтобы добавить `mix`, вы можете использовать режим `addMix`, который +является сокращением режима `mix` и выглядит более лаконично: ```js -addMix()('my-new-mix'); // Это полностью эквивалентно следующему: -mix()(function() { +addMix: 'my-new-mix' // Это полностью эквивалентно следующему: +mix: function() { var mixes = applyNext(); if (!Array.isArray(mixes)) mixes = [ mixes ]; return mixes.concat('my-new-mix'); -}); +} ``` #### mods @@ -469,7 +481,7 @@ mix()(function() { /** * @param {function|Object} mods */ -mods()(mods) +mods: mods ``` Хеш модификаторов блока. @@ -477,11 +489,11 @@ mods()(mods) Пример использования: ```js -block('link').mods()({ type: 'download' }); -block('link').mods()(function() { return { type: 'download' }; }); +block('link')({ mods: { type: 'download' } }); +block('link')({ mods: function() { return { type: 'download' }; } }); ``` -Значение, вычисленное в режиме `mods()`, переопределит значение, указанное в BEMJSON-е. +Значение, вычисленное в режиме `mods`, переопределит значение, указанное в BEMJSON-е. По умолчанию возвращает значение `this.mods`. @@ -490,8 +502,10 @@ block('link').mods()(function() { return { type: 'download' }; }); { block: 'b' } // Шаблон: -block('b').def()(function() { - return apply('mods'); +block('b')({ + default: function() { + return apply('mods'); + } }); ``` @@ -500,11 +514,11 @@ block('b').def()(function() { Для того, чтобы добавить модификаторы, вы должны использовать режим addMods, который является сокращением режима mods и выглядит более лаконично: ```js -addMods()({ theme: 'dark' }); // Это полностью эквивалентно следующему: -mods()(function() { +addMods: { theme: 'dark' } // Это полностью эквивалентно следующему: +mods: function() { this.mods = this.extend(applyNext(), { theme: 'dark' }); return this.mods; -}); +} ``` #### elemMods @@ -513,7 +527,7 @@ mods()(function() { /** * @param {function|Object} elemMods */ -elemMods()(elemMods) +elemMods: elemMods ``` Хеш модификаторов элемента. @@ -521,11 +535,11 @@ elemMods()(elemMods) **Пример** ```js -block('link').elemMods()({ type: 'download' }); -block('link').elemMods()(function() { return { type: 'download' }; }); +block('link')({ elemMods: { type: 'download' } }); +block('link')({ elemMods: function() { return { type: 'download' }; } }); ``` -Значение, вычисленное в режиме `elemMods()`, переопределит значение, указанное в BEMJSON-е. +Значение, вычисленное в режиме `elemMods`, переопределит значение, указанное в BEMJSON-е. По умолчанию возвращает значение `this.elemMods`. @@ -534,8 +548,10 @@ block('link').elemMods()(function() { return { type: 'download' }; }); { block: 'b', elem: 'e' } // Шаблон: -block('b').elem('e').def()(function() { - return apply('elemMods'); +block('b').elem('e')({ + default: function() { + return apply('elemMods'); + } }); ``` @@ -545,11 +561,11 @@ block('b').elem('e').def()(function() { является сокращением режима elemMods и выглядит более лаконично: ```js -addElemMods()({ theme: 'dark' }); // Это полностью эквивалентно следующему: -elemMods()(function() { +addElemMods: { theme: 'dark' } // Это полностью эквивалентно следующему: +elemMods: function() { this.elemMods = this.extend(applyNext(), { theme: 'dark' }); return this.elemMods; - }); +} ``` #### js @@ -558,7 +574,7 @@ elemMods()(function() { /** * @param {function|Boolean|Object} value */ -js()(value) +js: value ``` JS-параметры. Если значение не falsy, то миксует `i-bem` и добавляет содержимое в JS-параметры. Подробнее про [i-bem и JS-параметры](https://ru.bem.info/platform/i-bem/parameters/). Данные [будут экранированны функцией jsAttrEscape](6-templates-context.md#jsattrescape). @@ -569,7 +585,7 @@ JS-параметры. Если значение не falsy, то миксует /** * @param {function|Boolean} value */ -bem()(value) +bem: value ``` Указывает шаблонизатору, нужно ли добавлять классы и JS-параметры для самой БЭМ-сущности и её миксов. По умолчанию `true`. @@ -580,7 +596,7 @@ bem()(value) /** * @param {function|String} value */ -cls()(value) +cls: value ``` Добавить произвольный HTML-класс, не относящийся к предметной области БЭМ. @@ -599,8 +615,8 @@ cls()(value) Шаблоны: ```js -block('link').tag()('a'); -block('resource').replace()({ block: 'link' }); +block('link')({ tag: 'a' }); +block('resource')({ replace: { block: 'link' } }); ``` *Результат шаблонизации:* @@ -628,11 +644,13 @@ block('resource').replace()({ block: 'link' }); Шаблон: ```js -block('quote').wrap()(function() { - return { - block: 'wrap', - content: this.ctx - }; +block('quote')({ + wrap: function() { + return { + block: 'wrap', + content: this.ctx + }; + } }); ``` @@ -658,9 +676,13 @@ block('quote').wrap()(function() { Шаблоны: ```js -block('action').extend()({ 'ctx.type': 'Sale', sale: '50%' }); -block('action').content()(function() { - return this.ctx.type + ' ' + this.sale; +block('action')({ + extend: { 'ctx.type': 'Sale', sale: '50%' } +}); +block('action')({ + content: function() { + return this.ctx.type + ' ' + this.sale; + } }); ``` @@ -670,14 +692,14 @@ block('action').content()(function() {
Sale 50%
``` -`extend()` может использоваться для прокидывания данных во все дочерние узлы +`extend` может использоваться для прокидывания данных во все дочерние узлы через контекст выполнения шаблонов. **Пример** ```js -block('page').extend()({ meaning: 42 }); -block('*').attrs()(function() { return { life: this.meaning }; }); +block('page')({ extend: { meaning: 42 } }); +block('*')({ attrs: function() { return { life: this.meaning }; } }); ``` ```js @@ -706,25 +728,27 @@ life="42"> ```js block('control')( - mode('id')('username-control'), // Пользовательский режим с именем id - content()(function() { - return [ - { - elem: 'label', - attrs: { for: apply('id') } // Вызов пользовательского режима - }, - { - elem: 'input', - attrs: { - name: this.ctx.name, - value: this.ctx.value, - id: apply('id'), // Вызов пользовательского режима + { + id: 'username-control', // Пользовательский режим с именем id + content: function() { + return [ + { + elem: 'label', + attrs: { for: apply('id') } // Вызов пользовательского режима + }, + { + elem: 'input', + attrs: { + name: this.ctx.name, + value: this.ctx.value, + id: apply('id') // Вызов пользовательского режима + } } - } - ]; - }), - elem('input').tag()('input'), - elem('label').tag()('label') + ]; + } + }, + elem('input')({ tag: 'input' }), + elem('label')({ tag: 'label' }) ); ``` diff --git a/docs/ru/6-templates-context.md b/docs/ru/6-templates-context.md index f0b0c1ec..2236a786 100644 --- a/docs/ru/6-templates-context.md +++ b/docs/ru/6-templates-context.md @@ -39,7 +39,7 @@ block('page').match(function() { // Достаточно: return this.mods.type === 'index' && this.ctx.weather; -}).def()(function() { return … }); +})({ default: function() { return … } }); ``` ## Текущий узел BEMJSON @@ -54,11 +54,13 @@ block('page').match(function() { ``` ```js -block('company').attr()(function() { - return { - id: this.ctx.name, - name: this.ctx.name - }; +block('company')({ + attrs: function() { + return { + id: this.ctx.name, + name: this.ctx.name + }; + } }); ``` @@ -93,8 +95,10 @@ this.xmlEscape(str) Шаблон: ```js -block('button').def()(function() { - return this.xmlEscape('&'); +block('button')({ + default: function() { + return this.xmlEscape('&'); + } }); ``` @@ -168,13 +172,13 @@ this.jsAttrEscape(str) Функция определения последней БЭМ-сущности среди соседей `isLast` возвратит `false`, если в массиве, содержащем узлы, последний элемент не является БЭМ-сущностью. ```js -block('list')( - content()([ +block('list')({ + content: [ { block: 'item1' }, { block: 'item2' }, // this.isLast() === false 'text' - ]) -); + ] +}); ``` Такое поведение объясняется тем, что в целях оптимизации BEMHTML не выполняет @@ -221,23 +225,25 @@ this.isLast() Шаблон: ```js -block('input').content()(function() { - var id = this.generateId(); - - return [ - { - tag: 'label', - attrs: { for: id }, - content: this.ctx.label - }, - { - tag: 'input', - attrs: { - id: id, - value: this.ctx.value +block('input')({ + content: function() { + var id = this.generateId(); + + return [ + { + tag: 'label', + attrs: { for: id }, + content: this.ctx.label + }, + { + tag: 'input', + attrs: { + id: id, + value: this.ctx.value + } } - } - ]; + ]; + } }); ``` @@ -269,10 +275,12 @@ BEMJSON: Шаблон: ```js -block('a').js()(function() { - return { - template: this.reapply({ block: 'b', mods: { m: 'v' } }) - }; +block('a')({ + js: function() { + return { + template: this.reapply({ block: 'b', mods: { m: 'v' } }) + }; + } }); ``` @@ -301,8 +309,10 @@ var templates = bemxjst.bemhtml.compile(function() { }; }); - block('b').content()(function() { - return this.hi('username'); + block('b')({ + content: function() { + return this.hi('username'); + } }); }); @@ -331,15 +341,17 @@ templates.BEMContext.prototype.hi = function(name) { // Добавляем шаблоны templates.compile(function() { - block('b').content()(function() { - return this.hi('templates'); + block('b')({ + content: function() { + return this.hi('templates'); + } }); }); var bemjson = { block: 'b' }; // Применяем шаблоны -var html = templates.apply(bemjson)); +var html = templates.apply(bemjson); ``` В результате `html` будет содержать строку: @@ -363,14 +375,16 @@ var html = templates.apply(bemjson)); **Пример** ```js -block('link').attrs()(function(node, ctx) { - return { - // тоже самое что и this.ctx.url - href: ctx.url, - - // тоже самое что и this.position - 'data-position': node.position - }; +block('link')({ + attrs: function(node, ctx) { + return { + // тоже самое что и this.ctx.url + href: ctx.url, + + // тоже самое что и this.position + 'data-position': node.position + }; + } }); ``` @@ -388,8 +402,8 @@ match(function(node, ctx) { Кроме того, функции шаблонов поддерживают ES6 arrow functions, поэтому вы можете писать везде в таком стиле: ```js -match((node, ctx) => (!node.mods.disabled && ctx.target)) -addAttrs()((node, ctx) => ({ href: ctx.url })) +match((node, ctx) => ctx.target) +addAttrs: (node, ctx) => { href: ctx.url } ``` Читать далее: [runtime](7-runtime.md) diff --git a/docs/ru/7-runtime.md b/docs/ru/7-runtime.md index 20ad3af7..f10f1d45 100644 --- a/docs/ru/7-runtime.md +++ b/docs/ru/7-runtime.md @@ -41,11 +41,10 @@ ```js block('*') - .match(function() { - return this.ctx.counter; - }) - .mix()(function() { - return { block: 'counter', js: { id: this.ctx.counter } } + .match(function() { return this.ctx.counter; })({ + mix: function() { + return { block: 'counter', js: { id: this.ctx.counter } } + } }) ``` @@ -84,17 +83,17 @@ apply(modeName, assignObj) Шаблон: ```js -block('button')( - mode('test')(function() { +block('button')({ + test: function() { return this.tmp + this.ctx.foo; - }), - def()(function() { + }, + default: function() { return apply('test', { tmp: 'ping', 'ctx.foo': 'pong' }); - }) -); + } +}); ``` *Результат шаблонизации:* @@ -116,12 +115,14 @@ pingpong Шаблон: ```js -block('footer').mode('custom')('footer'); -block('header').mode('custom')('header'); -block('header').tag()(function() { - // не смотря на то, что вторым аргументом apply явно указан блок footer - // будет вызван пользовательский режим блока `header`. - return apply('custom', { block: 'footer' }); +block('footer')({ custom: 'footer' }); +block('header')({ + custom: 'header', + tag: function() { + // не смотря на то, что вторым аргументом apply явно указан блок footer + // будет вызван пользовательский режим блока `header`. + return apply('custom', { block: 'footer' }); + } }); ``` @@ -144,8 +145,10 @@ block('header').tag()(function() { Шаблон: ```js -block('animal').content()(function() { - return apply('type'); +block('animal')({ + content: function() { + return apply('type'); + } }); ``` @@ -171,10 +174,12 @@ applyNext(newctx) **Пример** ```js -block('link').tag()('a'); -block('link').tag()(function() { - var res = applyNext(); // res === 'a' - return res; +block('link')({ tag: 'a' }); +block('link')({ + tag: function() { + var res = applyNext(); // res === 'a' + return res; + } }); ``` @@ -198,11 +203,13 @@ applyCtx(bemjson, newctx) Шаблон: ```js -block('header').def()(function() { - return applyCtx(this.extend(this.ctx, { - block: 'layout', - mix: [{ block: 'header' }].concat(this.ctx.mix || []) - })); +block('header')({ + default: function() { + return applyCtx(this.extend(this.ctx, { + block: 'layout', + mix: [{ block: 'header' }].concat(this.ctx.mix || []) + })); + } }); ``` diff --git a/lib/bemxjst/tree.js b/lib/bemxjst/tree.js index 688ea250..c94d087d 100644 --- a/lib/bemxjst/tree.js +++ b/lib/bemxjst/tree.js @@ -326,13 +326,74 @@ Tree.prototype.flush = function(conditions, item) { // Body } else { - var template = new Template(conditions, arg); - template.wrap(); - this.templates.push(template); + if (this.isShortcutAllowed(arg, conditions)) { + var keys = Object.keys(arg); + for (var n = 0; n < keys.length; n++) + this.addTemplate( + conditions.concat(this.createMatch(keys[n])), + arg[keys[n]] + ); + } else { + this.addTemplate(conditions, arg); + } } } }; +Tree.prototype.createMatch = function(modeName) { + switch (modeName) { + case 'addAttrs': + return [ + new PropertyMatch('_mode', 'attrs'), + new AddMatch('attrs', this.refs) + ]; + case 'addJs': + return [ + new PropertyMatch('_mode', 'js'), + new AddMatch('js', this.refs) + ]; + case 'addMix': + return [ + new PropertyMatch('_mode', 'mix'), + new AddMatch('mix', this.refs) + ]; + case 'addMods': + return [ + new PropertyMatch('_mode', 'mods'), + new AddMatch('mods', this.refs) + ]; + case 'addElemMods': + return [ + new PropertyMatch('_mode', 'elemMods'), + new AddMatch('elemMods', this.refs) + ]; + case 'appendContent': + case 'prependContent': + return [ + new PropertyMatch('_mode', 'content'), + new AddMatch(modeName, this.refs) + ]; + + case 'wrap': + return new WrapMatch(this.refs); + + case 'replace': + return new ReplaceMatch(this.refs); + + case 'extend': + return new ExtendMatch(this.refs); + + default: + return new PropertyMatch('_mode', modeName); + } +}; + +Tree.prototype.addTemplate = function(conditions, arg) { + var template = new Template(conditions, arg); + template.wrap(); + this.templates.push(template); +}; + Tree.prototype.body = function() { var children = new Array(arguments.length); for (var i = 0; i < arguments.length; i++) @@ -347,6 +408,28 @@ Tree.prototype.body = function() { return this.boundBody; }; +Tree.modsCheck = { mods: 1, elemMods: 1 }; + +Tree.checkConditions = function(conditions) { + for (var i = 0; i < conditions.length; i++) { + var condition = conditions[i]; + if (condition.key === 'block' || + condition.key === 'elem' || + (Array.isArray(condition.key) && Tree.modsCheck[condition.key[0]]) || + condition instanceof CustomMatch) continue; + return false; + } + + return true; +}; + +Tree.prototype.isShortcutAllowed = function(arg, conditions) { + return typeof arg === 'object' && + arg !== null && + !Array.isArray(arg) && + Tree.checkConditions(conditions); +}; + Tree.prototype.match = function() { var children = new Array(arguments.length); diff --git a/test/templates-syntax-test.js b/test/templates-syntax-test.js index ee491919..86fc44c2 100644 --- a/test/templates-syntax-test.js +++ b/test/templates-syntax-test.js @@ -2,6 +2,7 @@ var fixtures = require('./fixtures')('bemhtml'); var test = fixtures.test; var assert = require('assert'); var BEMXJSTError = require('../lib/bemxjst/error').BEMXJSTError; +var bemhtml = require('../').bemhtml; describe('Templates syntax', function() { it('should support block without mode()', function() { @@ -71,4 +72,533 @@ describe('Templates syntax', function() { }, BEMXJSTError); Error.captureStackTrace = captureStackTrace; }); + + it('should return null if null passed', function() { + var templates = bemhtml.compile(function() { + block('b')(null); + }); + + assert.equal( + templates.apply({ block: 'b' }), + null + ); + }); + + describe('shortcut', function() { + describe('should work in chain with "non output modes"', function() { + it('block() mode only', function() { + test(function() { + block('button')({ + tag: 'button', + mix: 'mixed' + }); + }, + { block: 'button' }, + ''); + }); + + it('def() mode', function() { + test(function() { + block('b')({ default: 'result' }); + }, + { block: 'b' }, + 'result'); + }); + + it('match() mode', function() { + test(function() { + block('b').match(function() { + return this.ctx.flag; + })({ tag: 'span', mix: 'mixed' }); + }, + { block: 'b', flag: 1 }, + ''); + }); + + it('match in shortcut is treated as user defined mode ' + + 'not as subpredicate', function() { + test(function() { + block('b')({ + match: function(node) { + node.sideEffect = 'who cares?'; + return 'But '; + }, + content: function() { + return apply('match') + this.sideEffect; + } + }); + }, + { block: 'b' }, + '
But who cares?
'); + }); + + it('mod() mode', function() { + test(function() { + block('b').mod('modName', 'modVal')({ tag: 'span', mix: 'mixed' }); + }, + { block: 'b', mods: { modName: 'modVal' } }, + ''); + }); + + it('elem() mode', function() { + test(function() { + block('b').elem('e')({ tag: 'span', mix: 'mixed' }); + }, + { block: 'b', elem: 'e' }, + ''); + }); + + it('elemMod() mode', function() { + test(function() { + block('b') + .elem('e') + .elemMod('elemModName', 'elemModVal') + ({ tag: 'span', mix: 'mixed' }); + }, + { block: 'b', elem: 'e', elemMods: { elemModName: 'elemModVal' } }, + ''); + }); + + it('custom user defined mode', function() { + test(function() { + block('b')({ + myMode: function() { return 1 + 1; }, + myConstMode: 40 + }); + + block('b').content()(function() { + return apply('myMode') + apply('myConstMode'); + }); + }, + { block: 'b' }, + '
42
'); + }); + + it('wrap() mode', function() { + test(function() { + block('b')({ + wrap: function() { + return { + block: 'wrapper', + content: this.ctx + }; + } + }); + }, + { block: 'b' }, + '
'); + }); + + it('replace() mode', function() { + test(function() { + block('b')({ replace: { block: 'c' } }); + }, + { block: 'b' }, + '
'); + }); + + it('extend() mode', function() { + test(function() { + block('b')({ extend: { 'ctx.content': 'extended' } }); + }, + { block: 'b' }, + '
extended
'); + }); + + it('addAttrs() mode should add attrs', function() { + test(function() { + block('b')({ addAttrs: { name: 'test' } }); + }, + { block: 'b', attrs: { id: 'some' } }, + '
'); + }); + + it('addElemMods() mode should add elemMods', function() { + test(function() { + block('b').elem('e')({ addElemMods: { templ: 'test' } }); + }, + { block: 'b', elem: 'e', elemMods: { bemjson: 'some' } }, + '
'); + }); + + it('addJs() mode should add js-params and i-bem className', function() { + test(function() { + block('b')({ addJs: { templ: 'test' } }); + }, + { block: 'b', js: { bemjson: 'some' } }, + '
'); + }); + + it('addMix() mode should add mix', function() { + test(function() { + block('a')({ addMix: { block: 'c' } }); + }, + { block: 'a', mix: { block: 'b' } }, + '
'); + }); + + it('addMods() mode should add mods', function() { + test(function() { + block('b')({ addMods: { templ: 'test' } }); + }, + { block: 'b', mods: { bemjson: 'some' } }, + '
'); + }); + + it('appendContent() mode should append content', function() { + test(function() { + block('b')({ appendContent: ' post' }); + }, + { block: 'b', content: 'content' }, + '
content post
'); + }); + + it('attrs() mode should override attrs from BEMJSON', function() { + test(function() { + block('a')({ + attrs: function() { + var attrsFromData = applyNext(); + return { id: attrsFromData.id.substr(0, 3) }; + } + }); + }, + { block: 'a', attrs: { id: 'bemjson' } }, + '
'); + }); + + it('bem() mode should override bem from BEMJSON', function() { + test(function() { + block('b')({ bem: true }); + }, + { block: 'b', bem: false }, + '
'); + }); + + it('cls() mode should override cls from BEMJSON', function() { + test(function() { + block('b')({ cls: 'v-card' }); + }, + { block: 'b', cls: 'blah-blah' }, + '
'); + }); + + it('content() mode should override content from BEMJSON', function() { + test(function() { + block('b')({ content: 'bem-xjst rules' }); + }, + { block: 'b', content: 'nope' }, + '
bem-xjst rules
'); + }); + + it('elemMods() mode should override elemMods from BEMJSON', function() { + test(function() { + block('b').elem('e')({ elemMods: { templ: 'test' } }); + }, + { block: 'b', elem: 'e', elemMods: { bemjson: 'some' } }, + '
'); + }); + + it('js() mode should override js from BEMJSON', function() { + test(function() { + block('b')({ js: { templ: 'test' } }); + }, + { block: 'b', js: { bemjson: 'some' } }, + '
'); + }); + + it('mix() mode should override mix from BEMJSON', function() { + test(function() { + block('a')({ mix: { block: 'c' } }); + }, + { block: 'a', mix: { block: 'b' } }, + '
'); + }); + + it('mods() mode should override mods from BEMJSON', function() { + test(function() { + block('b')({ mods: { templ: 'test' } }); + }, + { block: 'b', mods: { bemjson: 'some' } }, + '
'); + }); + + it('prependContent() mode should prepend content', function() { + test(function() { + block('b')({ prependContent: 'pre ' }); + }, + { block: 'b', content: 'content' }, + '
pre content
'); + }); + + it('tag() mode should override tag from BEMJSON', function() { + test(function() { + block('reference')({ tag: 'a' }); + }, + { block: 'reference', tag: 'link' }, + ''); + }); + }); + + describe('should not work in chain with "output modes"', function() { + + it('addAttrs() mode', function() { + test(function() { + block('b').addAttrs()({ + mix: 'doesnt-work-as-mix-for-block-b' + }); + }, + { block: 'b', attrs: { 'data-test': 'one-two' } }, + '
'); + }); + + it('addElemMods() mode', function() { + test(function() { + block('b').elem('e').addElemMods()({ + mix: 'doesnt-work-as-mix-for-block-b' + }); + }, + { block: 'b', elem: 'e' }, + '
'); + }); + + it('addJs() mode', function() { + test(function() { + block('b').addJs()({ + mix: 'doesnt-work-as-mix-for-block-b' + }); + }, + { block: 'b' }, + '
'); + }); + + it('addMix() mode', function() { + test(function() { + block('b').addMix()({ + content: 'doesnt-work-as-content-for-block-b' + }); + }, + { block: 'b' }, + '
'); + }); + + it('addMods() mode', function() { + test(function() { + block('b').addMods()({ + mix: 'doesnt-work-as-mix-for-block-b' + }); + }, + { block: 'b' }, + '
'); + }); + + it('appendContent() mode', function() { + test(function() { + block('b').appendContent()({ + block: 'regular-content', + mix: 'doesnt-work-as-mix-for-block-b' + }); + }, + { block: 'b', content: 'test' }, + '
test' + + '
' + + '
' + + '
'); + }); + + it('attrs() mode', function() { + test(function() { + block('b').attrs()({ + id: 'test', + mix: 'doesnt-work-as-mix-for-block-b' + }); + }, + { block: 'b' }, + '
' + + '
'); + }); + + it('bem() mode', function() { + test(function() { + block('b').bem()({ + content: 'doesnt-work', + mix: 'doesnt-work' + }); + }, + { block: 'b' }, + '
'); + }); + + it('cls() mode', function() { + test(function() { + block('b').cls()({ + mix: 'doesnt-work', + toString: function() { return 'regular-html-class'; } + }); + }, + { block: 'b' }, + '
'); + }); + + it('content() mode', function() { + test(function() { + block('b').content()({ + block: 'more', + mix: 'doesnt-work-as-mix-for-block-b' + }); + }, + { block: 'b' }, + '
' + + '
' + + '
'); + }); + + it('custom user defined mode', function() { + test(function() { + block('b').content()({ + any: function() { return 1 + 1; } + }); + + block('b').content()(function() { + return apply('any'); + }); + }, + { block: 'b' }, + '
'); + }); + + it('def() mode', function() { + test(function() { + block('b').def()({ + mix: 'doesnt-work-as-mix-for-block-b' + }); + }, + { block: 'b' }, + { mix: 'doesnt-work-as-mix-for-block-b' }); + }); + + it('elemMods() mode', function() { + test(function() { + block('b').elem('e').elemMods()({ + shortcut: 'doesnt-work', + mix: 'doesnt-work-as-mix-for-block-b' + }); + }, + { block: 'b', elem: 'e' }, + '
'); + }); + + it('prependContent() mode', function() { + test(function() { + block('b').prependContent()({ + block: 'regular-content', + mix: 'doesnt-work-as-mix-for-block-b' + }); + }, + { block: 'b', content: 'test' }, + '
' + + '
' + + '
' + + 'test
'); + }); + + it('elemMods() mode', function() { + test(function() { + block('b').tag()({ + toString: function() { return 'html'; } + }); + }, + { block: 'b' }, + ''); + }); + }); + + describe('composition tests', function() { + it('should work in composition', function() { + test(function() { + block('button')( + { + tag: 'button', + mix: 'mixed' + }, + content()('bem-xjst') + ); + }, + { block: 'button' }, + ''); + }); + + it('more complex test', function() { + test(function() { + block('button')( + { attrs: { id: 'from-shortcut' } }, + match(function() { return true; })( + { + tag: 'button', + mix: 'mixed' + }, + content()('bem-xjst') + ) + ); + }, + { block: 'button' }, + ''); + }); + }); + + it('should support body as function', function() { + test(function() { + block('b')({ + attrs: function() { + return { id: 'from-shortcut' }; + }, + content: function() { + return 42; + } + }); + }, + { block: 'b' }, + '
42
'); + }); + + it('should support arguments in body function', function() { + test(function() { + block('b')({ + content: function(node, ctx) { return ctx.answer; }, + mix: function(node) { return node.ctx.customMix; } + }); + }, + { block: 'b', answer: 42, customMix: 'from-shortcut' }, + '
42
'); + }); + + it('should support applyNext() in body function', function() { + test(function() { + block('b')( + content()(function() { return applyNext() + ' 2'; }), + { content: function() { return applyNext() + ' 3'; } }, + match(function() { return true; })( + content()(function() { return applyNext() + ' 4'; }), + { content: function() { return applyNext() + ' 5'; } } + ) + ); + }, + { block: 'b', content: '1' }, + '
1 2 3 4 5
'); + }); + + it('should support applyCtx() in body function', function() { + test(function() { + block('test').def()('OK'); + block('b')( + { content: function() { return applyCtx({ block: 'test' }); } } + ); + }, + { block: 'b', content: '1' }, + '
OK
'); + }); + }); });