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メソッドの引数に指定してますが、こうすることで実際にバインディングが実行されます。
なお、HTMLの部品をDOMで操作するので、スクリプトは「window.onload()」や「document.ready()」など「すべての部品が読み込まれた」段階で実行するようにして下さい。
あと、applyBindingsメソッドの前の「ko」ですが、これはknockout.jsのグローバルオブジェクトで、knockout.js固有の処理は、この「ko」を介して実行します。
もちろん双方向バインディングも可能
ただ、鋭い方は気づいたかもしれませんが、これだとVM→Viewの一方通行です。
実際、このあとテキストボックスの値を変えても…
はい。
テキストボックスは'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です。
実際、今度はちゃんとテキストボックスの値とラベルの値が連動します。
ちなみに、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 変数名」として、スクリプト内でそれに対応した関数を作成するだけです。
ちなみに(もうお分かりとは思いますが)、実行結果はこんな感じ。
ただし、公式サイトを見る限り、こんな感じで直接イベント名を記載できるのは、'click'だけのようです…残念。(というか、それなら「イベント」じゃなくて「クリックイベント」としたほうが良かったような…)
※ただしchangeなど、その他のイベントも、実装する方法はもちろんありますので、ご安心を。(それについては、後日記載予定です。)
とまあ、かなり駆け足でknockout.jsの概要を記載しましたが、いかがですか?
せっかくのGW、ちょっと気になった方がいたら、試してみるのも良いかもしれません。(割と導入は簡単ですから。)
…でもやっぱり、C#は…いいぞ。(結局それ)