diff --git a/README.md b/README.md
index 96c02c0..1d1a080 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,7 @@ http://plugins.jetbrains.com/plugin/7648
* [rt-props](#rt-props)
* [rt-class](#rt-class)
* [rt-require](#rt-require)
+ * [rt-template](#rt-template)
* [styles](#styles)
* [event handlers](#event-handlers)
@@ -66,6 +67,9 @@ In most cases, this package will be wrapped in a build task, so CLI will not be
* Browserify plugin: [react-templatify](https://www.npmjs.com/package/react-templatify)
* Webpack loader : [react-templates-loader](https://github.com/AlexanderPavlenko/react-templates-loader)
+### Use React Templates for Native Apps?
+You can get all the react templates functionality and more. [Click here for more info](https://github.com/wix/react-templates/blob/gh-pages/docs/native.md)
+
# Template directives and syntax
## Any valid HTML is a template
@@ -399,7 +403,7 @@ define([
```
-## properties template functions
+## rt-template, and defining properties template functions
In cases you'd like to use a property that accepts a function and return renderable React component.
You should use a **rt-template** tag that will let you do exactly that: ``.
diff --git a/docs/native.md b/docs/native.md
new file mode 100644
index 0000000..88f3079
--- /dev/null
+++ b/docs/native.md
@@ -0,0 +1,80 @@
+# React Template for Native Apps
+
+In order to use React Templates for [React Native](https://facebook.github.io/react-native/) you should set the `native` option to true.
+In native mode the default `modules` option is set to `commonjs` and the default `react-import-path` is set to `react-native`.
+
+## Default properties templates configuration
+
+In native mode we define a default properties template configuration in order to easily write native templates.
+
+```json
+ {
+ ListView: {
+ Row: {prop: 'renderRow', arguments: ['rowData', 'sectionID', 'rowID', 'highlightRow']},
+ Footer: {prop: 'renderFooter', arguments: []},
+ Header: {prop: 'renderHeader', arguments: []},
+ ScrollComponent: {prop: 'renderScrollComponent', arguments: ['props']},
+ SectionHeader: {prop: 'renderSectionHeader', arguments: ['sectionData', 'sectionID']},
+ Separator: {prop: 'renderSeparator', arguments: ['sectionID', 'rowID', 'adjacentRowHighlighted']}
+ }
+ }
+```
+
+With this configuration you can write your ListView component as follow:
+
+##### With default arguments:
+
+```html
+
+
+
+ {rowData}
+
+
+
+```
+
+###### Compiled:
+```javascript
+'use strict';
+var React = require('react-native');
+var _ = require('lodash');
+function renderRow1(rowData) {
+ return React.createElement(React.Text, {}, rowData);
+}
+module.exports = function () {
+ return React.createElement(React.View, {}, React.createElement(React.ListView, {
+ 'dataSource': this.state.dataSource,
+ 'renderRow': renderRow1.bind(this)
+ }));
+};
+```
+
+##### With custom arguments:
+
+```html
+
+
+
+ {item}
+
+
+
+```
+
+###### Compiled:
+```javascript
+'use strict';
+var React = require('react-native');
+var _ = require('lodash');
+function renderRow1(item) {
+ return React.createElement(React.Text, {}, item);
+}
+module.exports = function () {
+ return React.createElement(React.View, {}, React.createElement(React.ListView, {
+ 'dataSource': this.state.dataSource,
+ 'renderRow': renderRow1.bind(this)
+ }));
+};
+```
+
diff --git a/package.json b/package.json
index c6efc9f..39b2b6c 100644
--- a/package.json
+++ b/package.json
@@ -47,6 +47,7 @@
"grunt-node-tap": "^0.1.61",
"istanbul": "^0.3.17",
"react": "^0.12.2",
+ "react-native": "^0.9.0",
"tape": "^4.0.1"
},
"keywords": [
diff --git a/src/options.js b/src/options.js
index 36a7054..d3934c4 100644
--- a/src/options.js
+++ b/src/options.js
@@ -11,6 +11,7 @@
var optionator = require('optionator');
var pkg = require('../package.json');
var reactDOMSupport = require('./reactDOMSupport');
+var reactNativeSupport = require('./reactNativeSupport');
//------------------------------------------------------------------------------
// Initialization and Public Interface
@@ -74,7 +75,7 @@ module.exports = optionator({
option: 'target-version',
alias: 't',
type: 'String',
- default: '0.13.1',
+ default: reactDOMSupport.default,
description: 'React version to generate code for (' + Object.keys(reactDOMSupport).join(', ') + ')'
}, {
option: 'list-target-version',
@@ -101,5 +102,16 @@ module.exports = optionator({
default: 'lodash',
type: 'String',
description: 'Dependency path for importing lodash.'
+ }, {
+ option: 'native',
+ alias: 'rn',
+ type: 'Boolean',
+ description: 'Renders react native templates.'
+ }, {
+ option: 'native-target-version',
+ alias: 'rnv',
+ type: 'String',
+ default: reactNativeSupport.default,
+ description: 'React native version to generate code for (' + Object.keys(reactNativeSupport).join(', ') + ')'
}]
});
diff --git a/src/reactDOMSupport.js b/src/reactDOMSupport.js
index 781e35d..3c2522d 100644
--- a/src/reactDOMSupport.js
+++ b/src/reactDOMSupport.js
@@ -17,7 +17,8 @@ var versions = {
'0.11.2': ver0_11_2,
'0.11.1': ver0_11_0,
'0.11.0': ver0_11_0,
- '0.10.0': ver0_10_0
+ '0.10.0': ver0_10_0,
+ default: '0.13.1'
};
module.exports = versions;
\ No newline at end of file
diff --git a/src/reactNativeSupport.js b/src/reactNativeSupport.js
new file mode 100644
index 0000000..c2a6a42
--- /dev/null
+++ b/src/reactNativeSupport.js
@@ -0,0 +1,10 @@
+'use strict';
+
+var ver0_9_0 = ['ActivityIndicatorIOS', 'DatePickerIOS', 'Image', 'ListView', 'MapView', 'Navigator', 'NavigatorIOS', 'PickerIOS', 'ScrollView', 'SliderIOS', 'SwitchIOS', 'TabBarIOS', 'Text', 'TextInput', 'TouchableHighlight', 'TouchableOpacity', 'TouchableWithoutFeedback', 'View', 'WebView'];
+
+var versions = {
+ '0.9.0': ver0_9_0,
+ default: '0.9.0'
+};
+
+module.exports = versions;
\ No newline at end of file
diff --git a/src/reactPropTemplates.js b/src/reactPropTemplates.js
new file mode 100644
index 0000000..e3029e4
--- /dev/null
+++ b/src/reactPropTemplates.js
@@ -0,0 +1,19 @@
+'use strict';
+
+var native = {
+ '0.9.0': {
+ ListView: {
+ Row: {prop: 'renderRow', arguments: ['rowData', 'sectionID', 'rowID', 'highlightRow']},
+ Footer: {prop: 'renderFooter', arguments: []},
+ Header: {prop: 'renderHeader', arguments: []},
+ ScrollComponent: {prop: 'renderScrollComponent', arguments: ['props']},
+ SectionHeader: {prop: 'renderSectionHeader', arguments: ['sectionData', 'sectionID']},
+ Separator: {prop: 'renderSeparator', arguments: ['sectionID', 'rowID', 'adjacentRowHighlighted']}
+ }
+ }
+};
+
+module.exports = {
+ native: native,
+ dom: {}
+};
\ No newline at end of file
diff --git a/src/reactTemplates.js b/src/reactTemplates.js
index 69a0035..cd78bb2 100644
--- a/src/reactTemplates.js
+++ b/src/reactTemplates.js
@@ -7,6 +7,8 @@ var _ = require('lodash');
var esprima = require('esprima-harmony');
var escodegen = require('escodegen');
var reactDOMSupport = require('./reactDOMSupport');
+var reactNativeSupport = require('./reactNativeSupport');
+var reactPropTemplates = require('./reactPropTemplates');
var stringUtils = require('./stringUtils');
var rtError = require('./RTCodeError');
var RTCodeError = rtError.RTCodeError;
@@ -55,7 +57,29 @@ var scopeAttr = 'rt-scope';
var propsAttr = 'rt-props';
var templateNode = 'rt-template';
-var defaultOptions = {modules: 'amd', version: false, force: false, format: 'stylish', targetVersion: '0.13.1', reactImportPath: 'react/addons', lodashImportPath: 'lodash'};
+function getOptions(options) {
+ options = options || {};
+ var defaultOptions = {
+ modules: options.native ? 'commonjs': 'amd',
+ version: false,
+ force: false,
+ format: 'stylish',
+ targetVersion: reactDOMSupport.default,
+ reactImportPath: options.native ? 'react-native' : 'react/addons',
+ lodashImportPath: 'lodash',
+ native: false,
+ nativeTargetVersion: reactNativeSupport.default
+ };
+
+ var finalOptions = _.defaults({}, options, defaultOptions);
+
+ var defaultPropTemplates = finalOptions.native ?
+ reactPropTemplates.native[finalOptions.nativeTargetVersion] :
+ reactPropTemplates.dom[finalOptions.targetVersion];
+
+ finalOptions.propTemplates = _.defaults({}, options.propTemplates, defaultPropTemplates);
+ return finalOptions;
+}
/**
* @param {Context} context
@@ -181,7 +205,7 @@ function generateInjectedFunc(context, namePrefix, body, params) {
}
function generateTemplateProps(node, context) {
- var propTemplateDefinition = context.options.templates && context.options.templates[node.name];
+ var propTemplateDefinition = context.options.propTemplates[node.name];
var propertiesTemplates = _(node.children)
.map(function (child, index) {
var templateProp = null;
@@ -190,7 +214,7 @@ function generateTemplateProps(node, context) {
throw RTCodeError.build('rt-template must have a prop attribute', context, child);
}
- var childTemplate = _.find(context.options.templates, {prop: child.attribs.prop}) || {arguments: []};
+ var childTemplate = _.find(context.options.propTemplates, {prop: child.attribs.prop}) || {arguments: []};
templateProp = {
prop: child.attribs.prop,
arguments: (child.attribs.arguments ? child.attribs.arguments.split(',') : childTemplate.arguments) || []
@@ -300,6 +324,10 @@ function generateProps(node, context) {
* @return {string}
*/
function convertTagNameToConstructor(tagName, context) {
+ if (context.options.native) {
+ return _.includes(reactNativeSupport[context.options.nativeTargetVersion], tagName) ? 'React.' + tagName : tagName;
+ }
+
var isHtmlTag = _.includes(reactDOMSupport[context.options.targetVersion], tagName);
if (shouldUseCreateElement(context)) {
isHtmlTag = isHtmlTag || tagName.match(/^\w+(-\w+)$/);
@@ -508,7 +536,7 @@ function convertTemplateToReact(html, options) {
*/
function convertRT(html, reportContext, options) {
var rootNode = cheerio.load(html, {lowerCaseTags: false, lowerCaseAttributeNames: false, xmlMode: true, withStartIndices: true});
- options = _.defaults({}, options, defaultOptions);
+ options = getOptions(options);
var defaultDefines = {};
defaultDefines[options.reactImportPath] = 'React';
@@ -576,7 +604,7 @@ function convertRT(html, reportContext, options) {
}
function convertJSRTToJS(text, reportContext, options) {
- options = _.defaults({}, options, defaultOptions);
+ options = getOptions(options)
options.modules = 'jsrt';
var templateMatcherJSRT = /([^]*?)<\/template>/gm;
var code = text.replace(templateMatcherJSRT, function (template, html) {
diff --git a/test/data/listViewAndCustomTemplate.rt b/test/data/listViewAndCustomTemplate.rt
new file mode 100644
index 0000000..d365b78
--- /dev/null
+++ b/test/data/listViewAndCustomTemplate.rt
@@ -0,0 +1,12 @@
+
+
+
+ {rowData}
+
+
+
+
+ {item}
+
+
+
\ No newline at end of file
diff --git a/test/data/listViewAndCustomTemplate.rt.js b/test/data/listViewAndCustomTemplate.rt.js
new file mode 100644
index 0000000..397f356
--- /dev/null
+++ b/test/data/listViewAndCustomTemplate.rt.js
@@ -0,0 +1,22 @@
+'use strict';
+var React = require('react-native');
+var _ = require('lodash');
+function renderRow1(rowData) {
+ return React.createElement(React.Text, {}, rowData);
+}
+function renderRow2(item) {
+ return React.createElement(React.Text, {}, item);
+}
+module.exports = function () {
+ return React.createElement(React.View, {}, React.createElement(React.ListView, {
+ 'dataSource': this.state.dataSource,
+ 'renderRow': renderRow1.bind(this)
+ }), React.createElement(MyComp, {
+ 'data': [
+ 1,
+ 2,
+ 3
+ ],
+ 'renderRow': renderRow2.bind(this)
+ }));
+};
\ No newline at end of file
diff --git a/test/data/listViewTemplate.rt b/test/data/listViewTemplate.rt
new file mode 100644
index 0000000..bdd57be
--- /dev/null
+++ b/test/data/listViewTemplate.rt
@@ -0,0 +1,7 @@
+
+
+
+ {rowData}
+
+
+
\ No newline at end of file
diff --git a/test/data/listViewTemplate.rt.js b/test/data/listViewTemplate.rt.js
new file mode 100644
index 0000000..37c7c7b
--- /dev/null
+++ b/test/data/listViewTemplate.rt.js
@@ -0,0 +1,12 @@
+'use strict';
+var React = require('react-native');
+var _ = require('lodash');
+function renderRow1(rowData) {
+ return React.createElement(React.Text, {}, rowData);
+}
+module.exports = function () {
+ return React.createElement(React.View, {}, React.createElement(React.ListView, {
+ 'dataSource': this.state.dataSource,
+ 'renderRow': renderRow1.bind(this)
+ }));
+};
\ No newline at end of file
diff --git a/test/data/nativeView.rt b/test/data/nativeView.rt
new file mode 100644
index 0000000..a7b0a4f
--- /dev/null
+++ b/test/data/nativeView.rt
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/test/data/nativeView.rt.js b/test/data/nativeView.rt.js
new file mode 100644
index 0000000..61eb17b
--- /dev/null
+++ b/test/data/nativeView.rt.js
@@ -0,0 +1,6 @@
+'use strict';
+var React = require('react-native');
+var _ = require('lodash');
+module.exports = function () {
+ return React.createElement(React.View, {});
+};
\ No newline at end of file
diff --git a/test/src/test.js b/test/src/test.js
index a15820d..9ab3d71 100644
--- a/test/src/test.js
+++ b/test/src/test.js
@@ -116,7 +116,7 @@ test('conversion test', function (t) {
test('prop template conversion test', function (t) {
var options = {
- templates: {
+ propTemplates: {
List: {
Row: {prop: 'renderRow', arguments: ['rowData']}
}
@@ -136,6 +136,30 @@ test('prop template conversion test', function (t) {
}
});
+test('conversion test - native', function (t) {
+ var options = {
+ propTemplates: {
+ MyComp: {
+ Row: {prop: 'renderRow', arguments: ['rowData']}
+ }
+ },
+ native: true
+ };
+
+ var files = ['nativeView.rt', 'listViewTemplate.rt', 'listViewAndCustomTemplate.rt'];
+ t.plan(files.length);
+ files.forEach(check);
+
+ function check(testFile) {
+ var filename = path.join(dataPath, testFile);
+ var html = readFileNormalized(filename);
+ var expected = readFileNormalized(filename + '.js');
+// var expected = fs.readFileSync(filename.replace(".html", ".js")).toString();
+ var actual = reactTemplates.convertTemplateToReact(html, options).replace(/\r/g, '').trim();
+ compareAndWrite(t, actual, expected, filename);
+ }
+});
+
/**
* @param {*} t
* @param {string} actual