KO範例8 - if及with的應用
| | 2 | | ![]() |
假想以下情境,ViewModel有個players屬性,為一ko.observableArray,其中的元素為player,具有name及bestRecord兩個屬性,其中bestRecord預期會再包含date及score屬性。
在網頁上,打算用foreach將每個player以<li>方式呈現,
<ul data-bind="foreach: players">
<li>
<span data-bind="text: name"></span>
<span data-bind="visible: bestRecord">
(最佳成績:
<span data-bind="text: bestRecord().score"></span>
於
<span data-bind="text: bestRecord().date"></span>)
</span>
</li>
</ul>
由於player不一定有bestRecord,所以我們利用範例7介紹的visible,在bestRecord有資料時才顯示其date及score。完整程式碼如下:
<!DOCTYPE html>
<html>
<head>
<title>Lab 8 - with應用(問題展示)</title>
<script src="../Scripts/jquery-1.7.2.js"></script>
<script src="../Scripts/knockout-2.1.0.debug.js"></script>
<script>
function player(name) {
var self = this;
self.name = ko.observable(name);
self.bestRecord = ko.observable();
self.setBestRecord = function (d, s) {
self.bestRecord({
date: d, score: s
});
};
}
function MyViewModel() {
var self = this;
self.players = ko.observableArray();
}
$(function () {
var vm = new MyViewModel();
//第一筆資料有最佳成績(含日期及分數)
var p1 = new player("Darkthread");
p1.setBestRecord("2012-06-01", 65535);
vm.players.push(p1);
//第二筆資料無最佳成績
var p2 = new player("Jeffrey");
vm.players.push(p2);
ko.applyBindings(vm);
});
</script>
<style>
span { margin: 5px; }
</style>
</head>
<body>
<ul data-bind="foreach: players">
<li>
<span data-bind="text: name"></span>
<!--
原本想法是沒有bestRecord時隱藏<span>區塊,但是其中的內容
還是會被解析,由於bestRecord()無內容,呼叫.score時會出錯
-->
<span data-bind="visible: bestRecord">
(最佳成績:
<span data-bind="text: bestRecord().score"></span>
於
<span data-bind="text: bestRecord().date"></span>)
</span>
</li>
</ul>
</body>
</html>
但以上程式是有問題的,實測時會產生如下錯誤: 線上展示(注意: 該網頁會引發JavaScript錯誤)
原來,雖然我們用visible控制在bestRecord無值時不要進一步顯示date及score,但是visible只控制HTML Style層次的display: none,KO仍會繼續解析其下內容,形成bestRecord()明明是null,卻試著去取score, date屬性的情況而發生錯誤!
要解決這個問題,我們可以改用 if,if 跟 visible 最大的不同,在於 if 條件式不成立時,KO會略過該HTML容器內子元素的data-bind不處理,便不會產生null還硬要取屬性的錯誤。改用 if 之後程式如下: 線上展示
<ul data-bind="foreach: players">
<li>
<span data-bind="text: name"></span>
<!-- 使用if判斷,有最佳成績時才處理 -->
<span data-bind="if: bestRecord">
(最佳成績:
<span data-bind="text: bestRecord().score"></span>
於
<span data-bind="text: bestRecord().date"></span>)
</span>
</li>
</ul>
除了 if,還有個 with 可實現相似效果,而且更方便的一點是: 當with繫結到指定物件,容器內子元素預設data-bind的對象就會變成該物件。於是先前的text: bestRecord().score,可以簡寫成text: score,更簡潔方便。應用with,程式改寫如下: 線上展示
<ul data-bind="foreach: players">
<li>
<span data-bind="text: name">X</span>
<!--
使用with有if的效果,bestRecord()有內容時,
span的內容才會出現在DOM中,而且Binding Context會
切成bestRecord,所以bestRecord().score直接寫成score即可
-->
<span data-bind="with: bestRecord">
(最佳成績:
<span data-bind="text: score"></span>
於
<span data-bind="text: date"></span>)
</span>
</li>
</ul>
Comments
# by Evo.vivi
Hi 黑大, 以此例而言, 我若想要在firebug console上看到var vm = new MyViewModel();的這個vm物件, 請要要怎麼用?
# by Jeffrey
to Evo.vivi, 有兩個做法,第一種是把vm變成全域變數,例如: 改成window.vm = new MyViewModel();,但要留意全域變數的副作用;第二種做法是利用KO提供的方法,ko.dataFor(element)找到element繫結的ViewModel物件,在本例中ko.dataFor($("ul")[0]).players().length可以得到2。