react-templates/README.md

330 lines
13 KiB
Markdown
Raw Normal View History

2014-11-15 23:32:40 +01:00
# react-templates
2014-11-10 11:47:49 +01:00
2014-11-18 17:20:16 +01:00
Light weight templates for [React](http://facebook.github.io/react/index.html). Reasons that we love it:
2014-11-18 17:20:16 +01:00
* No runtime libraries. No magic. Just simple pre-compilation to a clear React code
* Super easy to write panels. By panels we mean components that have a lot of HTML code and non-reusable logic
* Very good separation of presentation and logic. Almost no HTML within the component file
2014-12-02 16:09:45 +01:00
* Declarative coding for presentation. HTML that you write and inspect look similar
* Easy syntax. Similar to HTML. All IDEs recognize this format
2014-11-15 23:32:40 +01:00
## How does it work
2014-11-18 17:20:16 +01:00
React templates compiles a *.rt file (react template file - extended HTML format) into a JavaScript file. This file - which currently utilizes RequireJS format - returns a function. This function, when invoked, returns a virtual React DOM (based on React.DOM elements and custom user components). A common use case would be that a regular React component would require a JavaScript file generated from a template, and then perform `func.apply(this)`, causing the template to have that component as its context.
2014-11-25 17:02:13 +01:00
## playground
http://wix.github.io/react-templates/
2014-11-16 00:28:22 +01:00
###### Basic concepts for react templates
* Any valid HTML is a template (and comments)
2014-11-15 23:32:40 +01:00
* {} to identify JS expression
* rt-if
* rt-repeat
* rt-scope
2014-11-29 22:32:00 +01:00
* rt-props
* rt-class
* style
* event handlers
* rt-require, and using other components in the template
2014-11-15 23:32:40 +01:00
###### Why not use JSX?
2014-11-18 17:20:16 +01:00
Some love JSX, some don't. We don't. More specifically, it seems to us that JSX is a good fit only for components with very little HTML inside, which can be accomplished by creating elements in code. Also, we like to separate code and HTML. It just feels right.
2014-11-15 23:32:40 +01:00
2014-12-09 15:29:38 +01:00
## Installation
2014-12-10 08:46:35 +01:00
You can install react-templates using npm:
2014-12-09 15:29:38 +01:00
```shell
npm install git+ssh://git@github.com:wix/react-templates -g
```
2014-11-16 08:11:18 +01:00
## Usage
2014-12-09 15:29:38 +01:00
```shell
rt [file.rt|dir]* [options]
```
2014-11-16 08:11:18 +01:00
**src/cli.js < filename.rt >**
2014-12-09 15:29:38 +01:00
*(you might need to provide execution permission to the file)* ?
2014-11-16 08:11:18 +01:00
2014-11-18 17:20:16 +01:00
Note that in most cases, this package will be wrapped in a Grunt task, so that cli will not be used explicitly.
2014-12-09 15:29:38 +01:00
[grunt-react-templates](https://github.com/wix/grunt-react-templates)
2014-11-16 08:11:18 +01:00
# Template directives and syntax
2014-11-15 23:32:40 +01:00
## Any valid HTML is a template
2014-11-18 17:20:16 +01:00
Any HTML that you write is a valid template, except for inline event handler ("on" attributes). See the section about event handlers for more information
2014-11-15 23:32:40 +01:00
2014-11-18 17:20:16 +01:00
## {} to identify JavaScript expressions
You can easily embed JavaScript expressions in both attribute values and content by encapsulating them in {}. If this is done inside an attribute value, the value still needs to be wrapped by quotes. In tag content, you can just use it.
2014-11-15 23:32:40 +01:00
2014-11-16 00:49:47 +01:00
###### Sample:
2014-11-16 15:32:50 +01:00
```html
2014-11-15 23:32:40 +01:00
<a href="{this.state.linkRef}">{this.state.linkText}</a>
```
2014-11-16 00:49:47 +01:00
###### Compiled:
2014-11-16 15:28:18 +01:00
```javascript
2014-11-15 23:32:40 +01:00
define([
'react',
'lodash'
], function (React, _) {
'use strict';
return function () {
return React.DOM.a({ 'href': this.state.linkRef }, this.state.linkText);
};
});
```
2014-11-18 17:25:28 +01:00
*Note*: within the special **"rt-"** directives (see below), simple strings don't make sense, as all these directives are used for specifying execution instructions. Therefore, in these directives, you should not use {}
2014-11-15 23:46:27 +01:00
## rt-if
This gives you the ability to add conditions to a sub-tree of html. If the condition is evaluated to true, the subree will be returned, otherwise, it will not be calculated. It is implemented by a trinary expression
2014-11-16 00:49:47 +01:00
###### Sample:
2014-11-16 15:32:50 +01:00
```html
2014-11-15 23:46:27 +01:00
<div rt-if="this.state.resultCode === 200">Success!</div>
```
2014-11-16 00:49:47 +01:00
###### Compiled:
2014-11-16 15:28:18 +01:00
```javascript
2014-11-15 23:46:27 +01:00
define([
'react',
'lodash'
], function (React, _) {
'use strict';
return function () {
return this.state.resultCode === 200 ? React.DOM.div({}, 'Success!') : null;
};
});
```
2014-11-16 00:25:09 +01:00
## rt-repeat
2014-11-16 08:11:18 +01:00
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.
2014-11-15 23:46:27 +01:00
2014-11-16 00:49:47 +01:00
###### Sample:
2014-11-16 15:32:50 +01:00
```html
<div rt-repeat="myNum in this.getMyNumbers()">{myNumIndex}. {myNum}</div>
2014-11-15 23:46:27 +01:00
```
2014-11-16 00:49:47 +01:00
###### Compiled:
2014-11-16 15:28:18 +01:00
```javascript
2014-11-16 00:25:09 +01:00
define([
'react',
'lodash'
], function (React, _) {
'use strict';
function repeatMyNum1(myNum, myNumIndex) {
return React.DOM.div({}, myNumIndex + '. ' + myNum);
2014-11-16 00:25:09 +01:00
}
return function () {
return _.map(this.getMyNumbers(), repeatMyNum1.bind(this));
};
});
```
## rt-scope
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.
2014-11-15 23:46:27 +01:00
2014-11-16 00:49:47 +01:00
###### Sample:
2014-11-16 15:32:50 +01:00
```html
2014-11-16 00:25:09 +01:00
<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>
```
2014-11-16 00:49:47 +01:00
###### Compiled:
2014-11-16 15:28:18 +01:00
```javascript
2014-11-16 00:25:09 +01:00
define([
'react',
'lodash'
], function (React, _) {
'use strict';
function scopeSeparatorVal1(rpt, rptIndex, separator, val) {
return React.DOM.div({}, rptIndex + separator + ' ' + val);
}
function repeatRpt2(rpt, rptIndex) {
return React.DOM.div({}, scopeSeparatorVal1.apply(this, [
rpt,
rptIndex,
')',
rpt.val
]), React.DOM.div({}, '\'rpt\' exists here, but not \'separator\' and \'val\''));
}
return function () {
return _.map(array, repeatRpt2.bind(this));
};
});
2014-11-15 23:46:27 +01:00
```
2014-11-29 22:32:00 +01:00
## rt-props
This directive is used to inject properties to an element programmatically. It will merge the properties with the properties received in the template. This option allows the code writer to build properties based on some app logic and pass them to the template. It is also useful when passing properties set on the component to an element within the template. The expected value of this attribute is an expression returning an object. The keys will be the properties and the values will be the property values.
###### Sample:
```html
<input style="height:10px;width:3px;" rt-props="{style:{width:'5px'},type:'text'}"/>
```
###### Compiled:
```javascript
define([
'react',
'lodash'
], function (React, _) {
'use strict';
return function () {
return React.DOM.input(_.merge({}, {
'style': {
height: '10px',
width: '3px'
}
}, {
style: { width: '5px' },
type: 'text'
}));
};
});
```
2014-11-16 00:43:59 +01:00
## rt-class
In order to reduce the boiler-plate code when programatically setting class names, you can use the rt-class directive. It expectes to get a JSON object with keys as class names, and a value of true or false as the value. If the value is true, the class name will be included. Please note the following:
1. In react templates, you can use the "class" attribute the same as you'd do in html. If you like, you can even have execution context within
2. You cannot use class and rt-class on the same html element
2014-11-16 00:49:47 +01:00
###### Sample:
2014-11-16 15:32:50 +01:00
```html
2014-11-16 00:43:59 +01:00
<div rt-scope="{blue: true, selected: this.isSelected()} as classes">
These are logically equivalent
<div rt-class="classes">Reference</div>
<div rt-class="{blue: true, selected: this.isSelected()}">Inline</div>
<div class="blue{this.isSelected() ? ' selected' : ''}">Using the class attribute</div>
</div>
```
2014-11-16 00:49:47 +01:00
###### Compiled:
2014-11-16 15:28:18 +01:00
```javascript
2014-11-16 00:43:59 +01:00
define([
'react',
'lodash'
], function (React, _) {
'use strict';
function scopeClasses1(classes) {
return React.DOM.div({}, 'These are logically equivalent', React.DOM.div({ 'className': React.addons.classSet(classes) }, 'Reference'), React.DOM.div({
'className': React.addons.classSet({
blue: true,
selected: this.isSelected()
})
}, 'Inline'), React.DOM.div({ 'className': 'blue' + this.isSelected() ? ' selected' : '' }, 'Using the class attribute'));
}
return function () {
return scopeClasses1.apply(this, [{
blue: true,
selected: this.isSelected()
}]);
};
});
```
## style
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
2014-11-16 00:49:47 +01:00
###### Sample:
2014-11-16 15:32:50 +01:00
```html
2014-11-16 00:43:59 +01:00
<div>
These are really equivalent
<div style="color:white; line-height:{this.state.lineHeight}px">Inline</div>
<div style="{{'color': 'white', 'lineHeight': this.state.lineHeight + 'px'}}">Inline</div>
</div>
```
2014-11-16 00:49:47 +01:00
###### Compiled:
2014-11-16 15:28:18 +01:00
```javascript
2014-11-16 00:43:59 +01:00
define([
'react',
'lodash'
], function (React, _) {
'use strict';
return function () {
return React.DOM.div({}, 'These are really equivalent', React.DOM.div({
'style': {
color: 'white',
lineHeight: this.state.lineHeight + 'px'
}
}, 'Inline'), React.DOM.div({
'style': {
'color': 'white',
'lineHeight': this.state.lineHeight + 'px'
}
}, 'Inline'));
};
});
```
## event handlers
2014-11-16 08:11:18 +01:00
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')"`
2014-11-16 00:43:59 +01:00
2014-11-16 00:49:47 +01:00
###### Sample:
2014-11-16 15:32:50 +01:00
```html
2014-11-16 08:11:18 +01:00
<div rt-repeat="item in items">
<div onClick="()=>this.itemSelected(item)" onMouseDown="{this.mouseDownHandler}">
</div>
2014-11-16 00:43:59 +01:00
```
2014-11-16 00:49:47 +01:00
###### Compiled:
2014-11-16 15:28:18 +01:00
```javascript
2014-11-16 08:11:18 +01:00
define([
'react',
'lodash'
], function (React, _) {
'use strict';
function onClick1(item, itemIndex) {
this.itemSelected(item);
}
function repeatItem2(item, itemIndex) {
return React.DOM.div({}, React.DOM.div({
'onClick': onClick1.bind(this, item, itemIndex),
'onMouseDown': this.mouseDownHandler
}));
}
return function () {
return _.map(items, repeatItem2.bind(this));
};
});
2014-11-16 00:43:59 +01:00
```
## rt-require, and using other components in the template
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 **rt-require** tag and indicate dependencies. You do so by `<rt-require dependency="depVarPath" as="depVarName"/>`. After that, you will have **depVarName** in your scope. You can only use rt-require tags in the beginning of your template. 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**.
2014-11-15 23:46:27 +01:00
2014-11-16 00:49:47 +01:00
###### Sample:
2014-11-16 15:32:50 +01:00
```html
<rt-require dependency="comps/myComp" as="MyComp"/>
<rt-require dependency="utils/utils" as="utils"/>
2014-11-16 08:11:18 +01:00
<MyComp rt-repeat="item in items">
<div>{utils.toLower(item.name)}</div>
</MyComp>
2014-11-15 23:46:27 +01:00
```
###### Compiled (AMD):
2014-11-16 15:28:18 +01:00
```javascript
2014-11-16 08:11:18 +01:00
define([
'react/addons',
2014-11-16 08:11:18 +01:00
'lodash',
'comps/myComp',
'utils/utils'
], function (React, _, MyComp, utils) {
'use strict';
function repeatItem1(item, itemIndex) {
return React.createElement(MyComp, {}, React.createElement('div', {}, utils.toLower(item.name)));
2014-11-16 08:11:18 +01:00
}
return function () {
return _.map(items, repeatItem1.bind(this));
};
});
2014-11-16 15:28:18 +01:00
```
###### Compiled (with CommonJS flag):
```javascript
var React = require('react/addons');
var _ = require('lodash');
var MyComp = require('comps/myComp');
var utils = require('utils/utils');
'use strict';
function repeatItem1(item, itemIndex) {
return React.createElement(MyComp, {}, React.createElement('div', {}, utils.toLower(item.name)));
}
module.exports = function () {
return _.map(items, repeatItem1.bind(this));
};
```
2014-12-09 15:29:38 +01:00
## License
Copyright (c) 2014 Wix. Licensed under the MIT license.