Backbone.jsでModelに対応するViewをつくる


# 準備
## 依存ライブラリ
Backbone.jsはjQueryとunderscoreに依存しています。
準備を簡単にするために、各ライブラリをCDNから取得します。

```html
<head>
  <script src="https://code.jquery.com/jquery-2.2.2.js" integrity="sha256-4/zUCqiq0kqxhZIyp4G0Gk+AOtCJsY1TA00k5ClsZYE=" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.2/backbone.js"></script>
</head>

```

## RESTful API
[typicode/jsonplaceholder: A simple fake REST API server](https://github.com/typicode/jsonplaceholder)を使います。

```
npm install -g jsonplaceholder
jsonplaceholder
```

http://localhost:3000/posts/1 からリソースを取得します。

# 完成画面
![スクリーンショット 2016-03-24 10.40.24.png](https://qiita-image-store.s3.amazonaws.com/0/15964/19c8d870-f00c-2763-4933-0e7ca9b968b3.png "スクリーンショット 2016-03-24 10.40.24.png")

ES2015を使っています。

- Const
- Arrow Functions
- Template Strings
- Enhanced Object Literals

次のブラウザで動きます。

- Google Chrome 49.0.2623.87
- Firefox 44.0.2

Safari 9.0.3では動きません。

# ソースコード

```html
<head>
  <script src="https://code.jquery.com/jquery-2.2.2.js" integrity="sha256-4/zUCqiq0kqxhZIyp4G0Gk+AOtCJsY1TA00k5ClsZYE=" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.2/backbone.js"></script>
  <style>
    body {
      font-size: 1.1em;
    }

    input {
      font-size: 100%;
      width: 700px;
    }

    textarea {
      font-size: 100%;
      height: 100px;
      width: 700px
    }

    button {
      font-size: 100%;
    }
  </style>
</head>

<body>
  <script>
    const PostModel = Backbone.Model.extend({
        url: 'http://localhost:3000/posts/1'
      }),
      PostView = Backbone.View.extend({
        events: {
          'click button': function() {
            this.model
              .set({
                title: this.$('input').val(),
                body: this.$('textarea').val()
              })
              .save()
          }
        },
        initialize() {
          this.model.on('change', () => this.render())
        },
        render() {
          const json = this.model.attributes,
            html =
            `
              <div>title:</div>
              <input value="${json.title}">
              <div>body:</div>
              <textarea>${json.body}</textarea>
              <div>
                <button>押す</button>
              </div>
            `

          this.$el.html(html)
        }
      }),
      postModel = new PostModel(),
      postView = new PostView({
        model: postModel
      })

    $('body').append(postView.$el)
    postModel.fetch()
  </script>
</body>
```

## 解説
### PostModel
[Backbone.js の Model.fetch で RESTful API から情報取得](http://qiita.com/ledsun/items/d1146682e409c0687a1a) と同じです。
`url`をローカルアドレスに変更しています。

### PostView
#### eventsハッシュマップ
```js
events: {
  'click button': function() {
   this.model
    .set({
       title: this.$('input').val(),
       body: this.$('textarea').val()
    })
    .save()
  }
},
```

ボタンクリック時のイベントハンドラーを定義します。
thisでPostViewインスタンスを参照するため、アロー関数は使えません。

[backbonejs.org](http://backbonejs.org/) の


> views with declarative event handling


の部分です。

#### initialize関数

```js
initialize() {
  this.model.on('change', () => this.render())
},
```

初期化処理です。
モデルの`change`イベントに応じて`render`関数を実行します。
アロー関数なので、thisはinitialize関数実行時のthis、つまりPageViewのインスタンスです。

次のように、無名関数を使うと

```js
this.model.on('change', function (){ this.render() })
```

thisは`change`イベントのコールバック関数実行時のthis、つまりPageModelのインスタンスになり上手くうごきません。
http://backbonejs.org/#View-extend では

```js
this.listenTo(this.model, "change", this.render)
```

を推奨しています。

http://backbonejs.org/#View-constructor

> If the view defines an initialize function, it will be called when the view is first created.

initialize関数はインスタンス生成時に実行されます。より正確には、

https://github.com/jashkenas/backbone/blob/master/backbone.js#L1224

```js
var View = Backbone.View = function(options) {
  this.cid = _.uniqueId('view');
  _.extend(this, _.pick(options, viewOptions));
  this._ensureElement();
  this.initialize.apply(this, arguments);
};
```

コンストラクタの最後で実行されます。

https://github.com/jashkenas/backbone/blob/master/backbone.js#L1247

```js
initialize: function(){},
```

initialize関数が定義されていない場合は空関数が実行されます。

#### render関数

```js
render() {
  const json = this.model.attributes,
    html =
    `
      <div>title:</div>
      <input value="${json.title}">
      <div>body:</div>
      <textarea>${json.body}</textarea>
      <div>
        <button>押す</button>
      </div>
    `

  this.$el.html(html)
}
```

`this.model.attributes`からモデルの値を取得します。
Template Stringsを使ってhtml文字列を作成します。
jQuery.htmlを使って、自身の要素にhtml文字列を追加します。

#### main処理

```js
postModel = new PostModel(),
postView = new PostView({
  model: postModel
})

$('body').append(postView.$el)
postModel.fetch()
```

PostModelのインスタンスを作ります。
modelとしてPostModelのインスタンスを指定して、PostViewのインスタンスを作ります。
PostViewのインスタンスの`$el`プロパティに、作成したDOMのjQueryオブジェクトが入っています。
bodyに追加します。
PostModelのインスタンスの`fetch`メソッドで、RESTful APIから値を取得します。


# 関連記事

- [Backbone.jsでHello World （Backbone.Viewのサンプル）](http://qiita.com/ledsun/items/dde4f415205c888f54b4)
- [Backbone.jsのModel.fetch で RESTful API から情報取得](http://qiita.com/ledsun/items/d1146682e409c0687a1a)
- [Bakcbone.jsとBrowserifyを一緒に使う](http://qiita.com/ledsun/items/e6703e703d4b3e7df7d7)