Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
c7c10d0bfa
34
README.md
34
README.md
|
@ -41,11 +41,11 @@ Writing any html is a valid template. This does not apply to event handlers ("on
|
|||
In html attributes and text, you can replace context by a javascript expression. You do this by wrapping it in {}. If this is inside an attribute, it still needs to be wrapped by quotes. In text, you can just use it.
|
||||
|
||||
###### Sample:
|
||||
```
|
||||
```html
|
||||
<a href="{this.state.linkRef}">{this.state.linkText}</a>
|
||||
```
|
||||
###### Compiled:
|
||||
```
|
||||
```javascript
|
||||
define([
|
||||
'react',
|
||||
'lodash'
|
||||
|
@ -63,11 +63,11 @@ This gives you the ability to add conditions to a sub-tree of html. If the condi
|
|||
|
||||
|
||||
###### Sample:
|
||||
```
|
||||
```html
|
||||
<div rt-if="this.state.resultCode === 200">Success!</div>
|
||||
```
|
||||
###### Compiled:
|
||||
```
|
||||
```javascript
|
||||
define([
|
||||
'react',
|
||||
'lodash'
|
||||
|
@ -83,11 +83,11 @@ define([
|
|||
Repeats a node with its subtree for each item in an array. This is implemented by creating a method that is passed to a map call as a callback. It creates a real context for the iterated variable. The syntax is `rt-repeat="itemVar in arrayExpr"`. Within the scope of the element, `itemVar` will be available in javascript context, and also an `itemVarIndex` will be created to represent the index of the item. If the definition is `myNum in this.getMyNumbers()`, than there will be 2 variables in the scope: `myNum` and `myNumIndex`. This naming is used to allow nesting of repeat expression with access to all levels.
|
||||
|
||||
###### Sample:
|
||||
```
|
||||
```html
|
||||
<div rt-repeat="myNum in this.getMyNumbers()">{myNumIndex}. myNum</div>
|
||||
```
|
||||
###### Compiled:
|
||||
```
|
||||
```javascript
|
||||
define([
|
||||
'react',
|
||||
'lodash'
|
||||
|
@ -106,14 +106,14 @@ define([
|
|||
This directive creates a new javascript scope. It actually creates a new method for this scope, and calls it with its current context. The syntax is `rt-scope="expr1 as var1; expr2 as var2`. This gives a convenience method to shorten stuff up in a scope and make the code more readable. It also helps to execute an expression only once in a scope instead of every chunk that needs it.
|
||||
|
||||
###### Sample:
|
||||
```
|
||||
```html
|
||||
<div rt-repeat="rpt in array">
|
||||
<div rt-scope="')' as separator; rpt.val as val">{rptIndex}{separator} {val}</div>
|
||||
<div>'rpt' exists here, but not 'separator' and 'val'</div>
|
||||
</div>
|
||||
```
|
||||
###### Compiled:
|
||||
```
|
||||
```javascript
|
||||
define([
|
||||
'react',
|
||||
'lodash'
|
||||
|
@ -142,7 +142,7 @@ In order to reduce the boiler-plate code when programatically setting class name
|
|||
2. You cannot use class and rt-class on the same html element
|
||||
|
||||
###### Sample:
|
||||
```
|
||||
```html
|
||||
<div rt-scope="{blue: true, selected: this.isSelected()} as classes">
|
||||
These are logically equivalent
|
||||
<div rt-class="classes">Reference</div>
|
||||
|
@ -151,7 +151,7 @@ In order to reduce the boiler-plate code when programatically setting class name
|
|||
</div>
|
||||
```
|
||||
###### Compiled:
|
||||
```
|
||||
```javascript
|
||||
define([
|
||||
'react',
|
||||
'lodash'
|
||||
|
@ -178,7 +178,7 @@ define([
|
|||
In order to make it closer to html, we allow the settings of inline styles. In addition, this will take care of changing the styles from hyphen-style to camelCase style. If you'd like, you can still return an object from evaluation context. Please note that if you do it inline, you'll need to open single curly braces for the js context, and another for the object. Also, you'll need to use camelCase if using it that way
|
||||
|
||||
###### Sample:
|
||||
```
|
||||
```html
|
||||
<div>
|
||||
These are really equivalent
|
||||
<div style="color:white; line-height:{this.state.lineHeight}px">Inline</div>
|
||||
|
@ -186,7 +186,7 @@ In order to make it closer to html, we allow the settings of inline styles. In a
|
|||
</div>
|
||||
```
|
||||
###### Compiled:
|
||||
```
|
||||
```javascript
|
||||
define([
|
||||
'react',
|
||||
'lodash'
|
||||
|
@ -212,13 +212,13 @@ define([
|
|||
React event handlers accept function pointers. Therefore, when using event, you can just open an execution context and provide a pointer to a method. This would look like `onClick="{this.myClickHandler}"`. However, sometimes there's very little to do on click, or we just want to call a method with bound parameters. In that case, you can use a lambda notation, which will result in creating a react template creating a method for the handler. It does not have a performance impact, as the method is created once, and just bound to the context instead of created again. The lambda notation will look like this `onClick="(evt) => console.log(evt)"`. In this example, **evt** was the name you choose for the first argument that will be passed into your inline method. With browser events, this will most likely be the react synthetic event. However, if you expect a property that starts with **on**Something, then react-templates will treat it as an event handler. So if you have an event handler called **onBoxSelected** that will trigger an event with a row and column params, you can write `onBoxSelected="(row, col)=>this.doSomething(row,col)"`. You can use a no-param version as well `onClick="()=>console.log('just wanted to know it clicked')"`
|
||||
|
||||
###### Sample:
|
||||
```
|
||||
```html
|
||||
<div rt-repeat="item in items">
|
||||
<div onClick="()=>this.itemSelected(item)" onMouseDown="{this.mouseDownHandler}">
|
||||
</div>
|
||||
```
|
||||
###### Compiled:
|
||||
```
|
||||
```javascript
|
||||
define([
|
||||
'react',
|
||||
'lodash'
|
||||
|
@ -243,7 +243,7 @@ define([
|
|||
In many cases, you'd like to use either library code, or other components within your template. In order to do so, you can define a doctype and indicate dependencies. You do so by `<!doctype rt depVarName="depVarPath">`. After that, you will have **depVarName** in your scope. You can import react components and use them afterwords in the template as tag names. For example `<MySlider prop1="val1" onMyChange="{this.onSliderMoved}">`. This will also support nesting `<MyContainer><div>child</div><div>another</div></MyContainer>`. You will then be able to find the children in **this.props.children**.
|
||||
|
||||
###### Sample:
|
||||
```
|
||||
```html
|
||||
<!doctype rt MyComp="comps/myComp" utils="utils/utils">
|
||||
<MyComp rt-repeat="item in items">
|
||||
<div>{utils.toLower(item.name)}</div>
|
||||
|
@ -251,7 +251,7 @@ In many cases, you'd like to use either library code, or other components within
|
|||
|
||||
```
|
||||
###### Compiled:
|
||||
```
|
||||
```javascript
|
||||
define([
|
||||
'react',
|
||||
'lodash',
|
||||
|
@ -266,4 +266,4 @@ define([
|
|||
return _.map(items, repeatItem1.bind(this));
|
||||
};
|
||||
});
|
||||
```
|
||||
```
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>React templates - Image Search Sample</title>
|
||||
<link rel="stylesheet" href="playground.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="preloader" style="text-align: center;">Loading...</div>
|
||||
<div id="playground">
|
||||
</div>
|
||||
<script src="main.browser.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,93 @@
|
|||
var reactTemplates = require('../src/reactTemplates');
|
||||
var playgroundTemplate = require('./playground.rt.js');
|
||||
var React = require('react/addons');
|
||||
var _ = require('lodash');
|
||||
|
||||
|
||||
var html = "<div>hello</div>";
|
||||
var res = reactTemplates.convertTemplateToReact(html.trim());
|
||||
console.log(res);
|
||||
|
||||
function emptyFunc() {
|
||||
return null;
|
||||
}
|
||||
|
||||
function generateTemplateFunction(html) {
|
||||
try {
|
||||
var code = reactTemplates.convertTemplateToReact(html.trim().replace(/\r/g,""));
|
||||
var defineMap = {"react":React,"lodash":_};
|
||||
var define = function (requirementsNames,content) {
|
||||
var requirements = _.map(requirementsNames,function (reqName) {
|
||||
return defineMap[reqName];
|
||||
});
|
||||
return content.apply(this,requirements);
|
||||
};
|
||||
var res = eval(code);
|
||||
return res;
|
||||
} catch (e) {
|
||||
return emptyFunc
|
||||
}
|
||||
}
|
||||
|
||||
function generateRenderFunc(renderFunc) {
|
||||
return function() {
|
||||
var res = null;
|
||||
try {
|
||||
res = renderFunc.apply(this)
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
return React.DOM.div.apply(this, _.flatten([
|
||||
{},
|
||||
res
|
||||
]));
|
||||
}
|
||||
}
|
||||
var z = {getInitialState: function() {return {name:"reactTemplates"}}};
|
||||
var templateHTML = "<div>\n Have {_.filter(this.state.todos, {done:true}).length} todos done,\n and {_.filter(this.state.todos, {done:false}).length} not done\n <br/>\n <div rt-repeat=\"todo in this.state.todos\" key=\"{todo.key}\">\n <button onClick=\"(e)=>e.preventDefault(); this.remove(todo)\">x</button>\n <input type=\"checkbox\" checked=\"{todo.done}\" onChange=\"()=>this.toggleChecked(todoIndex)\"/>\n <span style=\"{todo.done ? {'text-decoration':'line-through'} : {} }\">{todo.value}</span>\n </div>\n <input key=\"myinput\" type=\"text\" onKeyDown=\"(e) => if (e.keyCode == 13) { this.add(); }\" valueLink=\"{this.linkState('edited')}\"/>\n <button onClick=\"(e)=>e.preventDefault(); this.add()\" >Add</button><br/>\n <button onClick=\"(e)=>e.preventDefault(); this.clearDone()\">Clear done</button>\n</div>";
|
||||
var templateProps = "{\n mixins: [React.addons.LinkedStateMixin],\n getInitialState: function () {\n return {edited: '', todos: [], counter: 0};\n },\n add: function () {\n if (this.state.edited.trim().length === 0) {\n return;\n }\n var newTodo = {value: this.state.edited, done: false, key: this.state.counter};\n this.setState({todos: this.state.todos.concat(newTodo), edited: '', counter: this.state.counter + 1});\n },\n remove: function (todo) {\n this.setState({todos: _.reject(this.state.todos, todo)});\n },\n toggleChecked: function (index) {\n var todos = _.cloneDeep(this.state.todos);\n todos[index].done = !todos[index].done;\n this.setState({todos: todos});\n },\n clearDone: function () {\n this.setState({todos: _.filter(this.state.todos, {done: false})});\n }\n}";
|
||||
|
||||
var Playground = React.createClass({
|
||||
|
||||
displayName: 'Playground',
|
||||
mixins: [React.addons.LinkedStateMixin],
|
||||
|
||||
updateSample: function (state) {
|
||||
|
||||
this.sampleFunc = generateTemplateFunction(state.templateHTML);
|
||||
this.sampleRender = generateRenderFunc(this.sampleFunc);
|
||||
var classBase = {};
|
||||
try {
|
||||
console.log(state.templateProps);
|
||||
classBase = eval("("+state.templateProps+")");
|
||||
/*if (typeof classBase !== 'Object') {
|
||||
throw "failed to eval";
|
||||
}*/
|
||||
} catch (e) {
|
||||
classBase = {};
|
||||
}
|
||||
classBase.render = this.sampleRender;
|
||||
console.log(classBase);
|
||||
this.sample = React.createClass(classBase);
|
||||
},
|
||||
|
||||
getInitialState: function () {
|
||||
var currentState = {
|
||||
templateHTML:templateHTML,
|
||||
templateProps: templateProps
|
||||
};
|
||||
this.updateSample(currentState);
|
||||
return currentState;
|
||||
},
|
||||
componentWillUpdate: function (nextProps,nextState) {
|
||||
if (nextState.templateHTML !== this.state.templateHTML || nextState.templateProps !== this.state.templateProps) {
|
||||
this.updateSample(nextState);
|
||||
}
|
||||
},
|
||||
|
||||
render: function () {
|
||||
return playgroundTemplate.apply(this);
|
||||
}
|
||||
});
|
||||
|
||||
React.render(Playground(),document.getElementById('playground'));
|
|
@ -0,0 +1,8 @@
|
|||
.hidden {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.large-text-area {
|
||||
width:800px;
|
||||
height:300px;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE rt PlaygroundSample="./playgroundSample">
|
||||
<div>
|
||||
<form>
|
||||
<textarea valueLink="{this.linkState('templateHTML')}" class="large-text-area"></textarea>
|
||||
<br/>
|
||||
<textarea valueLink="{this.linkState('templateProps')}" class="large-text-area"></textarea>
|
||||
</form>
|
||||
<this.sample>
|
||||
|
||||
</this.sample>
|
||||
</div>
|
|
@ -0,0 +1,13 @@
|
|||
var React = require('react');
|
||||
var _ = require('lodash');
|
||||
var PlaygroundSample = require('./playgroundSample');
|
||||
'use strict';
|
||||
module.exports = function () {
|
||||
return React.DOM.div({}, React.DOM.form({}, React.DOM.textarea({
|
||||
'valueLink': this.linkState('templateHTML'),
|
||||
'className': 'large-text-area'
|
||||
}), React.DOM.br({}), React.DOM.textarea({
|
||||
'valueLink': this.linkState('templateProps'),
|
||||
'className': 'large-text-area'
|
||||
})), this.sample({}));
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
var React = require('react/addons');
|
||||
var _ = require('lodash');
|
||||
var playgroundSample = React.createClass({
|
||||
componentWillReceiveProps: function (nextProps) {
|
||||
if (nextProps.stateString) {
|
||||
try {
|
||||
this.setState(JSON.parse(nextProps.stateString));
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
render: function () {
|
||||
var res = null;
|
||||
try {
|
||||
res = this.props.renderFunc.apply(this)
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
return React.DOM.div.apply(this, _.flatten([
|
||||
{},
|
||||
res
|
||||
]));
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = playgroundSample;
|
24
src/cli.js
24
src/cli.js
|
@ -8,15 +8,23 @@ var _ = require('lodash');
|
|||
var path = require('path');
|
||||
var reactTemplates = require('./reactTemplates');
|
||||
var pkg = require('../package.json');
|
||||
var options = {commonJS: false};
|
||||
|
||||
if (process.argv.length > 2) {
|
||||
if (process.argv.indexOf('-v') !== -1 || process.argv.indexOf('--version') !== -1) {
|
||||
console.log(pkg.version);
|
||||
} else if (process.argv.indexOf('-h') !== -1 || process.argv.indexOf('--help') !== -1) {
|
||||
printHelp();
|
||||
} else {
|
||||
_.forEach(process.argv.slice(2), handleSingleFile);
|
||||
}
|
||||
var files = [];
|
||||
_.forEach(process.argv.slice(2),function (param) {
|
||||
if (param === '-v' || param === '--version') {
|
||||
console.log(pkg.version);
|
||||
} else if (param === '-h' || param === '--help') {
|
||||
printHelp();
|
||||
} else if (param === '--common') {
|
||||
options.commonJS = true;
|
||||
} else {
|
||||
files.push(param);
|
||||
}
|
||||
});
|
||||
_.forEach(files,handleSingleFile);
|
||||
|
||||
} else {
|
||||
printHelp();
|
||||
}
|
||||
|
@ -41,7 +49,7 @@ function handleSingleFile(filename) {
|
|||
// var js = reactTemplates.convertTemplateToReact(html);
|
||||
// fs.writeFileSync(filename + '.js', js);
|
||||
try {
|
||||
reactTemplates.convertFile(filename, filename + '.js');
|
||||
reactTemplates.convertFile(filename, filename + '.js', options);
|
||||
} catch (e) {
|
||||
console.log('Error processing file: ' + filename + ', ' + e.description);
|
||||
}
|
||||
|
|
|
@ -16,8 +16,8 @@ var classSetTemplate = _.template('React.addons.classSet(<%= classSet %>)');
|
|||
var simpleTagTemplate = _.template('<%= name %>(<%= props %><%= children %>)');
|
||||
var tagTemplate = _.template('<%= name %>.apply(this,_.flatten([<%= props %><%= children %>]))');
|
||||
var commentTemplate = _.template(' /* <%= data %> */ ');
|
||||
var templateTemplate = _.template("define([<%= requirePaths %>], function (<%= requireNames %>) {\n'use strict';\n <%= injectedFunctions %>\nreturn function(){ return <%= body %>};\n});");
|
||||
|
||||
var templateAMDTemplate = _.template("define([<%= requirePaths %>], function (<%= requireNames %>) {\n'use strict';\n <%= injectedFunctions %>\nreturn function(){ return <%= body %>};\n});");
|
||||
var templateCommonJSTemplate = _.template("<%= vars %>\n\n'use strict';\n <%= injectedFunctions %>\nmodule.exports = function(){ return <%= body %>};\n");
|
||||
var templateProp = 'rt-repeat';
|
||||
var ifProp = 'rt-if';
|
||||
var classSetProp = 'rt-class';
|
||||
|
@ -261,8 +261,9 @@ function extractDefinesFromJSXTag(html, defines) {
|
|||
* @param {string} html
|
||||
* @return {string}
|
||||
*/
|
||||
function convertTemplateToReact(html) {
|
||||
function convertTemplateToReact(html,options) {
|
||||
// var x = cheerio.load(html);
|
||||
options = options || {};
|
||||
var defines = {react: 'React', lodash: '_'};
|
||||
html = extractDefinesFromJSXTag(html, defines);
|
||||
var rootNode = cheerio.load(html.trim(), {lowerCaseTags: false, lowerCaseAttributeNames: false, xmlMode: true});
|
||||
|
@ -270,9 +271,10 @@ function convertTemplateToReact(html) {
|
|||
var body = convertHtmlToReact(rootNode.root()[0].children[0], context);
|
||||
var requirePaths = _(defines).keys().map(function (reqName) { return '"' + reqName + '"'; }).value().join(',');
|
||||
var requireVars = _(defines).values().value().join(',');
|
||||
var data = {body: body, injectedFunctions: '', requireNames: requireVars, requirePaths: requirePaths};
|
||||
var vars = _(defines).map(function (reqVar,reqPath) {return "var "+reqVar+" = require('"+reqPath+"');"}).join("\n");
|
||||
var data = {body: body, injectedFunctions: '', requireNames: requireVars, requirePaths: requirePaths, vars:vars};
|
||||
data.injectedFunctions = context.injectedFunctions.join('\n');
|
||||
var code = templateTemplate(data);
|
||||
var code = options.commonJS ? templateCommonJSTemplate(data) : templateAMDTemplate(data);
|
||||
try {
|
||||
var tree = esprima.parse(code, {range: true, tokens: true, comment: true});
|
||||
tree = escodegen.attachComments(tree, tree.comments, tree.tokens);
|
||||
|
@ -288,7 +290,7 @@ function convertTemplateToReact(html) {
|
|||
* @param {string} source
|
||||
* @param {string} target
|
||||
*/
|
||||
function convertFile(source, target) {
|
||||
function convertFile(source, target, options) {
|
||||
// if (path.extname(filename) !== ".html") {
|
||||
// console.log('invalid file, only handle html files');
|
||||
// return;// only handle html files
|
||||
|
@ -304,7 +306,7 @@ function convertFile(source, target) {
|
|||
if (!html.match(/\<\!doctype rt/i)) {
|
||||
throw new Error('invalid file, missing header');
|
||||
}
|
||||
var js = convertTemplateToReact(html);
|
||||
var js = convertTemplateToReact(html, options);
|
||||
fs.writeFileSync(target, js);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue