react-templates/README.md

560 lines
19 KiB
Markdown
Raw Normal View History

2014-12-31 12:41:56 +01:00
[![NPM version][npm-image]][npm-url]
2014-12-31 13:58:58 +01:00
[![build status][travis-image]][travis-url]
2015-01-27 11:34:25 +01:00
[![Coverage Status][coveralls-image]][coveralls-url]
2014-12-31 12:41:56 +01:00
# React Templates
2014-11-10 11:47:49 +01:00
2015-07-30 16:49:11 +02:00
Lightweight templates for [React](http://facebook.github.io/react/index.html).
2015-01-04 21:04:02 +01:00
* No runtime libraries. No magic. Simply precompile your way to clear React code.
* Easy syntax that's similar to HTML, supported by most IDEs.
2015-01-04 23:15:28 +01:00
* Clear separation of presentation and logic - almost zero HTML in component files.
2015-01-04 23:42:30 +01:00
* Declarative coding ensures that the HTML that you write and the HTML you inspect look nearly identical.
2015-02-19 13:45:41 +01:00
* Supports AMD, CommonJS, ES6, Typescript and globals.
2014-11-15 23:32:40 +01:00
## How does it work
2015-09-03 10:11:43 +02:00
React Templates compiles an *.rt file (react template file - an extended HTML format) into a JavaScript file.
This file, which uses AMD syntax, returns a function. When invoked, this function returns a virtual React DOM based on React.DOM elements and custom user components.
<p>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.
## Playground
2014-11-25 17:02:13 +01:00
http://wix.github.io/react-templates/
2015-01-01 16:00:22 +01:00
## Yeoman generator
https://github.com/wix/generator-react-templates
2015-01-04 21:04:02 +01:00
## Hello react-templates
Here's a sample Hello project:<br/>
2015-01-01 16:00:22 +01:00
https://github.com/wix/hello-react-templates
2014-11-25 17:02:13 +01:00
2015-09-10 12:50:19 +02:00
Here's a sample Hello project with webpack, es6 and hot reload:<br/>
2015-11-17 12:05:41 +01:00
https://github.com/wix/react-templates-transform-boilerplate
2015-09-10 12:50:19 +02:00
2015-01-08 16:40:27 +01:00
## IntelliJ / WebStorm plugin
http://plugins.jetbrains.com/plugin/7648
2015-01-04 11:16:06 +01:00
2015-01-04 11:16:28 +01:00
2015-01-04 21:04:02 +01:00
###### Basic concepts for React templates
2015-07-30 16:49:11 +02:00
* Any valid HTML (including comments) is a template
2014-11-15 23:32:40 +01:00
* {} to identify JS expression
2015-01-04 23:42:30 +01:00
* Built-in directives:
2015-01-05 09:25:13 +01:00
* [rt-if](#rt-if)
* [rt-repeat](#rt-repeat)
* [rt-scope](#rt-scope)
* [rt-props](#rt-props)
* [rt-class](#rt-class)
2016-02-19 19:54:14 +01:00
* [rt-require](#rt-require-and-using-other-components-in-the-template)
2016-02-19 19:54:43 +01:00
* [rt-template](#rt-template-and-defining-properties-template-functions)
* [rt-include](#rt-include)
2015-01-05 09:25:13 +01:00
* [styles](#styles)
2015-01-05 09:26:06 +01:00
* [event handlers](#event-handlers)
2014-11-15 23:32:40 +01:00
###### Why not use JSX?
2015-09-03 10:11:43 +02:00
Some love JSX, some don't. We don't. More specifically, it seems to us that JSX is only a good fit for components with very little HTML inside.
And this can be accomplished by creating DOM elements in code. Also, we like to separate code and HTML because 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
2014-12-30 17:10:18 +01:00
npm install react-templates -g
2014-12-09 15:29:38 +01:00
```
2014-11-16 08:11:18 +01:00
## Usage
2014-12-09 15:29:38 +01:00
```shell
rt [file.rt|dir]* [options]
```
See more on CLI usage [here](https://github.com/wix/react-templates/blob/gh-pages/docs/cli.md).
2015-01-04 11:17:31 +01:00
2015-02-19 15:44:33 +01:00
In most cases, this package will be wrapped in a build task, so CLI will not be used explicitly:
* Grunt: [grunt-react-templates](https://github.com/wix/grunt-react-templates)
2015-03-17 13:11:28 +01:00
* Gulp: [gulp-react-templates](https://github.com/wix/gulp-react-templates)
2015-02-19 15:44:33 +01:00
* Broccoli: [broccoli-react-templates](https://github.com/kraftwer1/broccoli-react-templates)
2015-03-17 13:11:28 +01:00
* Browserify plugin: [react-templatify](https://www.npmjs.com/package/react-templatify)
* Webpack loader : [react-templates-loader](https://github.com/AlexanderPavlenko/react-templates-loader)
2014-11-16 08:11:18 +01:00
2015-08-19 19:20:31 +02:00
### 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)
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
2015-01-04 23:42:30 +01:00
Any HTML that you write is a valid template, except for inline event handlers ("on" attributes). See the "event handlers" section below for more information.
2014-11-15 23:32:40 +01:00
2014-11-18 17:20:16 +01:00
## {} to identify JavaScript expressions
2015-09-03 10:11:43 +02:00
To embed JavaScript expressions in both attribute values and tag content, encapsulate them in {}.
If this is done inside an attribute value, the value still needs to be wrapped in quotes. For directives (see below), {} are not used.
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-15 23:46:27 +01:00
## rt-if
2015-01-04 23:15:28 +01:00
This lets you add conditions to a subtree of HTML. If the condition evaluates to true, the subtree will be returned; otherwise, it will not be calculated. It is implemented as a ternary expression.
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-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
2015-09-03 10:11:43 +02:00
Repeats a DOM node with its subtree for each item in an array. The syntax is `rt-repeat="itemVar in arrayExpr"`, where the element, `itemVar`, will be available in JavaScript context,
and an `itemVarIndex` will be created to represent the index of the item. By using this naming scheme, repeated expressions have access to all levels of nesting.
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
2015-09-03 10:11:43 +02:00
This directive creates a new JavaScript scope by creating a new method and invoking it with its current context. The syntax is `rt-scope="expr1 as var1; expr2 as var2`.
This allows for a convenient shorthand to make the code more readable. It also helps to execute an expression only once per scope.
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
```
2015-09-03 10:11:43 +02:00
Subsequent expressions may reference preceding variables, since generated code declares each alias as a `var` (as opposed to a function parameter, which get bound to formal parameter names only after evaluation),
so you can do stuff like
```html
<div rt-scope="users[userId] as user; user.profile as profile; profile.avatar as avatar;">
```
When used with `rt-if`, the `rt-if` condition is evaluated first, and only if it is truthy, the `rt-scope` mappings are processed. This means you can write things like
```html
<div rt-if="user.profile" rt-scope="user.profile.image as image">
```
without risking accessing a field on an `undefined`, or doing something ugly like `user.profile && user.profile.image as image`.
When used with `rt-repeat`, the `rt-scope` is evaluated for every iteration, so that iteration's `item` and `itemIndex` are in scope.
2014-11-29 22:32:00 +01:00
## rt-props
2015-09-03 10:11:43 +02:00
rt-props is used to inject properties into an element programmatically. It will merge the properties with the properties received in the template.
This option allows you to build properties based on external 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 property name, and the values will be the property values.
2014-11-29 22:32:00 +01:00
###### 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
2015-09-03 10:11:43 +02:00
To reduce the boilerplate code when setting class names programatically, you can use the rt-class directive. It expects a JSON object with keys as class names, and a Boolean as the value.
If the value is true, the class name will be included.
<p>Note the following:<br/>
2015-01-04 23:15:28 +01:00
1. In React templates, you can use the "class" attribute as you would in HTML. <br/>
2015-09-03 10:11:43 +02:00
2. If you use both class and rt-class on the same HTML element, they get merged.
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 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()
}]);
};
});
```
## rt-include
Optionally choose to extract static contents out of rt files.<br>
rt-include is a "macro" that takes a text file (e.g svg/html/xml) and injects it into the file as if it was part of the original markup.
###### Sample:
given `main.rt`:
```html
<div>
<rt-include src="./my-icon.svg" />
</div>
```
and `my-icon.svg`:
```html
<svg xmlns="http://www.w3.org/2000/svg">
<rect height="50" width="50" style="fill: #00f"/>
</svg>
```
is equivalent to:
```html
<div>
<svg xmlns="http://www.w3.org/2000/svg">
<rect height="50" width="50" style="fill: #00f"/>
</svg>
</div>
```
2014-11-16 00:43:59 +01:00
## style
2015-07-30 16:49:11 +02:00
React templates allow the settings of styles inline in HTML, optionally returning an object from the evaluation context. By default, style names will be converted from hyphen-style to camelCase-style naming.
2015-01-04 22:38:01 +01:00
2015-01-04 23:42:30 +01:00
To embed JavaScript inside a style attribute, single curly braces are used. To embed an entire object, double curly braces are used. *Note*: When embedding objects, styles must conform to camelCase-style naming.
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 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
2015-09-03 10:11:43 +02:00
React event handlers accept function references inside of {}, such as `onClick="{this.myClickHandler}"`. When functions are not needed, lambda notation can be used,
which will create a React template that creates a function for the included code. There is no performance impact, as the function created is bound to the context instead of being recreated.
<p>The lambda notation has the form: `onClick="(evt) => console.log(evt)"`. In this example, **evt** is the name of the first argument passed into the inline function.
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.
If you have an event handler called **onBoxSelected** that triggers an event with row and column params, you can write `onBoxSelected="(row, col)=>this.doSomething(row,col)"`.
A no-param version is supported 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
2015-09-03 10:11:43 +02:00
In many cases, you'd like to use either external code or other components within your template. An **rt-require** tag lets you include dependencies: `<rt-require dependency="depVarPath" as="depVarName"/>`.
Once included, **depVarName** will be in scope. You can only use rt-require tags at the beginning of your template. When including React components, they can be referred to by their tag name inside a template.
For example, `<MySlider prop1="val1" onMyChange="{this.onSliderMoved}">`. Nesting is also supported: `<MyContainer><div>child</div><div>another</div></MyContainer>`.
Children are accessible from **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));
};
```
2015-02-19 13:45:41 +01:00
###### Compiled (with ES6 flag):
```javascript
2015-05-10 16:23:25 +02:00
import React from 'react/addons';
import _ from 'lodash';
import MyComp from 'comps/myComp';
import utils from 'utils/utils';
2015-02-19 13:45:41 +01:00
function repeatItem1(item, itemIndex) {
return React.createElement(MyComp, {}, React.createElement('div', {}, utils.toLower(item.name)));
}
export default function () {
return _.map(items, repeatItem1.bind(this));
};
```
2014-12-09 15:29:38 +01:00
## Inline Templates
Although we recommend separating the templates to a separate `.rt` file, there's an option to use a template inline as the render method (à la JSX).
To do that, write your code in a `.jsrt` file, and send it to react-templates with the `modules` flag set to `jsrt`.
###### Sample:
```javascript
define(['react','lodash'], function (React, _) {
var comp = React.createClass({
render:
<template>
<div>hello world</div>
</template>
});
return comp;
});
```
###### Compiled (with jsrt flag):
```javascript
define([
'react',
'lodash'
], function (React, _) {
var comp = React.createClass({
render: function () {
return function () {
return React.createElement('div', {}, 'hello world');
};
}()
});
return comp;
});
```
2015-08-19 19:20:31 +02:00
## 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: `<rt-template prop="propName" arguments="arg1, arg2"/>`.
2015-08-09 23:07:52 +02:00
Templates can be used only as an immediate child of the component that it will be used in. All scope variable will be available in the template function.
###### Sample:
```html
<MyComp data="{[1,2,3]}">
<rt-template prop="renderItem" arguments="item">
<div>{item}</div>
</rt-template>
</MyComp>
```
###### Compiled (AMD):
```javascript
define([
'react/addons',
'lodash'
], function (React, _) {
'use strict';
function renderItem1(item) {
return React.createElement('div', {}, item);
}
return function () {
return React.createElement(MyComp, {
'data': [
1,
2,
3
],
'renderItem': renderItem1.bind(this)
});
};
});
```
###### Compiled (with CommonJS flag):
```javascript
'use strict';
var React = require('react/addons');
var _ = require('lodash');
function renderItem1(item) {
return React.createElement('div', {}, item);
}
module.exports = function () {
return React.createElement(MyComp, {
'data': [
1,
2,
3
],
'renderItem': renderItem1.bind(this)
});
};
```
###### Compiled (with ES6 flag):
```javascript
import React from 'react/addons';
import _ from 'lodash';
function renderItem1(item) {
return React.createElement('div', {}, item);
}
export default function () {
return React.createElement(MyComp, {
'data': [
1,
2,
3
],
'renderItem': renderItem1.bind(this)
});
};
```
2015-07-30 16:49:11 +02:00
## Contributing
See the [Contributing page](CONTRIBUTING.md).
2014-12-09 15:29:38 +01:00
## License
2015-07-30 16:49:11 +02:00
Copyright (c) 2015 Wix. Licensed under the MIT license.
2014-12-31 13:57:52 +01:00
[npm-image]: https://img.shields.io/npm/v/react-templates.svg?style=flat-square
[npm-url]: https://npmjs.org/package/react-templates
2014-12-31 16:27:59 +01:00
[travis-image]: https://img.shields.io/travis/wix/react-templates/gh-pages.svg?style=flat-square
2014-12-31 13:57:52 +01:00
[travis-url]: https://travis-ci.org/wix/react-templates
2015-01-27 11:34:25 +01:00
[coveralls-image]: https://img.shields.io/coveralls/wix/react-templates/gh-pages.svg?style=flat-square
[coveralls-url]: https://coveralls.io/r/wix/react-templates?branch=gh-pages
2014-12-31 13:57:52 +01:00
[downloads-image]: http://img.shields.io/npm/dm/react-templates.svg?style=flat-square
[downloads-url]: https://npmjs.org/package/react-templates