echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

knockout.jsでMVVMを実装 その1

4月から新しいプロジェクトにアサインしたのですが、早くも「C#やれるって話だったのに全然やれないじゃん!話が違う!」みたいな事になってます…

でも「Xamarinやりたい!XAMLみたいにMVVMやりたい!」とか思ってたら、(C#ではないですが)「knockout.js」というJavaScriptフレームワークでMVVMを使用することになったので、復習がてらメモ。

knockout.jsとは?

  • JavaScriptのクライアントサイド MVVMフレームワーク
  • AngularJSに比べて、簡潔でとっつきやすく、敷居が低い…かも。(ただし簡潔だから良いというわけでもないので、「AngularJSより優秀だ!」なんて言うつもりはない。)
  • jQueryとは違い、HTML内の各コントロールはDOMで操作する。

 ※5/12訂正:DOMを操作するのではなく、バインドした変数を使って操作します。すいません。

公式サイト
日本語版ドキュメント(非公式)


インストールと実行
…とは書いたものの、別に「インストール」って程でもなく、jQueryなどと同様、knockout.js本体にscriptタグでリンクするだけ。
リンクもCDN形式でも良いし、公式サイトからダウンロードしたファイルへのパスを通してももちろんOK。

では、百聞は一見にしかずって事で、ソースをば。
なお面倒くさいソースが短かったので、*.htmlファイル内に直接スクリプトも書いてますが、本当はファイルを分けたほうが良いと思います。

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="./knockout-3.4.2.js"></script>
<script>
window.onload = function() {

    var self = this;

    var myViewModel = function() {
        self.myText = "Hello kockout.js!";
        
        onClick = function() {
            alert('Button is clicked.');
        };
    };

    var vm = new myViewModel();
    ko.applyBindings(vm);
};
</script>
</head>
<body>
<input type="text" data-bind="value: myText"><button data-bind="click: onClick">button</button><br>
<span data-bind="text: myText"></span><br>
</body>
</html>

とりあえず、HTMLタグ内に「data-bind」とかいう、明らかに見慣れない属性が目につきますが、これがknockout.jsのデータバインドの仕組みです。
この「data-bind」内で指定したプロパティ(value,text等)に、data-bind内で指定した変数(ここではmyText)に値を入れて、データバインドを実装しています。

で、JavaScriptの方を見ると、まず

var myViewModel = function()

という文がありますが、この「myViewModel」がいわばMVVMのViewModelの本体で、この中でHTML内の変数など、色々な定義をします。

そしてその中に

self.myText = "Hello kockout.js!";

とありますが、ここで「HTML内のmyTextに'Hello kockout.js!'を代入しなさい」という処理を実行しているわけです。

そしてmyViewModelの定義が終わったあとで、最後に

var vm = new myViewModel();
ko.applyBindings(vm);

として、myViewModelをapplyBindingsメソッドの引数に指定してますが、こうすることで実際にバインディングが実行されます。
f:id:Makky12:20170428204825p:plain

なお、HTMLの部品をDOMで操作するので、スクリプトは「window.onload()」や「document.ready()」など「すべての部品が読み込まれた」段階で実行するようにして下さい。
あと、applyBindingsメソッドの前の「ko」ですが、これはknockout.jsのグローバルオブジェクトで、knockout.js固有の処理は、この「ko」を介して実行します。


もちろん双方向バインディングも可能
ただ、鋭い方は気づいたかもしれませんが、これだとVM→Viewの一方通行です。
実際、このあとテキストボックスの値を変えても…
f:id:Makky12:20170428204832p:plain

はい。
テキストボックスは'Hello Xamarin!'に変わったのに、ラベルは'Hello knockout.js'のままです。
つまりView→VMバインディングが行われていません。

といっても、別にchangeイベントとかは全く必要なく、スクリプトを1行変えるだけで解決します。

window.onload = function() {

    var self = this;

    var myViewModel = function() {
        // self.myText = "Hello kockout.js!";
        self.myText = ko.observable("Hello kockout.js!");
        
        onClick = function() {
            alert('Button is clicked.');
        };
    };

    var vm = new myViewModel();[f:id:Makky12:20170428210141p:plain]
    ko.applyBindings(vm);

    alert("loaded.");
};

上記の通り、「self.myText =」の右辺を「ko.observable("Hello kockout.js!");」に変えるだけで、双方向バインディングの完成です。
ko.observable()メソッドの戻り値を変数に入れる事で、その変数はどこで変更されても、その変更がリアルタイムにViewやVMなどに反映されます。
※ちなみに、引数は初期値になりますので「デフォルトは未入力」という場合は、引数に何も指定しなければOKです。

実際、今度はちゃんとテキストボックスの値とラベルの値が連動します。
f:id:Makky12:20170428210141p:plain

ちなみに、ko.observable()を実行した変数の値の取得と設定(=getter,setter)ですが

var getter = vm.myText();  // getter
vm.myText('Hello C#!');    // setter

て感じで、[ViewModelの変数名].[変数名(引数なし)]だとgetter、[ViewModelの変数名].[変数名(引数あり)]だとsetterです。
(setterの場合、引数に指定した値が代入される。)

イベントについて
最後にイベントですが、まあ'button'コントロールにこれ見よがしに「data-bind="click: onClick"」なんて書いてるので、おおよそ見当はついたと思います。
まあそんな感じで「data-bind」に「イベント('click'など):関数 or 変数名」として、スクリプト内でそれに対応した関数を作成するだけです。

ちなみに(もうお分かりとは思いますが)、実行結果はこんな感じ。
f:id:Makky12:20170428211550p:plain

ただし、公式サイトを見る限り、こんな感じで直接イベント名を記載できるのは、'click'だけのようです…残念。(というか、それなら「イベント」じゃなくて「クリックイベント」としたほうが良かったような…)

※ただしchangeなど、その他のイベントも、実装する方法はもちろんありますので、ご安心を。(それについては、後日記載予定です。)

とまあ、かなり駆け足でknockout.jsの概要を記載しましたが、いかがですか?
せっかくのGW、ちょっと気になった方がいたら、試してみるのも良いかもしれません。(割と導入は簡単ですから。)


…でもやっぱり、C#は…いいぞ。(結局それ)