為了確保Server端及Client端的ViewModel一致,在專案中我使用T4自動產生對應C# Class及JavaScript function,如此可確保ASP.NET端拋出的ViewModel與Client端的ViewModel完全一致,雖然這點Knockout Mapping Plug-In也辦得到,但自己產生的JavaScript ViewModel還可以加上JavaScript Documentation註解,配合Visual Studio強大的JS Intellisense功能,爽度是無法相比滴~

但有個小問題: 除了自動產生的屬性外,常會因開發需要還得額外加入UI控制用途的ko.observable或ko.computed,這部分在開發過程常會機動修改調整,不適合綁進自動產生程序,事後外加是較好抉擇。一開始我想得天真,以為用vm.prototype.anotherProp就可輕鬆搞定,後來卻發現這招處理ko.observable可行,遇到ko.computed時會因無法存取當下Instance而陷入困境。在ko.computed函數中,我們無法透過this取得當時物件,而宣告prototype時物件個體尚不存在,故也不可能當成ko.computed的第二個參數傳入,結果空有ko.computed卻無法依賴其他屬性進行運算。

<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js">
</script>
<meta charset=utf-8 />
<title>KO範例27 - 擴充ViewModel(失敗)</title>
</head>
<body>
  <input data-bind="value: foo" />
  <div>fooPlusX = <span data-bind="text: fooPlusX"></span></div>
  
  <script>
    //透過CodeGen自動產生的ViewModel
    function VMBoo() {
      var self = this;
      self.foo = ko.observable(1);
    }
    
    //事後想為ViewModel多加一個fooPlusX屬性,
    //天真地想透過prototype加掛ko.computed直接搞定
    VMBoo.prototype.fooPlusX = ko.computed(function() {
      try {
        //問題來了,在ko.computed中無從存取當時的instance
        return this.foo() + "X";
      }
      catch (err) {
        return err.message;
      }
    }); //instnace在此時當不存在,也無法當成參數傳給ko.computed
    
    var vm = new VMBoo();
    ko.applyBindings(vm);
  </script>
  
</body>
</html>

在以上的失敗範例中,只會得到fooPlusX = Object [object global] has no method 'foo'的結果。線上展示

最後我找到一個解法,先在ViewModel的建構函式中埋下伏筆:
if (this.init) this.init(self);

而這個init函式可以透過vm.prototype.init = function(self) { … }加以定義,如此在init便能取得self(當時的物件個體)為所欲為,跟在建構式的寫法一致。線上展示

<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js">
</script>
<meta charset=utf-8 />
<title>KO範例27 - 擴充ViewModel(成功)</title>
</head>
<body>
  <input data-bind="value: foo" />
  <div>fooPlusX = <span data-bind="text: fooPlusX"></span></div>
  
  <script>
    //透過CodeGen自動產生的ViewModel
    function VMBoo() {
      var self = this;
      self.foo = ko.observable(1);
      //CodeGen時額外呼叫透過prototype定義的init
      if (this.init) this.init(self);
    }
    //宣告額外的init函式
    VMBoo.prototype.init = function(self) {
      self.fooPlusX = ko.computed(function() {
        return self.foo() + "X";
      });
    };
    var vm = new VMBoo();
    ko.applyBindings(vm);
  </script>
  
</body>
</html>

就醬,又排除掉偉大航道上的一個小障礙囉~

[KO系列]

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

Comments

# by GATTACA

看著TypeScript一個一個的加入C#語言的特性, Server端及Client端語言統一後, "碼農" 就輕鬆多了

Post a comment