diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c4183eb --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.module-cache diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9032760 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (C) 2014 Luca Antiga http://lantiga.github.io + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f495eef --- /dev/null +++ b/README.md @@ -0,0 +1,366 @@ + +# React.hiccup: React 0% JSX, 100% hiccup + +React.hiccup is a complete replacement for [React](http://facebook.github.io/react/) under the form of a [sweet.js](http://sweetjs.org) macro. + +Dig React but can't take JSX? React.hiccup to the rescue. + + +## Syntax + +React.hiccup syntax is heavily inspired by [hiccup](https://github.com/weavejester/hiccup), a popular [Clojure](http://clojure.org) HTML rendering library. + +In short, the syntax for a React.hiccup element is + +```js +hiccup [tag#id.class1.class2 {attributes} child1 child2 ...] +``` + +e.g. + +```js +hiccup [div#foo.bar.baz {some: "property", another: this.props.property} [p "A child element"] "Child text"] +``` + +where the id, classes, attributes and children are all optional. The className can be also specified +among the attributes, in this case it will be merged with the class names given after the tag. + +A child can be a variable identifier + +```js +var comment = "Some comment"; +hiccup [div#foo.bar.baz "The comment is: " comment] +``` + +but in case it is anything more complex, e.g. an expression, it needs to be surrounded by parentheses + +```js +hiccup [div#foo.bar.baz "The comment is: " (this.state.comment)] +``` + +or + +```js +var one_or_two = 1; +var comment1 = "First comment"; +var comment2 = "Second comment"; +hiccup [div#foo.bar.baz "The comment is: " (one_or_two == 1 ? comment1 : comment2 )] +``` + +Note that this is not required in the attributes. + + +## Complimentary rclass macro + +React.hiccup also comes with an optional macro for declaring a React class + +```js +rclass FooBar = { + render: function() { ... } +} +``` + +expands to (omitting the sweet.js gensym) + +```js +var FooBar = React.createClass({ + render: function() { ... } +}); +``` + +while + +```js +rclass window.FooBar = { + render: function() { ... } +} +``` + +expands to + +```js +window.FooBar = React.createClass({ + render: function() { ... } +}); +``` + + +## Get it + +First install [sweet.js](http://sweetjs.org) if you don't have it already + + $ npm install -g sweet.js + +Then get in your project directory + + $ wget https://raw2.github.com/lantiga/react.hiccup/master/react_hiccup.sjs + +All set. Now to compile a React.hiccup js file into a plain js file do + + $ sjs -m ./react_hiccup.sjs -o foo_build.js foo.js + +To watch the file and have it automatically compiled at every change + + $ sjs -m ./react_hiccup.sjs -o foo_build.js -w foo.js + +(this appears to be currently broken in sweet.js) + + +## Examples + +### React frontpage examples + +Here's how [React frontpage examples](http://facebook.github.io/react/) can be +written using React.hiccup. + +#### A Simple Component + +JSX: + +```js +/** @jsx React.DOM */ +var HelloMessage = React.createClass({ + render: function() { + return
{'Hello ' + this.props.name}
; + } +}); + +React.renderComponent(, mountNode); +``` + +React.hiccup: + +```js +var HelloMessage = React.createClass({ + render: function() { + return hiccup [div ('Hello' + this.props.name)]; + } +}); + +React.renderComponent(hiccup [HelloMessage {name: "John"}], mountNode); +``` + +or, using the rclass macro (we'll use it in the remainder of the examples) + +```js +rclass HelloMessage = { + render: function() { + return hiccup [div ('Hello' + this.props.name)]; + } +} + +React.renderComponent(hiccup [HelloMessage {name: "John"}], mountNode); +``` + +#### A Stateful Component + +React.js + +```js +var Timer = React.createClass({ + getInitialState: function() { + return {secondsElapsed: 0}; + }, + tick: function() { + this.setState({secondsElapsed: this.state.secondsElapsed + 1}); + }, + componentDidMount: function() { + this.interval = setInterval(this.tick, 1000); + }, + componentWillUnmount: function() { + clearInterval(this.interval); + }, + render: function() { + return React.DOM.div({}, + 'Seconds Elapsed: ', this.state.secondsElapsed + ); + } +}); + +React.renderComponent(Timer({}), mountNode); +``` + +React.hiccup + +```js +var Timer = React.createClass({ + getInitialState: function() { + return {secondsElapsed: 0}; + }, + tick: function() { + this.setState({secondsElapsed: this.state.secondsElapsed + 1}); + }, + componentDidMount: function() { + this.interval = setInterval(this.tick, 1000); + }, + componentWillUnmount: function() { + clearInterval(this.interval); + }, + render: function() { + return hiccup [div 'Seconds Elapsed: ' (this.state.secondsElapsed)] + } +}); + +React.renderComponent(hiccup [Timer], mountNode); +``` + +#### An Application + +JSX: + +```js +/** @jsx React.DOM */ +var TodoList = React.createClass({ + render: function() { + var createItem = function(itemText) { + return
  • {itemText}
  • ; + }; + return ; + } +}); +var TodoApp = React.createClass({ + getInitialState: function() { + return {items: [], text: ''}; + }, + onChange: function(e) { + this.setState({text: e.target.value}); + }, + handleSubmit: function(e) { + e.preventDefault(); + var nextItems = this.state.items.concat([this.state.text]); + var nextText = ''; + this.setState({items: nextItems, text: nextText}); + }, + render: function() { + return ( +
    +

    TODO

    + +
    + + +
    +
    + ); + } +}); +React.renderComponent(, mountNode); +``` + +React.hiccup + +``` +rclass TodoList = { + render: function() { + var createItem = function(itemText) { + return hiccup [li itemText]; + }; + return hiccup [ul (this.props.items.map(createItem))]; + } +} +rclass TodoApp = { + getInitialState: function() { + return {items: [], text: ''}; + }, + onChange: function(e) { + this.setState({text: e.target.value}); + }, + handleSubmit: function(e) { + e.preventDefault(); + var nextItems = this.state.items.concat([this.state.text]); + var nextText = ''; + this.setState({items: nextItems, text: nextText}); + }, + render: function() { + return hiccup + [div + [h3 "TODO"] + [TodoList {items: this.state.items}] + [form {onSubmit: this.handleSubmit} + [input {onChange: this.onChange, value: this.state.text}] + [button ('Add #' + (this.state.items.length + 1))]]]; + } +} +React.renderComponent(hiccup [TodoApp], mountNode); +``` + +#### A Component Using External Plugins + +JSX: + +```js +/** @jsx React.DOM */ + +var converter = new Showdown.converter(); + +var MarkdownEditor = React.createClass({ + getInitialState: function() { + return {value: 'Type some *markdown* here!'}; + }, + handleChange: function() { + this.setState({value: this.refs.textarea.getDOMNode().value}); + }, + render: function() { + return ( +
    +

    Input

    +