From 05b1ab5c86026c9d0def800843162cd8a5883d74 Mon Sep 17 00:00:00 2001 From: yatra9 Date: Wed, 8 Mar 2017 00:24:53 +0900 Subject: [PATCH] #214, rt-template with in-place child component --- README.md | 29 +++++++++++++++++++ src/reactTemplates.js | 24 +++++++++++---- test/data/invalid/invalid-rt-template-3.rt | 5 ++++ test/data/invalid/invalid-rt-template-4.rt | 5 ++++ test/data/propTemplates/inplaceTemplate.rt | 5 ++++ test/data/propTemplates/inplaceTemplate.rt.js | 12 ++++++++ .../propTemplates/inplaceTemplateInScope.rt | 5 ++++ .../inplaceTemplateInScope.rt.js | 16 ++++++++++ test/src/rt.invalid.spec.js | 2 ++ test/src/rt.valid.spec.js | 5 +++- 10 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 test/data/invalid/invalid-rt-template-3.rt create mode 100644 test/data/invalid/invalid-rt-template-4.rt create mode 100644 test/data/propTemplates/inplaceTemplate.rt create mode 100644 test/data/propTemplates/inplaceTemplate.rt.js create mode 100644 test/data/propTemplates/inplaceTemplateInScope.rt create mode 100644 test/data/propTemplates/inplaceTemplateInScope.rt.js diff --git a/README.md b/README.md index 94bf8571..bd6393ee 100644 --- a/README.md +++ b/README.md @@ -655,6 +655,35 @@ export default function () { }; ``` +### defining in-place child properties +In cases you'd like to set a renderable React component itself (not a function) as a child property, +You should use a **rt-template** tag with **in-place** attribute: ``. +Note that **argument** attribute and **in-place** attribute cannot be set simulatenously. + +###### Sample: +```html +
+ +
some
+
+
+``` +###### Compiled (AMD): +```javascript +define([ + 'react', + 'lodash' +], function (React, _) { + 'use strict'; + return function () { + function templateProp1() { + return React.createElement('div', {}, 'some'); + } + return React.createElement('div', { 'templateProp': templateProp1.call(this) }); + }; +}); +``` + ## Contributing See the [Contributing page](CONTRIBUTING.md). diff --git a/src/reactTemplates.js b/src/reactTemplates.js index 06bff01a..4884a86b 100644 --- a/src/reactTemplates.js +++ b/src/reactTemplates.js @@ -49,6 +49,9 @@ const classAttr = 'class'; const scopeAttr = 'rt-scope'; const propsAttr = 'rt-props'; const templateNode = 'rt-template'; +const templateNode_PropAttr = 'prop'; +const templateNode_ArgumentsAttr = 'arguments'; +const templateNode_InPlaceAttr = 'in-place'; const virtualNode = 'rt-virtual'; const includeNode = 'rt-include'; const includeSrcAttr = 'src'; @@ -123,17 +126,21 @@ function generateTemplateProps(node, context) { .map((child, index) => { let templateProp = null; if (child.name === templateNode) { // Generic explicit template tag - if (!_.has(child.attribs, 'prop')) { - throw RTCodeError.build(context, child, 'rt-template must have a prop attribute'); + if (!_.has(child.attribs, templateNode_PropAttr)) { + throw RTCodeError.build(context, child, "'rt-template' should have a " + templateNode_PropAttr + ' attribute'); } if (_.filter(child.children, {type: 'tag'}).length !== 1) { throw RTCodeError.build(context, child, "'rt-template' should have a single non-text element as direct child"); } + if (_.has(child.attribs, templateNode_ArgumentsAttr) && _.has(child.attribs, templateNode_InPlaceAttr)) { + throw RTCodeError.build(context, child, "'" + templateNode_ArgumentsAttr + "' and '" + templateNode_InPlaceAttr + "' cannot coexist together"); + } - const childTemplate = _.find(context.options.propTemplates, {prop: child.attribs.prop}) || {arguments: []}; + const childTemplate = _.find(context.options.propTemplates, {prop: child.attribs[templateNode_PropAttr]}) || {arguments: []}; templateProp = { - prop: child.attribs.prop, - arguments: (child.attribs.arguments ? child.attribs.arguments.split(',') : childTemplate.arguments) || [] + prop: child.attribs[templateNode_PropAttr], + arguments: (child.attribs[templateNode_ArgumentsAttr] ? child.attribs[templateNode_ArgumentsAttr].split(',') : childTemplate.arguments) || [], + inplace: _.has(child.attribs, templateNode_InPlaceAttr) }; } else if (propTemplateDefinition && propTemplateDefinition[child.name]) { // Implicit child template from configuration templateProp = { @@ -161,7 +168,7 @@ function generateTemplateProps(node, context) { context.boundParams = oldBoundParams; const generatedFuncName = generateInjectedFunc(context, templateProp.prop, functionBody, functionParams); - props[templateProp.prop] = genBind(generatedFuncName, _.values(context.boundParams)); + props[templateProp.prop] = templateProp.inplace ? genCall(generatedFuncName, _.values(context.boundParams)) : genBind(generatedFuncName, _.values(context.boundParams)); // Remove the template child from the children definition. node.children.splice(templateProp.childIndex, 1); @@ -240,6 +247,11 @@ function genBind(func, args) { return `${func}.bind(${bindArgs.join(',')})`; } +function genCall(func, args) { + const bindArgs = ['this'].concat(args); + return `${func}.call(${bindArgs.join(',')})`; +} + function handleStyleProp(val, node, context) { const styleStr = _(val) .split(';') diff --git a/test/data/invalid/invalid-rt-template-3.rt b/test/data/invalid/invalid-rt-template-3.rt new file mode 100644 index 00000000..2c12688b --- /dev/null +++ b/test/data/invalid/invalid-rt-template-3.rt @@ -0,0 +1,5 @@ +
+ +
1
+
+
diff --git a/test/data/invalid/invalid-rt-template-4.rt b/test/data/invalid/invalid-rt-template-4.rt new file mode 100644 index 00000000..0ab97a60 --- /dev/null +++ b/test/data/invalid/invalid-rt-template-4.rt @@ -0,0 +1,5 @@ +
+ +
1
+
+
diff --git a/test/data/propTemplates/inplaceTemplate.rt b/test/data/propTemplates/inplaceTemplate.rt new file mode 100644 index 00000000..11e8cf2b --- /dev/null +++ b/test/data/propTemplates/inplaceTemplate.rt @@ -0,0 +1,5 @@ +
+ +
some
+
+
\ No newline at end of file diff --git a/test/data/propTemplates/inplaceTemplate.rt.js b/test/data/propTemplates/inplaceTemplate.rt.js new file mode 100644 index 00000000..671cba33 --- /dev/null +++ b/test/data/propTemplates/inplaceTemplate.rt.js @@ -0,0 +1,12 @@ +define([ + 'react', + 'lodash' +], function (React, _) { + 'use strict'; + return function () { + function templateProp1() { + return React.createElement('div', {}, 'some'); + } + return React.createElement('div', { 'templateProp': templateProp1.call(this) }); + }; +}); diff --git a/test/data/propTemplates/inplaceTemplateInScope.rt b/test/data/propTemplates/inplaceTemplateInScope.rt new file mode 100644 index 00000000..34aef5d2 --- /dev/null +++ b/test/data/propTemplates/inplaceTemplateInScope.rt @@ -0,0 +1,5 @@ +
+ +
Name: {name} some
+
+
\ No newline at end of file diff --git a/test/data/propTemplates/inplaceTemplateInScope.rt.js b/test/data/propTemplates/inplaceTemplateInScope.rt.js new file mode 100644 index 00000000..e6ebb767 --- /dev/null +++ b/test/data/propTemplates/inplaceTemplateInScope.rt.js @@ -0,0 +1,16 @@ +define([ + 'react', + 'lodash' +], function (React, _) { + 'use strict'; + return function () { + function templateProp1(name) { + return React.createElement('div', {}, 'Name: ', name, ' some'); + } + function scopeName2() { + var name = 'boten'; + return React.createElement('div', { 'templateProp': templateProp1.call(this, name) }); + } + return scopeName2.apply(this, []); + }; +}); \ No newline at end of file diff --git a/test/src/rt.invalid.spec.js b/test/src/rt.invalid.spec.js index 08e75469..49303e5e 100644 --- a/test/src/rt.invalid.spec.js +++ b/test/src/rt.invalid.spec.js @@ -33,6 +33,8 @@ module.exports = { {file: 'invalid-rt-import-4.rt', issue: new RTCodeError("'rt-import' must be a toplevel node", 9, 54, 2, 4)}, {file: 'invalid-rt-template-1.rt', issue: new RTCodeError("'rt-template' should have a single non-text element as direct child", 9, 88, 2, 4)}, {file: 'invalid-rt-template-2.rt', issue: new RTCodeError("'rt-template' should have a single non-text element as direct child", 9, 90, 2, 4)}, + {file: 'invalid-rt-template-3.rt', issue: new RTCodeError("'rt-template' should have a prop attribute", 9, 72, 2, 4)}, + {file: 'invalid-rt-template-4.rt', issue: new RTCodeError("'arguments' and 'in-place' cannot coexist together", 9, 93, 2, 4)}, {file: 'invalid-brace.rt', issue: new RTCodeError('Unexpected end of input', 128, 163, 5, 11)}, {file: 'invalid-style-1.rt', issue: new RTCodeError('Unexpected token ILLEGAL', 10, 39, 2, 5)}, {file: 'invalid-style-2.rt', issue: new RTCodeError('style attribute keys cannot contain { } expressions', 35, 68, 2, 5)}, diff --git a/test/src/rt.valid.spec.js b/test/src/rt.valid.spec.js index 8335962f..18be867e 100644 --- a/test/src/rt.valid.spec.js +++ b/test/src/rt.valid.spec.js @@ -42,6 +42,7 @@ module.exports = { test('autobinding conversion test', t => { const options = { + modules: 'amd', autobind: true }; const files = ['autobind.rt']; @@ -62,7 +63,9 @@ module.exports = { 'templateInScope.rt', 'implicitTemplate.rt', 'twoTemplates.rt', - 'siblingTemplates.rt' + 'siblingTemplates.rt', + 'inplaceTemplate.rt', + 'inplaceTemplateInScope.rt' ].map(file => path.join('propTemplates', file)); testFiles(t, files, options); });