knockout.jsにknockoutされた話
2014/02/28 Fukuoka Frontend Frogs Ex Hiroyuki Tashima
“GGoodd ffiigghhtt oonn tthhee ssiiddee wwiitthh tthhee bbeesstt FFrraammeewwoorrkk..”
-- NNaappoolleeoonn BBoonnaappaarrttee
神は最高のフレームワークを使う側に味方する
- ナポレオン・ボナパルト
まずはじめに
は?誰?
田島寛之
【自己紹介】
fb hiroyuki.tashima
・日本唐揚協会公認カラアゲニスト ・株式会社gumi West ・フロントエンドエンジニア
@HiroyukiTashima
knockout.jsにknockoutされた話
なぜ
knockout.js ?
まずはこちらを
ご覧いただきたい
(・ワ・) : 福岡出張中のHさん(仮)
たしま:(・ε・)
たしまさん、knockout.jsってフレームワーク
使いませんか?
(・ワ・)
ア、イイデスネー > (・ε・)
knockout.jsだとかなりコード量減って
いい感じになりますよ〜
(・ワ・)
ア、イイデスネー > (・ε・)
たしまさんって何かフレームワーク使ってます?(・ワ・)
ア、イイデスネー > (・ε・)
要約
表示物、データ多い
表示物、データ多い
DOM操作も多い
+
表示物、データ多い
DOM操作も多い
+
↓
js長くなる
表示物、データ多い
DOM操作も多い
+
↓
js長くなる
↓
(´・ω・`)わけわかめ
よわい(確信)
表示物、データ多い
表示物、データ多い
DOM操作も多い
+
表示物、データ多い
DOM操作も多い
+
knockout.jsを使う
↓
表示物、データ多い
DOM操作も多い
+
↓
↓
knockout.jsを使う
コード量:減 動作:軽
つよい(確信)
で、knockout.jsを使うことに…
knockout.jsとは?
そもそも
knockout.jsとは?knockout.jsは
データモデルを基盤とし、リッチなUI構築を行う目的で開発された
JavaScriptライブラリである。
wikipediaのコピペを貼るんじゃあないよ!
> (・ε・#)
knockout.js・UI用JavaScriptライブラリ
・jQuery等の別ライブラリとぶつからない
・MVVM(Model, View, ViewModel)
・DOM操作に関するコードがほぼいらない
・軽い(動作、容量)
knockout.js
3大ポイント
knockout.js
・MVVM
Model : Ajaxから受け取ったデータ
View : HTML
ViewModel : ページ内で一時的に使用、変更するデータ
knockout.js
・View(HTML)からbind指定
click, style, css, attr, …
knockout.js・常に変数の変化を監視(observable)
概要だけじゃよく分からんすなぁ
> (・ε・)
使用例
テキスト入力 + ボタンclick
window & テキスト表示
↓
つまり
テキスト入力
ボタンclick
こうなる
テキスト表示ウィンドウ表示
knockout.jsを使わない場合
<div id="main"> <input type="text" id="inputForm"></input> <buKon onclick="openPopup()"> o p e n </buKon> </div> <div id="popup" style="display:none"> <div style="opacity:1;"> <font color="#ffffff">popupWindow</font><br /> <div style="color:#ffffff;">input.value : <span id="inputResult"></span> </div> <buKon onclick="closePopup()"> c l o s e </buKon> </div> </div>
HTML
HTML
JavaScriptでDOM取得するための
id、class指定が必要
var popup = document.getElementById("popup"); var input = document.getElementById("inputForm"); var inputResult = document.getElementById("inputResult"); funcVon openPopup(){ inputResult.innerHTML = input.value; popup.style.display="block"; } funcVon closePopup(){ popup.style.display="none"; }
JavaScript
JavaScript
・DOM elementの取得
・styleの変更
・innerHTMLで変更
(´・ω・`)うーん…
話
聞
!
Knockout.js
使
!
knockout.jsを使った場合
funcVon AppViewModel(){ this.popupVisible = ko.observable(false); this.inputValue = ko.observable(""); } ko.applyBindings(new AppViewModel());
JavaScript
変数をkoの監視対象に設定
koを作動させる(第二引数なし→document全体が対象)
JavaScript
ViewModelの作成
変数の宣言&監視対象化
bindingしてkoを作動
HTML<div="main"> <input type="text" data-‐bind="value: inputValue"></input> <buKon data-‐bind="click: popupVisible(true)"> o p e n </buKon> </div> <div id="popup" data-‐bind="style: {display: popupVisible() ? 'block' : 'none' }"> <div style="opacity:1;"> <font color="#ffffff">popupWindow</font><br /> <div style="color:#ffffff;">input.value : <span data-‐bind="text: inputValue()"></span></div> <buKon data-‐bind="click: popupVisible(false)"> c l o s e </buKon> </div> </div> 変数をSET
変数をGET
HTML
data-bind属性で関連付け(koで監視中の変数とリンク)
DOM取得用のid, class不要
変数名() : getter
変数名(value) : setter
var popup = document.getElementById("popup"); var input = document.getElementById("inputForm"); var inputResult = document.getElementById("inputResult"); funcVon openPopup(){ inputResult.innerHTML = input.value; popup.style.display="block"; } funcVon closePopup(){ popup.style.display="none"; }
Before
funcVon AppViewModel(){ this.popupVisible = ko.observable(false); this.inputValue = ko.observable(""); } ko.applyBindings(new AppViewModel());
After
圧倒的ではないか
knockout.jsは
次
Ajax通信(jQueryを使用)
適当に表示物を更新
↓
HTML<div id="main"> <buKon data-‐bind="click: ajaxStart"> Ajax Start </buKon> </div> <div id="popup" data-‐bind="style: {display: popupVisible ? 'block' : 'none' }"> <div style="opacity:1;"> <font color="#ffffff">popupWindow</font><br /> <div style="color:#ffffff;">ajax data<br /> <span data-‐bind="text: ajaxData"></span></div> <buKon data-‐bind="click: popupVisible(false) ">close</buKon> </div> </div>
funcVon AppViewModel(){ var self = this; self.popupVisible = ko.observable(false); self.ajaxData = ko.observable(); self.ajaxStart = funcVon(){ $.ajax({ url: “tset”, type: ”GET", success: funcVon (json){ var jsonObj = ko.toJS(json); self.ajaxData(jsonObj) self.popupVisible(true); } }) } } ko.applyBindings(new AppViewModel());
JavaScript (一部)
∧_∧( ・ω・)=つ≡つ(っ ≡つ=つ`/ ) ババババ(ノ ̄∪
knockout.js?
ボコボコにしてやんよー
(って思うでしょう?)
あれ?動かない…
なぜだ、なぜ動かん…
knockout.js
よくある落とし穴
knockout.jsの落とし穴
監視中(observable)の変数
getterとsetterをよく間違える!
knockout.jsの落とし穴
監視中(observable)の変数
getterとsetterをよく間違える!
getter → test_value()
setter → test_value(val)
knockout.jsの落とし穴しかも
knockout.jsの落とし穴しかも
<input type="text" data-‐bind="value: inputValue"></input>
valueへのbindはgetterの()は不要
knockout.jsの落とし穴しかも
<input type="text" data-‐bind="value: inputValue"></input>
valueへのbindはgetterの()は不要
data-‐bind=“style: {display: popupVisible() ? ‘block’ : ‘none’ } "
でもこいつ↑には()必要
knockout.jsの落とし穴しかも
<input type="text" data-‐bind="value: inputValue"></input>
valueへのbindはgetterの()は不要
(# ゚Д゚) < 紛らわしいんじゃーい!
data-‐bind=“style: {display: popupVisible() ? ‘block’ : ‘none’ } "
でもこいつ↑には()必要
事態は見えてきた、後は簡単だ
あれ?また動かない…
なぜだ、なぜ(ry
knockout.jsの落とし穴
同変数の監視化(ko.observable)は
2回目以降はスルーされる
knockout.jsの落とし穴
同変数の監視化(ko.observable)は
2回目以降はスルーされる
funcVon AppViewModel(){ this.popupVisible = ko.observable(false); this.popupVisible = ko.observable(true); }
2回目はスルーされるのでfalseのまま
knockout.jsの落とし穴
ko.observableは1回のみ。
変数への代入はsetterで指定!
funcVon AppViewModel(){ this.popupVisible = ko.observable(false); this.popupVisible(true); }
setterで変数の内容変更
事態は見えて(ry
あれ?また…
五飛教えてくれ。俺は後何回画像を張ればいい…? 俺はあと何回パプテマス様の画像を張ればいいんだ?
knockout.jsは俺に何も言ってはくれない… 教えてくれ、五飛!
knockout.jsの落とし穴ko.applyBinding(koの作動)で
要素に指定できるViewModelは一つのみ
※applyBindingの第二引数でDOM要素を指定可能※
※applyBindingの第二引数を指定しないとdocument全体が対象となる※
knockout.jsの落とし穴
ko.applyBinding(koの作動)は
指定できるViewModelは一つのみ
ko.applyBindings(new AppTestModel1()); ko.applyBindings(new AppTestModel2());
同要素(document全体)2つはできんのだよ
knockout.jsの落とし穴対応策
knockout.jsの落とし穴対応策
ViewModelを一つのみにする
knockout.jsの落とし穴対応策
ViewModelを一つのみにする
オブジェクトを作り、それにViewModelをつなげる
or
knockout.jsの落とし穴対応策
ViewModelを一つのみにする
オブジェクトを作り、それにViewModelをつなげる
or
orDOM指定で範囲を分ける
さすがにもうこれ以上
ひっかかることは
あっ
※お察し下さい※
knockout.jsまとめ
• JavaScriptコード量が大幅に減る
• HTML(View)との関連づけが簡単
• 動作がすごく軽い
• 大規模になると面倒くさい面が
funcVon draw(){ //draw map //draw window // ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); ctx.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); var buff = 0; if(imageDrag){ buff = xDis / 400 * Math.PI * (-‐1); } for(i = 0; i < IMG_NUM; i++){ renderOrder[i] = i; if((flagInfo[renderOrder[i]].rad + buff) < 0){ flagInfo[renderOrder[i]].rad = flagInfo[renderOrder[i]].rad + Math.PI * circleMode; } else if((flagInfo[renderOrder[i]].rad + buff) > (Math.PI * circleMode)){ flagInfo[renderOrder[i]].rad = flagInfo[renderOrder[i]].rad -‐ Math.PI * circleMode; } } //sort for render for(i = 0; i < IMG_NUM -‐ 1; i++){ for(j = (i + 1); j < IMG_NUM; j++){ if(Math.abs(Math.PI / 2 -‐ (flagInfo[renderOrder[i]].rad + buff)) < Math.abs(Math.PI / 2 -‐ (flagInfo[renderOrder[j]].rad + buff))){ var tmp; tmp = renderOrder[i]; renderOrder[i] = renderOrder[j]; renderOrder[j] = tmp; } } } for(i = 0; i < IMG_NUM; i++){ if( ((flagInfo[renderOrder[i]].rad + buff) > (Math.PI / 12))&& ((flagInfo[renderOrder[i]].rad + buff) < (Math.PI / 12 * 11)) ) { var mag = ZOOM_MIN; var temp = Math.abs((flagInfo[renderOrder[i]].rad + buff) -‐ Math.PI / 2); if(temp < ZOOM_RANGE){ mag = mag +(ZOOM_RANGE -‐ temp / ZOOM_RANGE) * ZOOM_MAX; } ctx.drawImage( drawFlagObj[renderOrder[i]], parseInt(RADIUS_SIDE * Math.cos(flagInfo[renderOrder[i]].rad + buff) + CENTER_X -‐ drawFlagObj[renderOrder[i]].width / 2 / 2 * mag),
parseInt(RADIUS_LENGTH * Math.sin(flagInfo[renderOrder[i]].rad + buff) + CENTER_Y -‐ drawFlagObj[renderOrder[i]].height / 2 / 2 * mag), parseInt(drawFlagObj[renderOrder[i]].width / 2 * mag), parseInt(drawFlagObj[renderOrder[i]].height / 2 * mag) ); } } centerImageNo = flagInfo[renderOrder[IMG_NUM -‐ 1]].imageNo; if( (mapState == NORMAL)&& (imageDrag == false) ) { ctx.drawImage( drawArrowObj[0], parseInt(50), parseInt((SCREEN_HEIGHT / 2) -‐ drawArrowObj[0].height / 2 + 7), parseInt(drawArrowObj[0].width / 2), parseInt(drawArrowObj[0].height / 2) ); } } //main loop funcVon mainLoop(){ var drawInterval = setInterval(funcVon(){ if(mapState == AUTO_MOVE){ for(i = 0; i < IMG_NUM; i++){ flagInfo[i].rad += autoMoveRad; } autoMoveFrame++; if(autoMoveFrame >= AUTO_MOVE_FRAME){ autoMoveFrame = 0; mapState = NORMAL; //change map detail var temp = $(".flagTitle"); var temp2 = $("#VtleViewer"); var element = temp[centerImageNo].cloneNode(true); temp2.empty(); temp2.append(element); } } draw(); }, 1000 / FPS); } }).call(this);
こんな長いコード(500行)が
funcVon AppViewModel(){ var self = this; self.popupVisible = ko.observable(false); self.viewWindow = ko.observable(0); $(“#statusWIndow”).swipeRight{ viewWindow(viewWindow()++); } $(“#statusWIndow”).swipeLe}{ viewWindow(viewWindow()-‐-‐); } } ko.applyBindings(new AppViewModel());
こうなる!(イメージ)
そう、knockout.jsならね
,.へ ___ ム i 「 ヒ_i〉 ゝ 〈 ト ノ iニ(() i { ____ | ヽ i i /__, , -‐-‐\ i } | i /(●) ( ● ) \ {、 λ ト-┤. / (__人__) \ ,ノ  ̄ ,! i ゝ、_ | ´ ̄` | ,. '´ハ ,! . ヽ、 `` 、,__\ /" \ ヽ/ \ノ ノ ハ ̄r/:::r―-‐-‐―/::7 ノ / ヽ. ヽ::〈; . '::. :' |::/ / ,. " `ー 、 \ヽ::. ;:::|/ r'" / ̄二二二二二二二二二二二二二二二二ヽ | 答 | k n o c k o u t . j s │| \_二二二二二二二二二二二二二二二二ノ
もうこれで使えるはず!
別にアレをknockoutしても構わんだろう
もう何も怖くないし
/ / / / / / / / / / / / / ビュー ,.、 ,.、 / / / / ∠二二、ヽ / / / / / (( ´・ω・`)) / / / ~~ :~~~〈 / / / ノ : _,,..ゝ / / / (,,..,)二i_,∠ / /
ちょっと川の様子を見てくるよ!
fin.
次回に続く!