ビューの一部分を`jQuery.ajax`と`Vue.js`で一定間隔で更新する


## はじめに
既にあるWebアプリケーションのビューをほとんどいじることなく、`Vue.js`を導入して、一部の要素を一定間隔で更新することができましたので、ここに紹介します。

### はじめた動機と`Vue.js`の印象
- 既にあるWebアプリケーションのビューの一部の要素を一定間隔で更新したい
- `Angular`、`React`などのjavascriptフレームワークをまともに使わずにここまできた
- `jquery`で何が悪いの、とまでは言わないが、データをセットするためにDOMを扱いたくない
- とはいえ実現したいのは、データバインディングだけで、SPAじゃない
- `Vue.js`（もしくは他のフレームワーク）を導入するからといって、ビューをすべて作り直すようなことはしたくない
- `Laravel`は`Vue.js`をコアに取り込んだらしい
- `Vue.js`良さそう from 『[私たちはなぜReactではなくVue.jsを選んだのか | プログラミング | POSTD](http://postd.cc/why-we-chose-vuejs-over-react/)』

## Vueインスタンスはアプリケーション？
公式ガイドの「[はじめに](https://jp.vuejs.org/v2/guide/index.html)」をみていると、Vueインスタンスはアプリケーションとして扱うのが流儀みたいにみえます。

```js
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})
```

アプリケーションって具体的に何って話ですが、このコードを見たときの僕の印象は下記のとおりです。

- ビューを構成する単なる一要素ではない？
- 子要素を包括する存在？
- 子要素は`Vue.component`で扱うべき？

## VueインスタンスはViewModel？
「はじめに」の次のページに「[Vue インスタンス](https://jp.vuejs.org/v2/guide/instance.html)」という章があり、そちらでは、VueインスタンスはViewModelとして扱われています。

```js
var vm = new Vue({
  // オプション
})
```

```
Vue のデザインは、MVVM パターンと厳密には関連が無いものの、部分的には影響を受けています。
慣例として、 vm (ViewModel の略) を Vue インスタンスの変数名としてよく使います。
```

シングルページアプリケーションみたいに、全体を管理したい場合は、アプリケーションとして扱って、配下にコンポーネントを配置したほうが効率的で、部品的に使う場合は、ViewModelとして扱うのが良さそうです。

## Vueインスタンスを動的に生成、更新するクラスをES6で実装
サーバーサイドで使われる言語と同様、フロントエンドでもES6のclass構文を使って、クラスを実装していきます。
最終的には、下記のクラスを実装し、`webpack`+`babel`でES5に変換してビュー側で読み込みます。

```
class SomeViewModels {

    constructor(endpoint, selector) {
        this.endpoint = endpoint;
        this.selector = selector;
    }

    bind() {
        let $elements = $(this.selector);
        let self = this;

        $.each($elements, function(index, value){
            let $element = $(value);

            new Vue({
                el: '#' + $element.attr('id'),
                data: {
                    id: $element.data('id'),
                    a: '',
                    b: '',
                    c: ''
                },
                created: function() {
                    self._getData(this);
                }
            });
        });
    }

    _getData(vue) {
        let self = this;
        $.ajax({
            url: this.endpoint + '?id=' + vue.id,
            dataType: 'json'
        }).then(function(data){
            self._bindData(vue, data);
        }).always(function(){
            setTimeout(function(){
                self._getData(vue);
            }, 60 * 1000);
        });
    }

    _bindData(vue, data) {
        if (!data) {
            return null;
        }
        $.each(data, function(index, value){
            vue.a = value.a;
            vue.b = value.b;
            vue.c = value.c;
        });
    }
}

$(document).ready(function(){
    let endpoint = $('#api-endpoint').data('api-endpoint');
    let selector = '.some-target';
    let vms = new SomeViewModels(endpoint, selector);
    vms.bind();
});
```

尚、テンプレート側は、下記のように、`v-text`属性を追加してあげるだけで、データバインドされます。
テンプレート構文（ {{ }} ）だとエスケープしたりしないといけないので、`v-text`属性を使う方が便利です。

```html
<div v-text="a"></div>
<div v-text="b"></div>
<div v-text="c"></div>
```

## その他（環境まわりなど）
`webpack`+`babel`を使った環境構築するまでの記事はたくさんあるので、ここでは細かく説明しませんが、ポイントになりそうなところは整理しておきます。

### Vue.js 本体はCDNのものをそのまま利用
Vue.js自体をwebpackでビルドする方法もありそうなんですが、特に必要性がないので、CDNに置いてあるものをそのまま使います。
httpとhttps両方に対応しているので、スキーマレスで書いても問題ありません。

```html
<script src="//unpkg.com/vue@2.1.10/dist/vue.min.js"></script>
```

### package.json

```json
{
  "scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "babel-core": "^6.22.1",
    "babel-loader": "^6.2.10",
    "babel-plugin-transform-runtime": "^6.22.0",
    "babel-preset-es2015": "^6.22.0",
    "babel-runtime": "^6.22.0",
    "cross-env": "^3.1.4",
    "css-loader": "^0.26.1",
    "stylus": "^0.54.5",
    "stylus-loader": "^2.4.0",
    "webpack": "^2.2.1"
  },
  "dependencies": {
    "es6-promise": "^4.0.5"
  }
}
```

### webpack.config.js
`webpack`の設定ファイルは下記のとおりです。

```js
const webpack = require('webpack');

module.exports = {
    context: __dirname + "/src",
    devtool: 'source-map',
    entry: {
        app: ["./scripts/app.js"]
    },
    output: {
        filename: "scripts/[name].min.js",
        libraryTarget: 'var',
        path: __dirname + "/dist"
    },
    plugins: [
        new webpack.optimize.UglifyJsPlugin({
            sourceMap: true
        })
    ],
    module: {
        loaders: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel-loader',
                query: {
                    presets: ['es2015'],
                }
            }
        ]
    }
};
```

細かい話ですが、`webpack.optimize.UglifyJsPlugin`を使う場合は、引数に`sourceMap: true`を指定するとともに、`devtool: 'source-map'`を指定しないと、mapファイルが生成されなかったです。

## おわりに
結果的に、既にあるWebアプリケーションのテンプレートに、`v-text`属性をつけ加えるだけで、対象の要素を一定間隔で更新することができました。
他のフレームワークはともかく、これからは、積極的に`Vue.js`を使っていこうと思います。
間違っているところ等ありましたら、コメントにてご指摘いただけますと助かります。

ではでは。