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..8686efb --- /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`. + +## 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 = /