実践knockout
TRANSCRIPT
実践 knockout@hakurai
自己紹介
•hakurai
•Backlog開発チーム@ヌーラボ
•関ジャバ
•hoge駆動
•奈良模型愛好会
1/10 新機能リリース!
knockoutをフル活用しました
KNOCKOUTについて
•JavaScriptでMVVMを実現するフレームワーク
• jQueryには依存していない
•サポートブラウザ
• IE 6+, Firefox 2+, Chrome, Safari, others
KNOCKOUTのコンセプト
•宣言型データバインディング
•UIの自動更新
•依存性追跡
•テンプレート
宣言型データバインディング
•HTMLの要素とJavaScriptの値を結びつける
•data-bind属性を要素に追加するだけ
data-bindの例
•div要素のテキストと vm.message の値をバインド
<div data-bind="text: message"></div>
vm = { message: ‘hello world!’};
ko.applyBindings(vm);
<html xmlns="http://www.w3.org/1999/xhtml"><head> <script type="text/javascript" src="../lib/knockout-2.1.0.js"></script></head><body><div data-bind="text: message"></div><script type="text/javascript" src="script.js"></script></body></html>
(function(){ var vm = { message: 'hello world!' }; ko.applyBindings(vm);}());
hello world!を表示する例
<html xmlns="http://www.w3.org/1999/xhtml"><head> <script type="text/javascript" src="../lib/knockout-2.1.0.js"></script></head><body><div data-bind="text: message"></div><script type="text/javascript" src="script.js"></script></body></html>
(function(){ var vm = { message: 'hello world!' }; ko.applyBindings(vm);}());
step1 data-bind属性を設定
<html xmlns="http://www.w3.org/1999/xhtml"><head> <script type="text/javascript" src="../lib/knockout-2.1.0.js"></script></head><body><div data-bind="text: message"></div><script type="text/javascript" src="script.js"></script></body></html>
(function(){ var vm = { message: 'hello world!' }; ko.applyBindings(vm);}());
step2 ViewModelを定義
<html xmlns="http://www.w3.org/1999/xhtml"><head> <script type="text/javascript" src="../lib/knockout-2.1.0.js"></script></head><body><div data-bind="text: message"></div><script type="text/javascript" src="script.js"></script></body></html>
(function(){ var vm = { message: 'hello world!' }; ko.applyBindings(vm);}());
step3 ViewとViewModelをバインド
6種類+ のbinding
•visible binding
• text binding
•html binding
•css binding
•style binding
•attr binding
•custom binding
visible binding
•要素の表示・非表示の切り替え
•いわゆるdisplay:noneのon/offの切り替え
•オブジェクトがtrueまたはnon-nullなら表示
• false, 0, null undefinedなら非表示
<div data-bind="visible: showMsg">hello world!</div>
text binding
•オブジェクトをテキストとして要素に設定
<div data-bind="text: message"></div>
html binding
•オブジェクトをinnerHTMLとして要素に設定
<div data-bind="html: message"></div>
vm = { message: ‘<strong>hello world!</strong>’};
css binding
•要素のclassの追加・削除
•オブジェクトがtrueならclassを追加
• falseならclassを削除<div data-bind="css:{selected: status}"></div>
vm = { status: true};
trueならclassを追加
<div data-bind="css:{selected: status}"></div>
vm = { status: true};
<div class=”selected”></div>
falseならclassを削除
<div data-bind="css:{selected: status}"></div>
vm = { status: false};
<div></div>
複数指定もできます<div data-bind="css:{selected: status, warn: over}"></div>
vm = { status: true, over: true};
<div class=”selected over”></div>
style binding
•要素のstyleを設定
•css bindingがあるので余り使わないかも
<div data-bind="style:{color: bg}"></div>
vm = { bg: ‘red’};
style bindingの注意点
•スタイル名に-(ハイフン)は使えない
•キャメルケースに書き換える
• font-weight → fontWeight
• text-decoration → textDecoration
attr binding
•要素の属性を設定
•hrefとかsrcとか
<a data-bind="attr:{href: url}"></a>
vm = { url: ‘http://www.backlog.jp/’};
custom binding
•好きな名前でbindingを自分で作れる
•これまで紹介したbindingも同じ仕組でできている
<div data-bind="hoge: message"></div>
ko.bindingHandlers.hoge = { init: function(){}, update: function(){}};
UIの自動更新
•UIとオブジェクトの状態を同期する
•オブジェクトを同期するko.observable
•配列を同期するko.observableArray
UIの自動更新
•ko.observableを使用していないプロパティは同期されない
vm = { name: ‘aoshima’, age: ko.observable(25)};
同期されない
同期される
•ko.applyBindingsの呼び出し時の値でバインドされる
最初のバインド
vm = { name: ‘aoshima’, age: ko.observable(25)};
ko.applyBindings(vm);
name = ‘hasegawa’;age(26);
aoshima25
•nameはko.observableでラップされていない
•値が変わっても更新されない
値の更新
vm = { name: ‘aoshima’, age: ko.observable(25)};
ko.applyBindings(vm);
name = ‘hasegawa’;age(26);
aoshima25
•ageはko.observableでラップされている
•値が変わると自動的に更新される
値の更新
vm = { name: ‘hakurai’, age: ko.observable(25)};
ko.applyBindings(vm);
name = ‘hasegawa’;age(26);
aoshima26
依存関係追跡
•オブジェクト同士に依存関係を持たせる
•ko.computed
•別のko.observableの値が変更されると更新される
依存関係追跡
•ko.computedに渡した関数内のko.observableの値が更新されるとko.computedの値も更新される
function vm(){ this.firstName =ko.observable('Bob'); this.lastName =ko.observable('Smith'); this.fullName = ko.computed(function () { return this.firstName() + " " + this.lastName(); }, this); }
テンプレート
•テンプレートを利用してオブジェクトを表示する
<div data-bind="template: { name: 'person-template', data: buyer }"></div> <div data-bind="template: { name: 'person-template', data: seller }"></div><script type="text/html" id="person-template"> <h3 data-bind="text: name"></h3> <p>Credits: <span data-bind="text: credits"></span></p></script>
vm = { buyer: { name: 'Franklin', credits: 250 }, seller: { name: 'Mario', credits: 5800 }}
<div data-bind="template: { name: 'person-template', data: buyer }"> <h3 data-bind="text: name">Franklin</h3> <p>Credits: <span data-bind="text: credits">250</span></p></div> <div data-bind="template: { name: 'person-template', data: seller }"> <h3 data-bind="text: name">Mario</h3> <p>Credits: <span data-bind="text: credits">5800</span></p></div>
vm = { buyer: { name: 'Franklin', credits: 250 }, seller: { name: 'Mario', credits: 5800 }}
テンプレートが展開された
Control flow
• foreach binding
• if binding
• ifnot binding
•with binding
foreach binding
•配列分要素を繰り返し追加する
<ul data-bind=”foreach: users”> <li data-bind=”text: name”></li></ul>
vm = { users: [{name: ‘hoge’}, {name: ‘fuga’}]}
foreach binding
•この例の場合 li 要素が2つ分表示される
<ul data-bind=”foreach: users”> <li data-bind=”text: name”>hoge</li> <li data-bind=”text: name”>fuga</li></ul>
vm = { users: [{name: ‘hoge’}, {name: ‘fuga’}]}
users配列分繰り返し
foreach bindingと自動更新
•UIを自動更新する場合はko.observableArrayを使う
<ul data-bind=”foreach: users”> <li data-bind=”text: name”></li></ul>
vm = { users: ko.observableArray( [{name: ‘hoge’}, {name: ‘fuga’}] )};
foreach bindingと自動更新
•pushで要素を追加すると li 要素も追加される
vm.users.push({name:‘piyo’});
<ul data-bind=”foreach: users”> <li data-bind=”text: name”>hoge</li> <li data-bind=”text: name”>fuga</li> <li data-bind=”text: name”>piyo</li></ul>
if binding
•オブジェクトがtrueの場合に要素を追加する
<div data-bind=”if: show”> <span>hello world</span></ul>
vm = { show: ko.observable(false);}
ifnot binding
•オブジェクトがtrueの場合に要素を追加しない
<div data-bind=”ifnot: notShow”> <span>hello world</span></ul>
vm = { notShow: ko.observable(false);}
with binding
•バインディングコンテキストを変更する
<span data-bind=”text: title”></span><div data-bind=”with: data”> <ul data-bind=”foreach: users”> <li data-bind=”text: name”></li> </ul></div>
vm = { title: “hello” data: { users: [{name: ‘hoge’}, {name: ‘fuga’}] }}
with binding
•vmオブジェクトのコンテキスト
<span data-bind=”text: title”></span><div data-bind=”with: data”> <ul data-bind=”foreach: users”> <li data-bind=”text: name”></li> </ul></div>
vm = { title: “hello” data: { users: [{name: ‘hoge’}, {name: ‘fuga’}] }}
with binding
•vm.dataオブジェクトのコンテキスト
<span data-bind=”text: title”></span><div data-bind=”with: data”> <ul data-bind=”foreach: users”> <li data-bind=”text: name”></li> </ul></div>
vm = { title: “hello” data: { users: [{name: ‘hoge’}, {name: ‘fuga’}] }}
with binding
•入れ子構造のオブジェクトにアクセスしやすくなる
•大きなViewModelを複数のオブジェクトに分割
<span data-bind=”text: title”></span><div> <ul data-bind=”foreach: data.users”> <li data-bind=”text: name”></li> </ul></div>
コンテキスト変数
•$parent
•$parents
•$root
•$data
•$index
•$parentContext
•$context
•$element
親のコンテキストにアクセス
•$parent
•$parents
•$root
•$data
•$index
•$parentContext
•$context
•$element
foreach中のオブジェクト
•$parent
•$parents
•$root
•$data
•$index
•$parentContext
•$context
•$element
配列中のインデックス
現在の要素
•$parent
•$parents
•$root
•$data
•$index
•$parentContext
•$context
•$element<div id=”item1” data-bind=”text: $element.id”></div>
item1
working with form fields
•click binding
•event binding
•submit binding
•enable binding
•disable binding
•value binfing
•hasfocus
•checked binding
•options binding
•selectedOptions binding
•uniqueName binding
click binding
•クリック人のイベントハンドラを設定する
<span data-bind=”text: message”></span><button data-bind=”click: onclick”>click me</button>
vm = { message: ko.observable(), onclick: function(data, event){ this.message(‘hello world!’); }}
click binding
• foreach中の要素の場合引数に対応するオブジェクトが渡される
•基本的にevent.preventDefaultされる
•したくない場合は関数からtrueを返す
イベントバブリング
•data-bind中で<イベント名>Bubbleを指定する
•クリックイベントのバブリングを止める場合
<button data-bind=”click: onclick, clickBubble: false”>click me</button>
小ネタ
仮想要素サポート
•コメントを使ってバインドを指定できる
•引数には更新後の値が渡される
<ul> <li>first</li> <!-- ko foreach: items --> <li data-bind=”text: $data”></li> <!-- /ko --></ul>
ko.observable.subscribe
•ko.observableの値が変更された時に呼び出す関数を設定できる
•引数には更新後の値が渡される
this.count = ko.observable(1);this.count.subscribe(function(newValue){ alert(newValue);});
スロットル拡張
•ko.observableの値が最後に変更されてから一定時間経った場合にUIを更新する
vm = { count: ko.observable(1).extend({ throttle: 5000})}ko.applyBindings(vm);
vm.count(2);
5 秒後に 2 に更新される
• jQueryを使ったDOM操作を隠蔽したり
•使いこなすと結構便利
custom binding
ko.bindingHandlers.<バインド名> = { init: function(element,valueAccessor){ // applyBinding 時に一度だけ呼ばれる // 主に element に対してイベントハンドラを追加したりする }, update: function(element, valueAccessor){ // バインドしたko.observable()が変更されると呼ばれる // element を更新したりする }};
ko.utils
•ko.utils.unwrapObservable
•ko.utils.arrayPushAll
•など
•公式サイトのチュートリアル
•http://learn.knockoutjs.com/
•https://github.com/hakurai/knockout-sample
サンプルコード
•http://hakurai.github.com/javap.js/web/
•knockout便利!
まとめ