diff --git a/src/options.js b/src/options.js index f66abfbb..d5adaae5 100644 --- a/src/options.js +++ b/src/options.js @@ -116,5 +116,10 @@ $ rt [ ...] []`, type: 'String', default: reactNativeSupport.default, description: `'React native version to generate code for (${Object.keys(reactNativeSupport).join(', ')})'` + }, { + option: 'autobind', + type: 'Boolean', + default: 'false', + description: 'Automatically bind event handlers to components' }] }); diff --git a/src/reactTemplates.js b/src/reactTemplates.js index 6fec433a..32e1c633 100644 --- a/src/reactTemplates.js +++ b/src/reactTemplates.js @@ -199,18 +199,28 @@ function generateProps(node, context) { } function handleEventHandler(val, context, node, key) { - const funcParts = val.split('=>'); - if (funcParts.length !== 2) { - throw RTCodeError.build(context, node, `when using 'on' events, use lambda '(p1,p2)=>body' notation or use {} to return a callback function. error: [${key}='${val}']`); - } - const evtParams = funcParts[0].replace('(', '').replace(')', '').trim(); - const funcBody = funcParts[1].trim(); - let params = context.boundParams; - if (evtParams.trim() !== '') { - params = params.concat([evtParams.trim()]); + let handlerString; + if (_.startsWith(val, 'this.')) { + if (context.options.autobind) { + handlerString = `${val}.bind(this)`; + } else { + throw RTCodeError.build(context, node, "'this.handler' syntax allowed only when the --autobind is on, use {} to return a callback function."); + } + } else { + const funcParts = val.split('=>'); + if (funcParts.length !== 2) { + throw RTCodeError.build(context, node, `when using 'on' events, use lambda '(p1,p2)=>body' notation or 'this.handler'; otherwise use {} to return a callback function. error: [${key}='${val}']`); + } + const evtParams = funcParts[0].replace('(', '').replace(')', '').trim(); + const funcBody = funcParts[1].trim(); + let params = context.boundParams; + if (evtParams.trim() !== '') { + params = params.concat([evtParams.trim()]); + } + const generatedFuncName = generateInjectedFunc(context, key, funcBody, params); + handlerString = genBind(generatedFuncName, context.boundParams); } - const generatedFuncName = generateInjectedFunc(context, key, funcBody, params); - return genBind(generatedFuncName, context.boundParams); + return handlerString; } function genBind(func, args) { diff --git a/test/data/autobind.rt b/test/data/autobind.rt new file mode 100644 index 00000000..b740b7d6 --- /dev/null +++ b/test/data/autobind.rt @@ -0,0 +1,2 @@ +
+ diff --git a/test/data/autobind.rt.js b/test/data/autobind.rt.js new file mode 100644 index 00000000..1d9eb061 --- /dev/null +++ b/test/data/autobind.rt.js @@ -0,0 +1,10 @@ +define([ + 'react', + 'lodash' +], function (React, _) { + 'use strict'; + return function () { + return React.createElement('div', { 'onKeyDown': this.handleKeyDown.bind(this) }); + }; +}); + diff --git a/test/data/invalid/invalid-autobind.rt b/test/data/invalid/invalid-autobind.rt new file mode 100644 index 00000000..46a366c1 --- /dev/null +++ b/test/data/invalid/invalid-autobind.rt @@ -0,0 +1,4 @@ +
+ This should trigger an error because evaluated with no autobinding (--autobind=false) +
+ diff --git a/test/src/rt.invalid.spec.js b/test/src/rt.invalid.spec.js index 4ffa7d0c..66ccc558 100644 --- a/test/src/rt.invalid.spec.js +++ b/test/src/rt.invalid.spec.js @@ -19,7 +19,8 @@ module.exports = { {file: 'invalid-scope.rt', issue: new RTCodeError("invalid scope part 'a in a in a'", 0, 35, 1, 1)}, {file: 'invalid-html.rt', issue: new RTCodeError('Document should have a root element', -1, -1, -1, -1)}, {file: 'invalid-exp.rt', issue: new RTCodeError("Failed to parse text '\n {z\n'", 5, 13, 1, 6)}, - {file: 'invalid-lambda.rt', issue: new RTCodeError("when using 'on' events, use lambda '(p1,p2)=>body' notation or use {} to return a callback function. error: [onClick='']", 0, 23, 1, 1)}, + {file: 'invalid-lambda.rt', issue: new RTCodeError("when using 'on' events, use lambda '(p1,p2)=>body' notation or 'this.handler'; otherwise use {} to return a callback function. error: [onClick='']", 0, 23, 1, 1)}, + {file: 'invalid-autobind.rt', issue: new RTCodeError("'this.handler' syntax allowed only when the --autobind is on, use {} to return a callback function.", 0, 132, 1, 1)}, {file: 'invalid-js.rt', issue: new RTCodeError('Unexpected token ILLEGAL', 0, 32, 1, 1)}, {file: 'invalid-single-root.rt', issue: new RTCodeError('Document should have no more than a single root element', 12, 23, 2, 1)}, {file: 'invalid-repeat.rt', issue: new RTCodeError('rt-repeat invalid \'in\' expression \'a in b in c\'', 0, 35, 1, 1)}, diff --git a/test/src/rt.valid.spec.js b/test/src/rt.valid.spec.js index 165bfd5d..533f1ec4 100644 --- a/test/src/rt.valid.spec.js +++ b/test/src/rt.valid.spec.js @@ -33,6 +33,14 @@ module.exports = { testFiles(t, files); }); + test('autobinding conversion test', t => { + const options = { + autobind: true + }; + const files = ['autobind.rt']; + testFiles(t, files, options); + }); + test('prop template conversion test', t => { const options = { propTemplates: {