diff --git a/blocks-common/i-bem/__dom/i-bem__dom.js b/blocks-common/i-bem/__dom/i-bem__dom.js index 3c4fde69..5434ee72 100644 --- a/blocks-common/i-bem/__dom/i-bem__dom.js +++ b/blocks-common/i-bem/__dom/i-bem__dom.js @@ -1115,11 +1115,12 @@ var DOM = BEM.DOM = BEM.decl('i-bem__dom', { * @param {jQuery|String} content New content * @param {Function} [callback] Handler to be called after initialization * @param {Object} [callbackCtx] Handler's context + * @returns {jQuery} ctx Initialization context */ update : function(ctx, content, callback, callbackCtx) { this.destruct(ctx, true); - this.init(ctx.html(content), callback, callbackCtx); + return this.init(ctx.html(content), callback, callbackCtx); }, @@ -1127,11 +1128,12 @@ var DOM = BEM.DOM = BEM.decl('i-bem__dom', { * Changes a fragment of the DOM tree including the context and initializes blocks. * @param {jQuery} ctx Root DOM node * @param {jQuery|String} content Content to be added + * @returns {jQuery} ctx Initialization context */ replace : function(ctx, content) { this.destruct(true, ctx); - this.init($(content).replaceAll(ctx)); + return this.init($(content).replaceAll(ctx)); }, @@ -1139,10 +1141,11 @@ var DOM = BEM.DOM = BEM.decl('i-bem__dom', { * Adds a fragment of the DOM tree at the end of the context and initializes blocks * @param {jQuery} ctx Root DOM node * @param {jQuery|String} content Content to be added + * @returns {jQuery} ctx Initialization context */ append : function(ctx, content) { - this.init($(content).appendTo(ctx)); + return this.init($(content).appendTo(ctx)); }, @@ -1150,10 +1153,11 @@ var DOM = BEM.DOM = BEM.decl('i-bem__dom', { * Adds a fragment of the DOM tree at the beginning of the context and initializes blocks * @param {jQuery} ctx Root DOM node * @param {jQuery|String} content Content to be added + * @returns {jQuery} ctx Initialization context */ prepend : function(ctx, content) { - this.init($(content).prependTo(ctx)); + return this.init($(content).prependTo(ctx)); }, @@ -1161,10 +1165,11 @@ var DOM = BEM.DOM = BEM.decl('i-bem__dom', { * Adds a fragment of the DOM tree before the context and initializes blocks * @param {jQuery} ctx Contextual DOM node * @param {jQuery|String} content Content to be added + * @returns {jQuery} ctx Initialization context */ before : function(ctx, content) { - this.init($(content).insertBefore(ctx)); + return this.init($(content).insertBefore(ctx)); }, @@ -1172,10 +1177,11 @@ var DOM = BEM.DOM = BEM.decl('i-bem__dom', { * Adds a fragment of the DOM tree after the context and initializes blocks * @param {jQuery} ctx Contextual DOM node * @param {jQuery|String} content Content to be added + * @returns {jQuery} ctx Initialization context */ after : function(ctx, content) { - this.init($(content).insertAfter(ctx)); + return this.init($(content).insertAfter(ctx)); }, diff --git a/blocks-common/i-bem/__html/i-bem__html.bemhtml b/blocks-common/i-bem/__html/i-bem__html.bemhtml index 030b6525..6b682f83 100644 --- a/blocks-common/i-bem/__html/i-bem__html.bemhtml +++ b/blocks-common/i-bem/__html/i-bem__html.bemhtml @@ -270,7 +270,7 @@ this._mode === '' { this.block = vBlock || (vElem ? block : undefined), this._currBlock = vBlock || vElem ? undefined : block, this.elem = this.ctx.elem, - this.mods = (vBlock ? this.ctx.mods : this.mods) || {}, + this.mods = vBlock? this.ctx.mods || (this.ctx.mods = {}) : this.mods, this.elemMods = this.ctx.elemMods || {} ) { (this.block || this.elem) ? diff --git a/blocks-common/i-bem/__html/lib/bemhtml/api.js b/blocks-common/i-bem/__html/lib/bemhtml/api.js index 2820106e..b209fe6d 100644 --- a/blocks-common/i-bem/__html/lib/bemhtml/api.js +++ b/blocks-common/i-bem/__html/lib/bemhtml/api.js @@ -80,10 +80,13 @@ api.translate = function translate(source, options) { var exportName = options.exportName; // Replace known context lookups with context vars - xjstJS = replaceContext(xjstJS); + var cr = new ContextReplacer(); + xjstJS = cr.run(xjstJS); return 'var ' + exportName + ' = function() {\n' + ' var ' + propValues.join(', ') + ';\n' + + cr.getCallWrap() + '\n' + + cr.getApplyWrap() + '\n' + ' var cache,\n' + ' exports = {},\n' + ' xjst = ' + xjstJS + ';\n' + @@ -133,79 +136,260 @@ api.compile = function compile(source, options) { return context.BEMHTML; }; -function replaceContext(src) { - function translateProp(prop) { - if (properties.hasOwnProperty(prop)) - return properties[prop]; - else - return false; - }; +function ContextReplacer() { + // estraverse context + this.estraverse = null; - function isHash(node) { - var val = node.init; - if (!val) - return false; + this.applyc = null; + this.map = null; - if (val.type !== 'ObjectExpression' || val.properties.length !== 3) - return false; + this.needCallWrap = false; + this.needApplyWrap = false; +}; - var props = val.properties; - return props.every(function(prop) { - var name = prop.key.name; - var val = prop.value; +ContextReplacer.prototype.translateProp = function translateProp(prop) { + if (properties.hasOwnProperty(prop)) + return properties[prop]; + else + return false; +}; - if ((name === 'n' || name === 'm') && val.type === 'ObjectExpression') - return true; - if (name === 'd' && val.type === 'FunctionExpression') - return true; - return false; - }); - } +ContextReplacer.prototype.isHash = function isHash(node) { + var val = node.init; + if (!val) + return false; - var applyc = null; - var map = null; + if (val.type !== 'ObjectExpression' || val.properties.length !== 3) + return false; + var props = val.properties; + return props.every(function(prop) { + var name = prop.key.name; + var val = prop.value; + + if ((name === 'n' || name === 'm') && val.type === 'ObjectExpression') + return true; + if (name === 'd' && val.type === 'FunctionExpression') + return true; + return false; + }); +}; + +ContextReplacer.prototype.run = function run(src) { var ast = esprima.parse(src); - ast = estraverse.replace(ast, { + + var self = this; + return escodegen.generate(estraverse.replace(ast, { + enter: function(node, parent) { + self.estraverse = this; + return self.enterNode(node, parent); + }, + leave: function(node) { + return self.leaveNode(node); + } + })); +}; + +ContextReplacer.prototype.isApplyc = function isApplyc(node) { + var isFunction = node.type === 'FunctionDeclaration' || + node.type === 'FunctionExpression'; + var id = node.id && node.id.name; + + return this.applyc === null && + isFunction && + (this.map !== null || /^(applyc|\$\d+)$/.test(id)) +}; + +ContextReplacer.prototype.isMap = function isMap(node) { + return this.applyc === null && + node.type === 'VariableDeclarator' && + /^__(h|\$m)\d+$/.test(node.id && node.id.name) && + this.isHash(node); +}; + +ContextReplacer.prototype.enterNode = function enterNode(node, parent) { + var isFunction = node.type === 'FunctionDeclaration' || + node.type === 'FunctionExpression'; + + if (this.isApplyc(node)) { + this.applyc = node; + } else if (this.isMap(node)) { + this.map = node; + } else if (this.applyc === null) { + return; + } + + if (this.applyc !== node && isFunction) { + this.replaceEscapingApplies(node, parent); + return this.estraverse.skip(); + } + + var res; + + res = this.handleMember(node); + if (res) + return res; + + res = this.handleEscapingThis(node, parent); + if (res) + return res; +}; + +ContextReplacer.prototype.handleMember = function handleMember(node) { + if (node.type !== 'MemberExpression' || node.computed) + return; + + var obj = node.object; + if (obj.type !== 'Identifier' || obj.name !== '__$ctx') + return; + + var prop = this.translateProp(node.property.name || node.property.value); + if (!prop) + return; + + return { type: 'Identifier', name: prop }; +}; + +ContextReplacer.prototype.isMapCall = function isMapCall(node) { + if (node.type !== 'LogicalExpression' || node.operator !== '||') + return false; + + var match = false; + estraverse.traverse(node, { enter: function(node) { - var isFunction = node.type === 'FunctionDeclaration' || - node.type === 'FunctionExpression'; - var id = node.id && node.id.name; - if (applyc === null && - isFunction && - (map !== null || /^(applyc|\$\d+)$/.test(id))) { - applyc = node; - } else if (applyc === null && - node.type === 'VariableDeclarator' && - /^__(h|\$m)\d+$/.test(id) && - isHash(node)) { - map = node; - } else if (applyc === null) { - return; + if (node.type === 'Identifier' && /^__(h|\$m)\d+$/.test(node.name)) { + match = true; + this['break'](); } + } + }); - if (applyc !== node && isFunction) { - this.skip(); + return match; +}; + +ContextReplacer.prototype.handleEscapingThis = + function handleEscapingThis(node, parent) { + if (node.type !== 'Identifier' || node.name !== '__$ctx') + return; + + if (/Expression$/.test(parent.type)) { + if (parent.type === 'FunctionExpression') + return; + + var isMember = parent.type === 'MemberExpression' && + (!parent.computed || parent.property.type === 'Literal'); + if (isMember) + return; + + if (parent.type === 'CallExpression') { + var c = parent.callee; + + // Ignore $1(__$ctx) calls + if (c.type === 'Identifier' && /^(applyc|\$e|\$\d+)$/.test(c.name)) return; - } - if (node.type === 'MemberExpression' && - node.computed === false && - node.object.type === 'Identifier' && - node.object.name === '__$ctx') { - var prop = translateProp(node.property.name || node.property.value); - if (!prop) - return; + // Ignore __$wrapThis(__$ctx) + if (c.type === 'Identifier' && c.name === '__$wrapThis') + return; - return { type: 'Identifier', name: prop }; - } + // Ignore (typeof x === 'number' ? __$m\.n[x] : __$m.d)(__$ctx) + if (this.isMapCall(c)) + return; + } + } else if (parent.type === 'VariableDeclarator') { + // Internal, xjst-thing + if (parent.id.type === 'Identifier' && parent.id.name === '__this') + return; + + if (parent.init !== node) + return; + } else { + return; + } + + this.needCallWrap = true; + + // Wrap in call + return { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: '__$wrapThis' }, - leave: function(node) { - if (node === applyc) - applyc = null; - if (node === map) - applyc = null; + arguments: [ node ] + }; +}; + +ContextReplacer.prototype.replaceEscapingApplies = + function replaceEscapingApplies(node, parent) { + if (this.applyc === null) + return; + + var self = this; + estraverse.replace(node, { + enter: function(node, parent) { + return self.handleEscapingApply(node, parent); } }); - return escodegen.generate(ast); -} +}; + +ContextReplacer.prototype.handleEscapingApply = + function handleEscapingApply(node, parent) { + if (node.type !== 'CallExpression') + return; + + var callee = node.callee; + if (callee.type !== 'Identifier' || callee.name !== 'applyc') + return; + + this.needApplyWrap = true; + + // Wrap in call + return { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: '__$wrapApply' + }, + arguments: [ callee ].concat(node.arguments) + }; +}; + +ContextReplacer.prototype.leaveNode = function leaveNode(node) { + if (node === this.applyc) + this.applyc = null; + if (node === this.map) + this.map = null; +}; + +ContextReplacer.prototype.getCallWrap = function getCallWrap() { + if (!this.needCallWrap) + return ''; + + return 'function __$wrapThis(ctx) {\n' + + propKeys.map(function(key) { + return 'ctx.' + key + ' = ' + properties[key] + ';'; + }).join('\n') + '\n' + + 'return ctx;\n' + + '};' +}; + +ContextReplacer.prototype.getApplyWrap = function getApplyWrap() { + if (!this.needApplyWrap) + return ''; + + return 'function __$wrapApply(applyc, ctx) {\n' + + // var __t$prop = $$prop; + // $$prop = this.prop; + propKeys.map(function(key) { + return 'var __t$' + key + ' = ' + properties[key] + ';\n' + + properties[key] + ' = ctx.' + key + ';'; + }).join('\n') + '\n' + + 'var r = applyc(ctx);\n' + + // $$prop = __t$prop; + propKeys.map(function(key) { + return properties[key] + ' = __t$' + key + ';'; + }).join('\n') + '\n' + + 'return r;\n' + + '};' +}; diff --git a/blocks-common/i-bem/__html/test/files/i-bem/escaping-this.bemhtml b/blocks-common/i-bem/__html/test/files/i-bem/escaping-this.bemhtml new file mode 100644 index 00000000..7473fc08 --- /dev/null +++ b/blocks-common/i-bem/__html/test/files/i-bem/escaping-this.bemhtml @@ -0,0 +1,6 @@ +block b1, custom: 'ok' +block b1, tag: { + return [ this ].map(function(ctx) { + return this._mode + '-' + ctx.block + ':' + apply({ _mode: 'custom' }); + }, this); +} diff --git a/blocks-common/i-bem/__html/test/files/i-bem/escaping-this.html b/blocks-common/i-bem/__html/test/files/i-bem/escaping-this.html new file mode 100644 index 00000000..964d13c9 --- /dev/null +++ b/blocks-common/i-bem/__html/test/files/i-bem/escaping-this.html @@ -0,0 +1 @@ + diff --git a/blocks-common/i-bem/__html/test/files/i-bem/escaping-this.json b/blocks-common/i-bem/__html/test/files/i-bem/escaping-this.json new file mode 100644 index 00000000..7850c937 --- /dev/null +++ b/blocks-common/i-bem/__html/test/files/i-bem/escaping-this.json @@ -0,0 +1,3 @@ +{ + "block": "b1" +} diff --git a/blocks-common/i-bem/__html/test/files/i-bem/gh-550.bemhtml b/blocks-common/i-bem/__html/test/files/i-bem/gh-550.bemhtml new file mode 100644 index 00000000..21e71ccb --- /dev/null +++ b/blocks-common/i-bem/__html/test/files/i-bem/gh-550.bemhtml @@ -0,0 +1,6 @@ +block b1, default: { + this.mods.foo = 'bar'; + applyNext(); +} + +block b1, mod foo bar, content: 'foo bar'; diff --git a/blocks-common/i-bem/__html/test/files/i-bem/gh-550.html b/blocks-common/i-bem/__html/test/files/i-bem/gh-550.html new file mode 100644 index 00000000..6f02dab6 --- /dev/null +++ b/blocks-common/i-bem/__html/test/files/i-bem/gh-550.html @@ -0,0 +1 @@ +
foo bar
diff --git a/blocks-common/i-bem/__html/test/files/i-bem/gh-550.json b/blocks-common/i-bem/__html/test/files/i-bem/gh-550.json new file mode 100644 index 00000000..68e46640 --- /dev/null +++ b/blocks-common/i-bem/__html/test/files/i-bem/gh-550.json @@ -0,0 +1,3 @@ +{ + "block": "b1" +} diff --git a/blocks-common/i-bem/__html/test/i-bem-test.js b/blocks-common/i-bem/__html/test/i-bem-test.js index 232d7047..bfe3e64a 100644 --- a/blocks-common/i-bem/__html/test/i-bem-test.js +++ b/blocks-common/i-bem/__html/test/i-bem-test.js @@ -46,4 +46,6 @@ suite('i-bem block and others', function() { unit('condition regression #239', 'gh-239', true); unit('simple types regression #254', 'gh-254'); unit('applyNext in content regression #289', 'gh-289'); + unit('mods redefinition #550', 'gh-550'); + unit('block with escaping this', 'escaping-this'); }); diff --git a/blocks-common/i-bem/i-bem.ru.md b/blocks-common/i-bem/i-bem.ru.md index b8bc2ede..576bbace 100644 --- a/blocks-common/i-bem/i-bem.ru.md +++ b/blocks-common/i-bem/i-bem.ru.md @@ -728,10 +728,6 @@ BEM.DOM.decl('b-my-block', { * `findBlockOn/findBlocksOn` — поиск блока/блоков на `DOM`-элементах текущего блока или его элементов; * `findBlockOutside/findBlocksOutside` — поиск блока/блоков снаружи `DOM`-элементов текущего блока или его элементов. -Список методов поиска блоков и их сигнатуры можно посмотреть в [референсе по BEM.DOM](/blocks/i-bem/dom/i-bem__dom.jsdoc.md). - -Примерами блоков, использующих методы поиска других блоков, могут быть: [b-smart-help](/blocks/b-smart-help/b-smart-help.md), [b-screenshot](blocks/b-screenshot/b-screenshot.md) и [b-dropdowna](blocks/b-dropdowna/b-dropdowna.md). - #### Методы доступа к элементам Для поиска элементов внутри блока используется метод `elem`. Результат этого метода кэшируется. diff --git a/blocks-desktop/i-jquery/__leftclick/i-jquery__leftclick.js b/blocks-desktop/i-jquery/__leftclick/i-jquery__leftclick.js index 9f007b8e..7a34ec03 100755 --- a/blocks-desktop/i-jquery/__leftclick/i-jquery__leftclick.js +++ b/blocks-desktop/i-jquery/__leftclick/i-jquery__leftclick.js @@ -29,7 +29,7 @@ var leftClick = $.event.special.leftclick = { if(!e.button) { e.type = 'leftclick'; - $.event.handle.apply(this, arguments); + $.event.dispatch.apply(this, arguments); e.type = 'click'; } diff --git a/blocks-desktop/i-jquery/__outsideclick/i-jquery__outsideclick.js b/blocks-desktop/i-jquery/__outsideclick/i-jquery__outsideclick.js index ed5996d1..72d8d39d 100644 --- a/blocks-desktop/i-jquery/__outsideclick/i-jquery__outsideclick.js +++ b/blocks-desktop/i-jquery/__outsideclick/i-jquery__outsideclick.js @@ -20,7 +20,7 @@ var outsideClick = $.event.special.outsideclick = { e.type = 'outsideclick'; - $.event.handle.apply(this, arguments); + $.event.dispatch.apply(this, arguments); e.type = 'click'; diff --git a/blocks-touch/i-jquery/__tap/i-jquery__tap.js b/blocks-touch/i-jquery/__tap/i-jquery__tap.js index 91a282ef..a710ad3d 100644 --- a/blocks-touch/i-jquery/__tap/i-jquery__tap.js +++ b/blocks-touch/i-jquery/__tap/i-jquery__tap.js @@ -24,7 +24,7 @@ var tap = $.event.special.tap = { if(!e.button) { e.type = 'tap'; - $.event.handle.apply(this, arguments); + $.event.dispatch.apply(this, arguments); e.type = 'click'; } diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index dfa137c5..385ed734 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -2,37 +2,97 @@ "name": "bem-bl", "version": "0.0.0", "dependencies": { + "bem-environ": { + "version": "1.4.0", + "from": "bem-environ@>=1.4.0 <1.5.0" + }, + "dom-js": { + "version": "0.0.9", + "from": "dom-js@>=0.0.9 <0.1.0", + "dependencies": { + "sax": { + "version": "0.6.1", + "from": "sax@>=0.1.5" + } + } + }, + "escodegen": { + "version": "1.2.0", + "from": "escodegen@>=1.2.0 <1.3.0", + "dependencies": { + "esutils": { + "version": "1.0.0", + "from": "esutils@>=1.0.0 <1.1.0" + }, + "source-map": { + "version": "0.1.40", + "from": "source-map@>=0.1.30 <0.2.0", + "dependencies": { + "amdefine": { + "version": "0.1.0", + "from": "amdefine@>=0.0.4" + } + } + } + } + }, + "esprima": { + "version": "1.0.4", + "from": "esprima@>=1.0.4 <1.1.0" + }, + "estraverse": { + "version": "1.5.1", + "from": "estraverse@>=1.5.0 <1.6.0" + }, + "ometajs": { + "version": "3.3.7", + "from": "ometajs@>=3.3.5 <3.4.0", + "dependencies": { + "coa": { + "version": "0.3.9", + "from": "coa@>=0.3.0 <0.4.0" + }, + "q": { + "version": "0.8.12", + "from": "q@>=0.8.0 <0.9.0" + }, + "uglify-js": { + "version": "1.3.5", + "from": "uglify-js@>=1.3.0 <1.4.0" + } + } + }, "xjst": { - "version": "0.5.18", - "from": "xjst@~0.5.1", + "version": "0.5.25", + "from": "xjst@>=0.5.24 <0.6.0", "dependencies": { "coa": { "version": "0.3.9", - "from": "coa@~ 0.3.8" + "from": "coa@>=0.3.8 <0.4.0" }, "q": { "version": "0.8.12", - "from": "q@~ 0.8.10" + "from": "q@>=0.8.10 <0.9.0" }, "uglify-js": { "version": "1.3.5", - "from": "uglify-js@1.3.x" + "from": "uglify-js@>=1.3.0 <1.4.0" }, "spoon": { "version": "0.1.10", - "from": "spoon@~0.1.10", + "from": "spoon@>=0.1.10 <0.2.0", "dependencies": { "escodegen": { "version": "0.0.28", - "from": "escodegen@~0.0.15", + "from": "escodegen@>=0.0.15 <0.1.0", "dependencies": { "estraverse": { "version": "1.3.2", - "from": "estraverse@~1.3.0" + "from": "estraverse@>=1.3.0 <1.4.0" }, "source-map": { - "version": "0.1.33", - "from": "source-map@>= 0.1.2", + "version": "0.1.40", + "from": "source-map@>=0.1.2", "dependencies": { "amdefine": { "version": "0.1.0", @@ -44,71 +104,11 @@ }, "estraverse": { "version": "0.0.4", - "from": "estraverse@~0.0.4" + "from": "estraverse@>=0.0.4 <0.1.0" } } } } - }, - "ometajs": { - "version": "3.3.5", - "from": "ometajs@~3.3.5", - "dependencies": { - "coa": { - "version": "0.3.9", - "from": "coa@0.3.x" - }, - "q": { - "version": "0.8.12", - "from": "q@0.8.x" - }, - "uglify-js": { - "version": "1.3.5", - "from": "uglify-js@1.3.x" - } - } - }, - "dom-js": { - "version": "0.0.9", - "from": "dom-js@~0.0.9", - "dependencies": { - "sax": { - "version": "0.6.0", - "from": "sax@>=0.1.5" - } - } - }, - "estraverse": { - "version": "1.5.0", - "from": "estraverse@~1.5.0" - }, - "esprima": { - "version": "1.0.4", - "from": "esprima@~1.0.4" - }, - "escodegen": { - "version": "1.2.0", - "from": "escodegen@~1.2.0", - "dependencies": { - "esutils": { - "version": "1.0.0", - "from": "esutils@~1.0.0" - }, - "source-map": { - "version": "0.1.33", - "from": "source-map@~0.1.30", - "dependencies": { - "amdefine": { - "version": "0.1.0", - "from": "amdefine@>=0.0.4" - } - } - } - } - }, - "bem-environ": { - "version": "1.4.0", - "from": "bem-environ@~1.4.0" } } } diff --git a/package.json b/package.json index 3e165cbc..215b1a0c 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "private": true, "dependencies": { - "xjst": "~0.5.19", + "xjst": "~0.5.24", "ometajs": "~3.3.5", "dom-js": "~0.0.9", "estraverse": "~1.5.0", @@ -12,11 +12,11 @@ "bem-environ": "~1.4.0" }, "devDependencies": { - "bem-sets": "~0.1.3", + "bem-sets": "~0.2.2", "mocha": "1.2.x", "benchmark": "~1.0.0", "microtime": "~0.3.1", - "bem": "~0.7.8" + "bem": "0.8.x" }, "scripts": { "test": "mocha --ui tdd --growl --reporter spec blocks-common/i-bem/__html/test/*-test.js blocks-common/i-bem/__i18n/test/test-*.js",