利用knockout.js將網頁輸入結果對應到ViewModel,後續常需將其再轉為JavaScript物件或JSON字串抛至後端。由於ViewModel主要使用ko.observable()及ko.observableArray(),以JavaScript物件觀點為方法函數(function)而非一般保存資料的屬性,進行JSON轉換時內容不會被保留。例如:

function MyViewModel() {
    var self = this;
    self.boo = ko.observable("BooValue");
    self.foo = ko.observableArray(["boo", "foo", "bar"]);
    self.bar = ko.computed(function() { return self.boo() + "!"; });
}
var vm = new MyViewModel();
alert(JSON.stringify(vm));

以上程式會得到什麼結果? 答案是"{}",因為boo, foo及bar皆為function,vm被視為沒有任何屬性的JavaScript物件,故使用JSON.stringify轉換無法達到保存資料的目的。

針對此需求,Knockout提供了兩個輔助方法:

  • ko.toJS
    擷取ViewModel中ko.obervable(), ko.obervableArray(), ko.computed()等屬性當下的內容,轉為一般的JavaScript字串、數字屬性。
  • ko.toJSON
    等同先呼叫ko.toJS()後再進行JSON.stringify()取得JSON字串。

在最前面的例子,只須改成alert(ko.toJSON(vm)),便可得到我們想要的結果:

{"boo":"BooValue","foo":["boo","foo","bar"],"bar":"BooValue!"}

ViewModel轉JavaScript物件的工作可交給ko.toJS()/ko.toJSON()搞定,而在Ajax互動過程,將伺服器端送來的JavaScript物件對應到ViewMode則l是另一項重大課題。有兩種做法:

第一種是自己寫程式設定取得JavaScript物件各欄位值寫入ViewModel的observable及observableArray,例如: 
    vm.prop1(jsObj.prop1);
    vm.prop2(jsObj.prop2);

甲於這種做法易衍生大量只有屬性名稱不同的重複程式碼,直覺上該用for迴圈簡化,但某些時候可能需要略過某些屬性不處理、或在設定某些屬性值時加入自訂轉換邏輯,一個可以加入屬性客製化設定的通用函數,應是最理想的解決方法。Knockout Mapping Plug-In(可透過NuGet取得)便是用來解決此問題的利器。

它可支援將JavaScript物件轉成ViewModel,寫法如下:
    vm = ko.mapping.fromJS(jsObj);
此時,jsObj的屬性會一一被轉為ko.observable(),陣列則轉成ko.observableArray();vm建立完成後,如果要依照另一顆JavaScript物件更新其內容的話,可以寫成 :
    ko.mapping.fromJS(jsObj, vm)

如需進階控制轉換細節,可使用ko.mapping.fromJS(jsObj, mappingOptions, target)方式傳入參數,提供ignore(忽略哪些屬性不處理)、include(包含哪些ko.computed或函數)、copy(哪些欄位直接複製即可,不用轉成Observable)等參數,另外亦可指定create及update特定屬性的轉換邏輯,詳細使用說明可參考官方文件

在範例14中,簡單示範了用ko.obesrvable(newValue)及ko.mapping.fromJS()兩種做法改變ViewModel內容。網頁下方則是觀察三種不同取JSON做法的結果: 一如預期,JSON.stringify只能傳回空物件;ko.toJSON()可正確取值,但ViewModel經過ko.mapping.fromJS()後,所加入的__ko__mapping__屬性也會一併被擷取下來;而ko.mapping.toJSON()則能識別控制用途的__ko__mapping__屬性,排除在JSON結果之外。

線上展示

完整程式範例如下:

<!DOCTYPE html>
 
<html>
<head>
    <title>Lab 14 - JavaScript物件與JSON轉換</title>
    <script src="../Scripts/jquery-1.7.2.js"></script>
    <script src="../Scripts/knockout-2.1.0.debug.js"></script>
    <script src="../Scripts/knockout.mapping-latest.debug.js"></script>
    <script>
        function MyViewModel() {
            var self = this;
            self.name = ko.observable("林志玲");
            self.height = ko.observable(174);
            self.weight = ko.observable(52);
        }
 
        var michelle = {
            name: "陳研希", height: 160, weight: 45
        };
        var guilunmei = {
            name: "桂綸鎂", height: 164, weight: 46
        }
 
        $(function () {
            var vm = new MyViewModel();
            //將現有JavaScript物件轉為ViewModel
            $("#b1").click(function () {
                //方法1: 自己寫程式設定各欄位
                vm.name(michelle.name);
                vm.height(michelle.height);
                vm.weight(michelle.weight);
            });
            $("#b2").click(function () {
                //方法2: 透過Knockout Mapping Plug-In(可透過NuGet取得)
                ko.mapping.fromJS(guilunmei, { }, vm);
            });
            ko.applyBindings(vm);
        });
    </script>
</head>
<body>
    <dl>
        <dt>姓名</dt>
        <dd>
            <input type="text" data-bind="value: name" /></dd>
        <dt>身高</dt>
        <dd>
            <input type="text" data-bind="value: height" /></dd>
        <dt>體重</dt>
        <dd>
            <input type="text" data-bind="value: weight" /></dd>
    </dl>
    <div style="margin: 10px;">
        <input type="button" id="b1" value="JS物件轉ViewModel 1" />
        <input type="button" id="b2" value="JS物件轉ViewModel 2" />
    </div>
    <div>JSON.stringify</div>
    <pre data-bind="text: JSON.stringify($root)"></pre>
    <div>ko.toJSON</div>
    <pre data-bind="text: ko.toJSON($root)"></pre>
    <div>ko.mapping.toJSON</div>
    <pre data-bind="text: ko.mapping.toJSON($root)"></pre>
</body>
</html>

[KO系列]

http://www.darkthread.net/kolab/labs/default.aspx?m=post

Comments

Be the first to post a comment

Post a comment