假想以下情境,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>

[KO系列]
http://www.darkthread.net/kolab/labs/default.aspx?m=post

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。

Post a comment