[React] TutorialをCoffeeScript化してみた

### あらすじ
React の勉強をかねて、Tutorialに書かれているソースをCoffeeScript化してみた。

### チュートリアルページ
[Tutorial | React](http://facebook.github.io/react/docs/tutorial.html)

### 前準備
 * coffee-react のインストール
 * index.htmlの準備
 * 共通JSの準備

```shell-session
# npm install -g coffee-react
```

```html:index.html
<!doctype html>
<html lang="ja">

<head>
<meta charset="utf-8">
<title>React Starter kit</title>

<body>
<div id="content"></div>

<script src="node_modules/react/dist/react.js"></script>
<!-- <script src="node_modules/react/dist/JSXTransformer.js"></script> -->
<script src="//code.jquery.com/jquery-2.1.3.min.js"></script>
<script src="node_modules/showdown/compressed/Showdown.min.js"></script>
<!--
<script src="node_modules/underscore/underscore-min.js"></script>
<script src="node_modules/backbone/backbone-min.js"></script>
-->
<script src="assets/jsx/tutorial_common.js"></script>
<!--
<script src="assets/jsx/tutorial1.js"></script>
-->
<!--
<script src="assets/jsx/tutorial2.js"></script>
<script src="assets/jsx/tutorial3.js"></script>
-->
<!--
<script src="assets/jsx/tutorial5.js"></script>
<script src="assets/jsx/tutorial4.js"></script>
-->
<!--
<script src="assets/jsx/tutorial6.js"></script>
<script src="assets/jsx/tutorial4.js"></script>
-->
<!--
<script src="assets/jsx/tutorial7.js"></script>
<script src="assets/jsx/tutorial4.js"></script>
-->
<!--
<script src="assets/jsx/tutorial8.js"></script>
<script src="assets/jsx/tutorial7.js"></script>
<script src="assets/jsx/tutorial10.js"></script>
<script src="assets/jsx/tutorial9.js"></script>
<!-- Each child in an array should have a unique "key" prop.
 Check the renderComponent call using <undefined>.
 See http://fb.me/react-warning-keys for more information. -->

<!-- 謎
<script src="assets/jsx/tutorial11.js"></script>
-->
<!--
<script src="assets/jsx/tutorial8.js"></script>
<script src="assets/jsx/tutorial7.js"></script>
<script src="assets/jsx/tutorial10.js"></script>
<script src="assets/jsx/tutorial12.js"></script>
-->
<!--
<script src="assets/jsx/tutorial7.js"></script>
<script src="assets/jsx/tutorial10.js"></script>
<script src="assets/jsx/tutorial13.js"></script>
-->
<!--
<script src="assets/jsx/tutorial7.js"></script>
<script src="assets/jsx/tutorial10.js"></script>
<script src="assets/jsx/tutorial14.js"></script>
-->
<!--
<script src="assets/jsx/tutorial15.js"></script>
-->
<!--
<script src="assets/jsx/tutorial7.js"></script>
<script src="assets/jsx/tutorial16.js"></script>
<script src="assets/jsx/tutorial17.js"></script>
-->
<!--
<script src="assets/jsx/tutorial7.js"></script>
<script src="assets/jsx/tutorial18.js"></script>
<script src="assets/jsx/tutorial19.js"></script>
-->
<script src="assets/jsx/tutorial20.js"></script>
```


```js:tutorial_common.cjsx
// jQuery 使うとき
$content =
  $ "#content"
    .get 0

// jQuery 使わないとき
$content = document.getElementById "content"
```

### 本編

#### tutorial 1

```coffeescript:tutorial.cjsx
React.render <h1>Hello, React!</h1>, $content
```

#### tutorial 2

```coffeescript:tutorial1.cjsx
CommentList = React.createClass
  render: ->
    <div className="commentList">
      Hello, React! I am a CommentList.
    </div>

CommentForm = React.createClass
  render: ->
    <div className="commentForm">
      Hello, React! I am a CommentForm.
    </div>
```

#### tutorial 2

```coffeescript:tutorial2.cjsx
CommentList = React.createClass
  render: ->
    <div className="commentList">
      Hello, React! I am a CommentList.
    </div>

CommentForm = React.createClass
  render: ->
    <div className="commentForm">
      Hello, React! I am a CommentForm.
    </div>
```

#### tutorial 3

```coffeescript:tutorial3.cjsx
CommentBox = React.createClass
  render: ->
    <div className="commentBox">
      <h1>Comments</h1>
      <CommentList />
      <CommentForm />
    </div>

React.render <CommentBox />, $content
```

#### tutorial 4

```coffeescript:tutorial4.cjsx
CommentList = React.createClass
  render: ->
    <div className="commentList">
      <Comment author="Pete Hunt">This is one comment</Comment>
      <Comment author="Jordan Walke">This is *another* comment</Comment>
    </div>

React.render <CommentList />, $content
```

#### tutorial 5

```coffeescript:tutorial5.cjsx
Comment = React.createClass
  render: ->
    <div classNmae="comment">
      <h2 className="commentAuthor">
        {@props.author}
      </h2>
      {@props.children}
    </div>
```

#### tutorial 6

```coffeescript:tutorial6.cjsx
converter = new Showdown.converter()
Comment = React.createClass
  render: ->
    <div className="comment">
      <h2 className="commentAuthor">
        {@props.author}
      </h2>
      {converter.makeHtml @props.children.toString()}
    </div>
```

#### tutorial 7

```coffeescript:tutorial7.cjsx
converter = new Showdown.converter()
Comment = React.createClass
  render: ->
    rawMarkup = converter.makeHtml @props.children.toString()
    <div className="comment">
      <h2 className="commentAuthor">
        {@props.author}
      </h2>
      <span dangerouslySetInnerHTML={{__html: rawMarkup}} />
    </div>
```


#### tutorial 8

```coffeescript:tutorial8.cjsx
data_list = [
  {
    author: "Pete Hunt"
    text:   "This is one comment"
  }
  {
    author: "Jordan Walke"
    text:   "This is *another* comment"
  }
]
```

#### tutorial 9

```coffeescript:tutorial9.cjsx
CommentBox = React.createClass
  render: ->
    <div className="commentBox">
      <h1>Comments</h1>
      <CommentList data={@props.params} />
      <CommentForm />
    </div>

React.render <CommentBox params={data_list} />, $content
```

#### tutorial 10

```coffeescript:tutorial10.cjsx
CommentList = React.createClass
  render: ->
    commentNodes = @props.data.map (comment) ->
      <Comment author={comment.author}>
        {comment.text}
      </Comment>

    <div className="commentList">
      {commentNodes}
    </div>


CommentForm = React.createClass
  render: ->
    <div className="commentForm">
      Hello, React! I am a CommentForm.
    </div>
```

#### tutorial 11

```coffeescript:tutorial11.cjsx
CommentBox = React.createClass
  render: ->
    console.log @prop
    <div className="commentBox">
      <h1>Comments</h1>
    </div>

React.render <CommentBox url="comments.json" />, $content
```

#### tutorial 12

```coffeescript:tutorial12.cjsx
CommentBox = React.createClass
  getInitialState: ->
    data: data_list

  render: ->
    <div className="commentBox">
      <h1>Comments</h1>
      <CommentList data={@state.data} />
      <CommentForm />
    </div>

React.render <CommentBox />, $content
```

#### tutorial 13

```coffeescript:tutorial13.cjsx
CommentBox = React.createClass
  getInitialState: ->
    data: []

  componentDidMount: ->

    jqxhr = $.ajax
      url:      @props.url
      dataType: "jsonp"

    jqxhr
      .done (res) =>
        @setState
          data: res
      .fail (xhr, status, err) =>
        console.error @props.url, status, err.toString()

  render: ->
    <div className="commentBox">
      <h1>Comments</h1>
      <CommentList data={@state.data} />
      <CommentForm />
    </div>

React.render <CommentBox url="makejson.php" />, $content
```

#### tutorial 14

```coffeescript:tutorial14.cjsx
CommentBox = React.createClass
  loadCommentsFromServer: ->
    jqxhr = $.ajax
      url:      @props.url
      dataType: "jsonp"

    jqxhr
      .done (res) =>
        @setState
          data: res
      .fail (xhr, status, err) =>
        console.error @props.url, status, err.toString()

  getInitialState: ->
    data: []

  componentDidMount: ->
    @loadCommentsFromServer()
    setInterval @loadCommentsFromServer, @props.pollInterval

  render: ->
    <div className="commentBox">
      <h1>Comments</h1>
      <CommentList data={@state.data} />
      <CommentForm />
    </div>

React.render <CommentBox url="makejson.php" pollInterval="2000" />, $content
```

#### makejson.php

```php:makejson.php
<?php
$data = array(
    array(
        'author' => 'Pete Hunt',
        'text'   => 'This is one comment.',
    ),
    array(
        'author' => 'Jordan Walke',
        'text'   => 'This is *another* comment.',
    ),
);

if (! isset($_GET['callback'])) {
    $callback = 'callback';
} else {
    $callback = $_GET['callback'];
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $data[] = $_REQUEST;
}

header('Content-type: application/json');
echo $callback.'('.json_encode($data).')';
```


#### tutorial 15

```coffeescript:tutorial15.cjsx
CommentForm = React.createClass
  render: ->
    <form className="commentForm">
      <input type="text" placeholder="Your name" />
      <input type="text" placeholder="Say something..." />
      <input type="submit" value="Post" />
    </form>

CommentBox = React.createClass
  render: ->
    <div className="commentBox">
      <h1>Comments</h1>
      <CommentForm />
    </div>

React.render <CommentBox />, $content
```

#### tutorial 16

```coffeescript:tutorial16.cjsx
CommentList = React.createClass
  render: ->
    commentNodes = @props.data.map (comment) ->
      <Comment author={comment.author}>
        {comment.text}
      </Comment>

    <div className="commentList">
      {commentNodes}
    </div>

CommentForm = React.createClass
  handleSubmit: (e) ->
    e.preventDefault();

    author = @refs.author.getDOMNode().value.trim()
    text   = @refs.text.getDOMNode().value.trim()

    if ! text or ! author
      return

    # TODO: send request to the server
    @refs.author.getDOMNode().value = ""
    @refs.text.getDOMNode().value   = ""

  render: ->
    <form className="commentForm" onSubmit={@handleSubmit}>
      <input type="text" placeholder="Your name" ref="author" />
      <input type="text" placeholder="Say something..." ref="text" />
      <input type="submit" value="Post" />
    </form>
```

#### tutorial 17

```coffeescript:tutorial17.cjsx
CommentBox = React.createClass
  loadCommentsFromServer: ->
    jqxhr = $.ajax
      url:      @props.url
      dataType: "jsonp"

    jqxhr
      .done (res) =>
        @setState
          data: res
      .fail (xhr, status, err) =>
        console.error @props.url, status, err.toString()

  handleCommentSubmit: (comment) ->
    console.log comment

  getInitialState: ->
    data: []

  componentDidMount: ->
    @loadCommentsFromServer()
    setInterval @loadCommentsFromServer, @props.pollInterval

  render: ->
    <div className="commentBox">
      <h1>Comments</h1>
      <CommentList data={@state.data} />
      <CommentForm />
    </div>

React.render <CommentBox url="makejson.php" pollInterval="2000" />, $content
```

#### tutorial 18

```coffeescript:tutorial18.cjsx
CommentList = React.createClass
  render: ->
    commentNodes = @props.data.map (comment) ->
      <Comment author={comment.author}>
        {comment.text}
      </Comment>

    <div className="commentList">
      {commentNodes}
    </div>

CommentForm = React.createClass
  handleSubmit: (e) ->
    e.preventDefault();

    author = @refs.author.getDOMNode().value.trim()
    text   = @refs.text.getDOMNode().value.trim()

    if ! text or ! author
      return

    @props.onCommentSubmit
      author: author
      text:   text

    @refs.author.getDOMNode().value = ""
    @refs.text.getDOMNode().value   = ""

  render: ->
    <form className="commentForm" onSubmit={@handleSubmit}>
      <input type="text" placeholder="Your name" ref="author" />
      <input type="text" placeholder="Say something..." ref="text" />
      <input type="submit" value="Post" />
    </form>
```

#### tutorial 19

```coffeescript:tutorial19.cjsx
CommentBox = React.createClass
  loadCommentsFromServer: ->
    jqxhr = $.ajax
      url:      @props.url
      dataType: "jsonp"

    jqxhr
      .done (res) =>
        @setState
          data: res
      .fail (xhr, status, err) =>
        console.error @props.url, status, err.toString()


  handleCommentSubmit: (comment) ->
    jqxhr = $.ajax
      url:      @props.url
      dataType: "jsonp"
      type:     "POST"
      data:     comment

    jqxhr
      .done (data) =>
        @setState data:data
      .fail (xhr, status, err) =>
        console.error @props.url, status, err.toString()


  getInitialState: ->
    data: []


  componentDidMount: ->
    @loadCommentsFromServer()
    setInterval @loadCommentsFromServer, @props.pollInterval


  render: ->
    <div className="commentBox">
      <h1>Comments</h1>
      <CommentList data={@state.data} />
      <CommentForm onCommentSubmit={@handleCommentSubmit}/>
    </div>

React.render <CommentBox url="makejson.php" />, $content
```


#### tutorial 20

全部結合

```coffeescript:tutorial20.cjsx
##
# param doc.
# converter
#
converter = new Showdown.converter()

##
# class doc.
# Comment
#
Comment = React.createClass
  render: ->
    rawMarkup = converter.makeHtml @props.children.toString()
    <div className="comment">
      <h2 className="commentAuthor">
        {@props.author}
      </h2>
      <span dangerouslySetInnerHTML={{__html: rawMarkup}} />
    </div>

##
# class doc.
# CommentList
#
CommentList = React.createClass
  render: ->
    commentNodes = @props.data.map (comment) ->
      <Comment author={comment.author}>
        {comment.text}
      </Comment>

    <div className="commentList">
      {commentNodes}
    </div>

##
# class doc.
# CommentForm
#
CommentForm = React.createClass
  handleSubmit: (e) ->
    e.preventDefault();

    author = @refs.author.getDOMNode().value.trim()
    text   = @refs.text.getDOMNode().value.trim()

    if ! text or ! author
      return

    @props.onCommentSubmit
      author: author
      text:   text

    @refs.author.getDOMNode().value = ""
    @refs.text.getDOMNode().value   = ""

  render: ->
    <form className="commentForm" onSubmit={@handleSubmit}>
      <input type="text" placeholder="Your name" ref="author" />
      <input type="text" placeholder="Say something..." ref="text" />
      <input type="submit" value="Post" />
    </form>

##
# class doc.
# CommentBox
#
CommentBox = React.createClass
  loadCommentsFromServer: ->
    jqxhr = $.ajax
      url:      @props.url
      dataType: "jsonp"

    jqxhr
      .done (res) =>
        @setState
          data: res
      .fail (xhr, status, err) =>
        console.error @props.url, status, err.toString()


  handleCommentSubmit: (comment) ->
    comments   = @state.data
    newComment = comments.concat [comment]
    @setState
      data: newComment

    jqxhr = $.ajax
      url:      @props.url
      dataType: "jsonp"
      type:     "POST"
      data:     comment

    jqxhr
      .done (data) =>
        # レスポンスは無視する
        @setState
          data: newComment
      .fail (xhr, status, err) =>
        console.error @props.url, status, err.toString()


  getInitialState: ->
    data: []


  componentDidMount: ->
    @loadCommentsFromServer()
    setInterval @loadCommentsFromServer, @props.pollInterval


  render: ->
    <div className="commentBox">
      <h1>Comments</h1>
      <CommentList data={@state.data} />
      <CommentForm onCommentSubmit={@handleCommentSubmit}/>
    </div>

##
# function doc.
# React.render
#
React.render <CommentBox url="makejson.php" />, $content
```

Congrats!