RxJSと組み合わせるMVVMフレームワークWebRx

WebRxはRxJSと組み合わせて使うことを前提とした、KnockoutJS風味のMVVMフレームワークだ。今回はWebRxを動かしてRxJSとの連携をやるところまでを記事に書いた。

RxJSに関しては、[RxJSで始めるRx(Reactive Extensions)入門1 - Qiita](http://qiita.com/erukiti/items/6a82dfd3506e9773d592) を参考にしてほしい。

## まずはWebRxを動かしてみる

[WebRx - Getting Started](http://webrxjs.org/docs/getting-started.html) を、参考に WebRx を動かしてみる

#### 準備

* node.js がインストールされていること

```
npm init
npm install webrx --save
npm install rx --save
npm install express --save
```

#### ソースコード

```html
<!doctype html>
<html>
  <head>
    <title>WebRx</title>
  </head>
  <body>
    <div data-bind="view: 'main'"></div>

    <script src="node_modules/rx/dist/rx.all.js"></script>
    <script src="node_modules/webrx/dist/web.rx.js"></script>
    <script src="index.js"></script>
  </body>
</html>
```

```js
wx.app.component('hello', {
  viewModel: function() {
    this.firstName = 'Bart';
    this.lastName = 'Simpson';
  },
  template: 'The name is <span data-bind="text: firstName + \' \' + lastName"></span>'
});

wx.router.state({
  name: "$",
  views: { 'main': "hello" }
});

wx.router.reload();

wx.applyBindings();
```

### なぜかローカルで動かない

```
Uncaught SecurityError: Failed to execute 'replaceState' on 'History': A history state object with URL 'file:///' cannot be created in a document with origin 'null'.
```

このようなエラーが出て、動作しないので express を使って動かしてみる。

```js
express = require('express');

app = express();

app.use(express.static('./'));
app.listen(3000);
```

必要なファイルを HTTP で export できれば、何を使ってもかまわない。
これをnodeで動かして、[http://localhost:3000/](http://localhost:3000) をたたくと意図通りの結果がブラウザ上に表示される

### テキストインプットの変更時にRxのオペレータが使える

```html
<!doctype html>
<html>
  <head>
    <title>WebRx</title>
  </head>
  <body>
    The name is <span data-bind="text: personName"></span><br>
    <input type="text" data-bind="textInput: @personName">

    <script src="node_modules/rx/dist/rx.all.js"></script>
    <script src="node_modules/webrx/dist/web.rx.js"></script>
    <script src="index.js"></script>
  </body>
</html>
```

```js
var myViewModel = {
    personName: wx.property('Bob'),
    personAge: wx.property(123)
};

myViewModel.personName.changed
  .map(function(s){return s.length;})
  .subscribe(function(s){console.log(s);});

wx.applyBindings(myViewModel);
```

これは、テキスト入力時に、テキストインプットに入力されている文字列の長さをコンソールに出力するコードだ。ViewModelのプロパティのchangedが、Rx.observable なので、おなじみのRxJSのオペレータを使うことができる。

### Output Property

```html
<!doctype html>
<html>
  <head>
    <title>WebRx</title>
  </head>
  <body>
    full name: <span data-bind="text: fullName"></span><br>
    <input type="text" data-bind="textInput: @firstName"><br>
    <input type="text" data-bind="textInput: @lastName">

    <script src="node_modules/rx/dist/rx.all.js"></script>
    <script src="node_modules/webrx/dist/web.rx.js"></script>
    <script src="index.js"></script>
  </body>
</html>
```

```js
function MyViewModel() {
  this.firstName = wx.property('Bob');
  this.lastName = wx.property('Simpson');
  this.fullName = wx.whenAny(this.firstName, this.lastName, function(firstName, lastName){
    return firstName + ' ' + lastName;
  }).toProperty();
}

myViewModel = new MyViewModel();

myViewModel.fullName.changed
  .map(function(s){return s.length;})
  .subscribe(function(s){console.log(s);});
wx.applyBindings(myViewModel);
```

さきほどの例と同じく、consoleにfullName(firstName + ' ' + lastName)の長さを垂れ流しにしているが、今回の例はRxの要素はただのおまけだ。

WebRx自体の機能として、別のプロパティが変更したのを検知して変更される出力プロパティ(リードオンリー)というモノを使ってみたコードだ。

RxJSとDOM操作を組み合わせれば、出力プロパティを使わなくても可能だが、もちろんこちらの方が操作としては楽だろう。