Knockout應用開發指南

第一章:入門

1、Knockout簡介 (Introduction)

Knockout是一個輕量級的UI類庫,通過應用MVVM模式使JavaScript前端UI簡單化。

Knockout有如下4大重要概念:

聲明式綁定 (Declarative Bindings):使用簡明易讀的語法很容易地將模型(model)數據關聯到DOM元素上。

UI界面自動刷新 (Automatic UI Refresh):當您的模型狀態(model state)改變時,您的UI界面將自動更新。

依賴跟蹤 (Dependency Tracking):爲轉變和聯合數據,在你的模型數據之間隱式建立關係。

模板 (Templating):爲您的模型數據快速編寫複雜的可嵌套的UI。

簡稱:KO

官方網站:http://knockoutjs.com

2、入門介紹 (Getting started)

2.1KO工作原理及帶來的好處

Knockout是一個以數據模型(data model)爲基礎的能夠幫助你創建富文本,響應顯示和編輯用戶界面的JavaScript類庫。任何時候如果你的UI需要自動更新(比如:更新依賴於用 戶的行爲或者外部數據源的改變),KO能夠很簡單的幫你實現並且很容易維護。

重要特性:

優雅的依賴追蹤- 不管任何時候你的數據模型更新,都會自動更新相應的內容。

聲明式綁定- 淺顯易懂的方式將你的用戶界面指定部分關聯到你的數據模型上。

靈活全面的模板- 使用嵌套模板可以構建複雜的動態界面。

輕易可擴展- 幾行代碼就可以實現自定義行爲作爲新的聲明式綁定。

額外的好處:

純JavaScript類庫 – 兼容任何服務器端和客戶端技術

可添加到Web程序最上部 – 不需要大的架構改變

簡潔的 – Gzip之前大約25kb

兼容任何主流瀏覽器 (IE 6+、Firefox 2+、Chrome、Safari、其它)

Comprehensive suite of specifications (採用行爲驅動開發) - 意味着在新的瀏覽器和平臺上可以很容易通過驗證。

開發人員如果用過Silverlight或者WPF可能會知道KO是MVVM模式的一個例子。如果熟悉 Ruby on Rails 或其它MVC技術可能會發現它是一個帶有聲明式語法的MVC實時form。換句話說,你可以把KO當成通過編輯JSON數據來製作UI用戶界面的一種方 式… 不管它爲你做什麼

OK, 如何使用它?

簡單來說:聲明你的數據作爲一個JavaScript 模型對象(model object),然後將DOM 元素或者模板(templates)綁定到它上面.

讓我們來看一個例子

想想在一個頁面上,航空旅客可以爲他們的旅行升級高級食物套餐,當他們選擇一個套餐的時候,頁面立即顯示套餐的描述和價格。首先,聲明可用的套餐:

var availableMeals = [
    { mealName: 'Standard', description: 'Dry crusts of bread', extraCost: 0 },
    { mealName: 'Premium', description: 'Fresh bread with cheese', extraCost: 9.95 },
    { mealName: 'Deluxe', description: 'Caviar and vintage Dr Pepper', extraCost: 18.50 }
  ];

如果想讓這3個選項顯示到頁面上,我們可以綁定一個下拉菜單(例如:<select>元素)到這個數據上。例如:

<h3>Meal upgrades</h3>
<p>Make your flight more bearable by selecting a meal to match your social and economic status.</p>
Chosen meal: <select data-bind="options: availableMeals,
                                optionsText: 'mealName'"></select>

啓用Knockout並使你的綁定生效,在availableMeals變量聲明之後添加如下代碼:

var viewModel = {
    /* we'll populate this in a moment */
};

ko.applyBindings(viewModel); // Makes Knockout get to work
// 注意:ko. applyBindings需要在上述HTML之後應用纔有效

你可以在這個系列裏將學習更多的view model 和 MVVM。現在你的頁面將render成如下的樣子:

 

 

響應選擇

下一步,聲明一個簡單的data model來描述旅客已經選擇的套餐,添加一個屬性到當前的view model上:

var viewModel = {
    chosenMeal: ko.observable(availableMeals[0])
};

ko.observable是什麼?它是KO裏的一個基礎概念。UI可以監控(observe)它的值並且迴應它的變化。這裏我們設置 chosenMeal是UI可以監控已經選擇的套餐,並初始化它,使用availableMeal裏的第一個值作爲它的默認值(例 如:Standard)。

讓我們將chosenMeal 關聯到下拉菜單上,僅僅是更新<select>的data-bind屬性,告訴它讓<select>元素的值讀取/寫入chosenMeal這個模型屬性:

Chosen meal: <select data-bind="options: availableMeals,
                                optionsText: 'mealName',
                                value: chosenMeal"></select>

理論上說,我們現在可以讀/寫chosenMeal 屬性了,但是我們不知道它的作用。讓我們來顯示已選擇套餐的描述和價格:

<p>
    You've chosen:
    <b data-bind="text: chosenMeal().description"></b>
    (price: <span data-bind='text: chosenMeal().extraCost'></span>)
</p>

於是,套餐信息和價格,將根據用戶選擇不同的套餐項而自動更新:

 

更多關於observables和dependency tracking的使用

最後一件事:如果能將價格格式化成帶有貨幣符號的就好了,聲明一個JavaScript函數就可以實現了…

function formatPrice(price) {
    return price == 0 ? "Free" : "$" + price.toFixed(2);
}

… 然後更新綁定信息使用這個函數 …

(price: <span data-bind='text: formatPrice(chosenMeal().extraCost)'></span>)

… 界面顯示結果將變得好看多了:

 

Price的格式化展示了,你可以在你的綁定裏寫任何JavaScript代碼,KO仍然能探測到你的綁定依賴代碼。這就展示了當你的model改變時,KO如何只進行局部更新而不用重新render整個頁面 – 僅僅是有依賴值改變的那部分。

 

鏈式的observables也是支持的(例如:總價依賴於價格和數量)。當鏈改變的時候,依賴的下游部分將會重新執行,同時所有相關的UI將自動更新。不需要在各個observables之間聲明關聯關係,KO框架會在運行時自動執行的。

你可以從 observables 和 observable arrays 獲取更多信息。上面的例子非常簡單,沒有覆蓋很多KO的功能。你可以獲取更多的內嵌的綁定和模板綁定。

 

KO和jQuery (或Prototype等)是競爭關係還是能一起使用?

所有人都喜歡jQuery! 它是一個在頁面裏操作元素和事件的框架,非常出色並且易使用,在DOM操作上肯定使用jQuery,KO解決不同的問題。

如果頁面要求複雜,僅僅使用jQuery需要花費更多的代碼。 例如:一個表格裏顯示一個列表,然後統計列表的數量,Add按鈕在數據行TR小於5調的時候啓用,否則就禁用。jQuery 沒有基本的數據模型的概念,所以需要獲取數據的數量(從table/div或者專門定義的CSS class),如果需要在某些SPAN裏顯示數據的數量,當添加新數據的時候,你還要記得更新這個SPAN的text。當然,你還要判斷當總 數>=5條的時候禁用Add按鈕。 然後,如果還要實現Delete功能的時候,你不得不指出哪一個DOM元素被點擊以後需要改變。

 

Knockout的實現有何不同?

使用KO非常簡單。將你的數據描繪成一個JavaScript數組對象myItems,然後使用模板(template)轉化這個數組到表格裏(或 者一組DIV)。不管什麼時候數組改變, UI界面也會響應改變(不用指出如何插入新行<tr>或在哪裏插入),剩餘的工作就是同步了。例如:你可以聲明綁定如下一個SPAN顯示數據 數量(可以放在頁面的任何地方,不一定非要在template裏):

There are <span data-bind="text: myItems().count"></span> items

就是這些!你不需要寫代碼去更新它,它的更新依賴於數組myItems的改變。同樣, Add按鈕的啓用和禁用依賴於數組myItems的長度,如下:

<button data-bind="enable: myItems().count < 5">Add</button>

之後,如果你要實現Delete功能,不必指出如何操作UI元素,只需要修改數據模型就可以了。

 

總結:KO沒有和jQuery或類似的DOM 操作API對抗競爭。KO提供了一個關聯數據模型和用戶界面的高級功能。KO本身不依賴jQuery,但是你可以一起同時使用jQuery, 生動平緩的UI改變需要真正使用jQuery。

2.2下載安裝

Knockout的核心類庫是純JavaScript代碼,不依賴任何第三方的類庫。所以按照如下步驟即可添加KO到你的項目裏:

下載Knockout類庫的最新版本,在正式開發和產品使用中,請使用默認的壓縮版本(knockout-x.x.js)。
下載地址:https://github.com/SteveSanderson/knockout/downloads

Debug調試目的,可使用非壓縮版本(knockout-x.x.debug.js). 和壓縮版本同樣的功能,但是具有全變量名和註釋的可讀性源代碼,並且沒有隱藏內部的API。

在你的HTML頁面裏使用<script>標籤引用Knockout類庫文件。

這就是你需要的一切…

開啓模板綁定

…除非你想使用模板綁定功能(您很有可能使用它,因爲非常有用),那你需要再引用兩個JavaScript文件。 KO1.3版的默認模板引擎是依賴jQuery 的jquery.tmpl.js(最新版2.0版已經不依賴jquery tmp了)。所以你需要下載下面的2個文件並在引用KO之前引用:

jQuery 1.4.2 或更高版本

jquery-tmpl.js — 此版本 可以很容易使用,或者你訪問官方網站 查找最新版本。

正確的引用順序:

<script type='text/javascript' src='jquery-1.4.2.min.js'></script>
<script type='text/javascript' src='jquery-tmpl.js'></script>
<script type='text/javascript' src='knockout-1.2.1.js'></script>

(當然,您要根據你的文件路徑累更新上面的文件路徑和文件名。)

Knockout應用開發指南 第二章:監控屬性(Observables)

關於Knockout的3個重要概念(Observables,DependentObservables,ObservableArray),本人無法準確表達它的準確含義,所以暫定翻譯爲(監控屬性、依賴監控屬性和監控數組),如果有好的建議請指正,多謝。

1、創建帶有監控屬性的view model

Observables

Knockout是在下面三個核心功能是建立起來的:

監控屬性(Observables)和依賴跟蹤(Dependency tracking)

聲明式綁定(Declarative bindings)

模板(Templating)

 這一節,你講學到3個功能中的第一個。 在這之前, 我們來解釋一下MVVM模式和view model的概念。

 MVVM and View Models

Model-View-View Model (MVVM) 是一種創建用戶界面的設計模式。 描述的是如何將複雜的UI用戶界面分成3個部分:

 model: 你程序裏存儲的數據。這個數據包括對象和業務操作(例如:銀子賬戶可以完成轉賬功能), 並且獨立於任何UI。使用KO的時候,通常說是向服務器調用Ajax讀寫這個存儲的模型數據。

 view model: 在UI上,純code描述的數據以及操作。例如,如果你實現列表編輯,你的view model應該是一個包含列表項items的對象和暴露的add/remove列表項(item)的操作方法。

    注意這不是UI本身:它不包含任何按鈕的概念或者顯示風格。它也不是持續數據模型 – 包含用戶正在使用的未保存數據。使用KO的時候,你的view models是不包含任何HTML知識的純JavaScript 對象。保持view model抽象可以保持簡單,以便你能管理更復雜的行爲。

 view: 一個可見的,交互式的,表示view model狀態的UI。 從view model顯示數據,發送命令到view model(例如:當用戶click按鈕的時候) ,任何view model狀態改變的時候更新。

使用KO的時候,你的view就是你帶有綁定信息的HTML文檔,這些聲明式的綁定管理到你的view model上。或者你可以使用模板從你的view model獲取數據生成HTML。

創建一個view model,只需要聲明任意的JavaScript object。例如:

var myViewModel = {
    personName: 'Bob',
    personAge: 123
};

你可以爲view model創建一個聲明式綁定的簡單view。例如:下面的代碼顯示personName 值:

The name is <span data-bind="text: personName"></span>

 

Activating Knockout

data-bind屬性儘快好用但它不是HTML的原生屬性(它嚴格遵從HTML5語法, 雖然HTML4驗證器提示有不可識別的屬性但依然可用)。由於瀏覽器不識別它是什麼意思,所以你需要激活Knockout 來讓他起作用。

激活Knockout,需要添加如下的 <script> 代碼塊:

ko.applyBindings(myViewModel);

你可以將這個代碼塊放在HTML底部,或者放在jQuery的$函數或者ready 函數裏,然後放在頁面上面, 最終生成結果就是如下的HTML代碼:

The name is <span>Bob</span>

你可能奇怪ko.applyBindings使用的是什麼樣的參數,

 第一個參數是你想用於聲明式綁定

第二個參數(可選),可以聲明成使用data-bind的HTML元素或者容器。例如, ko.applyBindings(myViewModel, document.getElementById('someElementId'))。它的現在是隻有作爲someElementId 的元素和子元素才能激活KO功能。 好處是你可以在同一個頁面聲明多個view model,用來區分區域。

 

Observables

現在已經知道如何創建一個簡單的view model並且通過binding顯示它的屬性了。但是KO一個重要的功能是當你的view model改變的時候能自動更新你的界面。當你的view model部分改變的時候KO是如何知道的呢?答案是:你需要將你的model屬性聲明成observable的, 因爲它是非常特殊的JavaScript objects,能夠通知訂閱者它的改變以及自動探測到相關的依賴。

 

例如:將上述例子的view model改成如下代碼:

var myViewModel = {
    personName: ko.observable('Bob'),
    personAge: ko.observable(123)
};

你根本不需要修改view – 所有的data-bind語法依然工作,不同的是他能監控到變化,當值改變時,view會自動更新。

 

監控屬性(observables)的讀和寫

不是所有的瀏覽器都支持JavaScript的 getters and setters (比如IE),,所以爲了兼容性,使用ko.observable監控的對象都是真實的function函數。

 讀取監控屬性(observable)的值,只需要直接調用監控屬性(observable)(不需要參數),例如myViewModel.personName() 將返回'Bob', myViewModel.personAge() 將返回 123。

寫一個新值到監控屬性(observable)上,調用這個observable屬性並當新值作爲參數。例如:調用 myViewModel.personName('Mary') 將更新name值爲'Mary'。

給一個model對象的多個屬性寫入新值,你可以使用鏈式語法。例如: myViewModel.personName('Mary').personAge(50) 將會將name更新爲 'Mary' 並且 將age更新爲 50.

監控屬性(observables)的特徵就是監控(observed),例如其它代碼可以說我需要得到對象變化的通知,所以KO內部有很多內置的 綁定語法。所以如果你的代碼寫成data-bind="text: personName", text綁定註冊到自身,一旦personName的值改變,它就能得到通知。

當然調用myViewModel.personName('Mary')改變name的值,text綁定將自動更新這個新值到相應的DOM元素上。這就是如何將view model的改變傳播到view上的。

 

監控屬性(Observables)的顯式訂閱

通常情況下,你不用手工訂閱,所以新手可以忽略此小節。高級用戶,如果你要註冊自己的訂閱到監控屬性(observables),你可以調用它的subscribe 函數。例如:

myViewModel.personName.subscribe(function (newValue) {
    alert("The person's new name is " + newValue);
});

這個subscribe 函數在內部很多地方都用到的。你也可以終止自己的訂閱:首先得到你的訂閱,然後調用這個對象的dispose函數,例如:

var subscription = myViewModel.personName.subscribe(function (newValue) { /* do stuff */ });
// ...then later...
subscription.dispose(); // I no longer want notifications

大多數情況下,你不需要做這些,因爲內置的綁定和模板系統已經幫你做好很多事情了,可以直接使用它們。

 

2、使用依賴監控屬性(Dependent Observables)

如果你已經有了監控屬性firstName和lastName,你想顯示全稱怎麼辦? 這就需要用到依賴監控屬性了 – 這些函數是一個或多個監控屬性, 如果他們的依賴對象改變,他們會自動跟着改變。

例如,下面的view model,

var viewModel = {
    firstName: ko.observable('Bob'),
    lastName: ko.observable('Smith')
};

… 你可以添加一個依賴監控屬性來返回姓名全稱:

viewModel.fullName = ko.dependentObservable(function () {
    return this.firstName() + " " + this.lastName();
}, viewModel);

並且綁定到UI的元素上,例如:

The name is <span data-bind="text: fullName"></span>

… 不管firstName還是lastName改變,全稱fullName都會自動更新(不管誰改變,執行函數都會調用一次,不管改變成什麼,他的值都會更新到UI或者其他依賴監控屬性上)。

 

管理‘this’

新手可忽略此小節,你只需要安裝上面例子中的代碼模式寫就行了,無需知道/關注這個this。

你可能疑惑ko.dependentObservable的第二個參數是做什麼用的(上面的例子中我傳的是viewModel), 它是聲明執行依賴監控屬性的this用的。 沒有它,你不能引用到this.firstName() 和this.lastName()。 老練的JavaScript 開發人員不覺得this怎麼樣,但是如果你不熟悉JavaScript,那就對它就會很陌生。(C#和Java需要不需要爲set一個值爲設置this, 但是JavaScript 需要,因爲默認情況下他們的函數自身不是任何對象的一部分)。

 

不幸的是, JavaScript 對象沒有任何辦法能引用他們自身,所以你需要通過myViewModelObject.myDependentObservable = ... 的形式添加依賴監控屬性到view model對象上。 你不能直接在view model裏聲明他們,換句話說,你不能寫成下面這樣:

var viewModel = {
    myDependentObservable: ko.dependentObservable(function() {
        ...
    }, /* can't refer to viewModel from here, so this doesn't work */)
}

… 相反你必須寫成如下這樣:

var viewModel = {
    // Add other properties here as you wish
};

viewModel.myDependentObservable = ko.dependentObservable(function() {
    ...
}, viewModel); // This is OK

只要你知道期望什麼,它確實不是個問題。J

 

依賴鏈

理所當然,如果你想你可以創建一個依賴監控屬性的鏈。例如:

監控屬性items表述一組列表項

監控屬性selectedIndexes保存着被用戶選上的列表項的索引

依賴監控屬性selectedItems 返回的是selectedIndexes 對應的列表項數組

另一個依賴監控屬性返回的true或false依賴於 selectedItems 的各個列表項是否包含一些屬性(例如,是否新的或者還未保存的)。一些UI element(像按鈕的啓用/禁用)的狀態取決於這個值)。

 然後,items或者selectedIndexes 的改變將會影響到所有依賴監控屬性的鏈,所有綁定這些屬性的UI元素都會自動更新。多麼整齊與優雅!

 

可寫的依賴監控屬性

新手可忽略此小節,可寫依賴監控屬性真的是太advanced了,而且大部分情況下都用不到。

正如所學到的,依賴監控屬性是通過計算其它的監控屬性而得到的。感覺是依賴監控屬性正常情況下應該是隻讀的。那麼,有可能讓依賴監控屬性支持可寫麼?你只需要聲明自己的callback函數然後利用寫入的值再處理一下相應的邏輯即可。

你可以像使用普通的監控屬性一樣使用依賴監控屬性 – 數據雙向綁定到DOM元素上,並且通過自定義的邏輯攔截所有的讀和寫操作。這是非常牛逼的特性並且可以在大範圍內使用。

 

例1:分解用戶的輸入

返回到經典的“first name + last name = full name” 例子上,你可以讓事情調回來看: 讓依賴監控屬性fullName可寫,讓用戶直接輸入姓名全稱,然後輸入的值將被解析並映射寫入到基本的監控屬性firstName和lastName 上:

var viewModel = {
    firstName: ko.observable("Planet"),
    lastName: ko.observable("Earth")
};

viewModel.fullName = ko.dependentObservable({

    read: function () {
        return this.firstName() + " " + this.lastName();
    },

    write: function (value) {
        var lastSpacePos = value.lastIndexOf(" ");
        if (lastSpacePos > 0) { // Ignore values with no space character
            this.firstName(value.substring(0, lastSpacePos)); // Update "firstName"
            this.lastName(value.substring(lastSpacePos + 1)); // Update "lastName"
        }
    },
    owner: viewModel
});

這個例子裏,寫操作的callback接受寫入的值,把值分離出來,分別寫入到“firstName”和“lastName”上。 你可以像普通情況一樣將這個view model綁定到DOM元素上,如下:

<p>First name: <span data-bind="text: firstName"></span></p>
<p>Last name: <span data-bind="text: lastName"></span></p>
<h2>Hello, <input data-bind="value: fullName"/>!</h2>

這是一個Hello World 例子的反例子,姓和名都不可編輯,相反姓和名組成的姓名全稱卻是可編輯的。

上面的view model演示的是通過一個簡單的參數來初始化依賴監控屬性。你可以給下面的屬性傳入任何JavaScript對象:

   read — 必選,一個用來執行取得依賴監控屬性當前值的函數。

   write — 可選,如果聲明將使你的依賴監控屬性可寫,別的代碼如果這個可寫功能寫入新值,通過自定義邏輯將值再寫入各個基礎的監控屬性上。

   owner — 可選,如果聲明,它就是KO調用read或write的callback時用到的this。查看“管理this”獲取更新信息。

 例2:Value轉換器

 有時候你可能需要顯示一些不同格式的數據,從基礎的數據轉化成顯示格式。比如,你存儲價格爲float類型,但是允許用戶編輯的字段需要支持貨幣單位和小數點。你可以用可寫的依賴監控屬性來實現,然後解析傳入的數據到基本 float類型裏:

viewModel.formattedPrice = ko.dependentObservable({

    read: function () {
        return "$" + this.price().toFixed(2);
    },

    write: function (value) {
        // Strip out unwanted characters, parse as float, then write the raw data back to the underlying "price" observable
        value = parseFloat(value.replace(/[^\.\d]/g, ""));
        this.price(isNaN(value) ? 0 : value); // Write to underlying storage
    },
    owner: viewModel
});

然後我們綁定formattedPrice到text box上:

<p>Enter bid price: <input data-bind="value: formattedPrice"/></p>

所以,不管用戶什麼時候輸入新價格,輸入什麼格式,text box裏會自動更新爲帶有2位小數點和貨幣符號的數值。這樣用戶可以看到你的程序有多聰明,來告訴用戶只能輸入2位小數,否則的話自動刪除多餘的位數,當 然也不能輸入負數,因爲write的callback函數會自動刪除負號。

 

例3:過濾並驗證用戶輸入

例1展示的是寫操作過濾的功能,如果你寫的值不符合條件的話將不會被寫入,忽略所有不包括空格的值。

再多走一步,你可以聲明一個監控屬性isValid 來表示最後一次寫入是否合法,然後根據真假值顯示相應的提示信息。稍後仔細介紹,先參考如下代碼:

var viewModel = {
    acceptedNumericValue: ko.observable(123),
    lastInputWasValid: ko.observable(true)
};

viewModel.attemptedValue = ko.dependentObservable({
    read: viewModel.acceptedNumericValue,
    write: function (value) {
        if (isNaN(value))
            this.lastInputWasValid(false);
        else {
            this.lastInputWasValid(true);
            this.acceptedNumericValue(value); // Write to underlying storage
        }
    },
    owner: viewModel
});

… 按照如下格式聲明綁定元素:

<p>Enter a numeric value: <input data-bind="value: attemptedValue"/></p>
<div data-bind="visible: !lastInputWasValid()">That's not a number!</div>

現在,acceptedNumericValue 將只接受數字,其它任何輸入的值都會觸發顯示驗證信息,而會更新acceptedNumericValue。

備註:上面的例子顯得殺傷力太強了,更簡單的方式是在<input>上使用jQuery Validation和number class。Knockout可以和jQuery Validation一起很好的使用,參考例子:grid editor 。當然,上面的例子依然展示了一個如何使用自定義邏輯進行過濾和驗證數據,如果驗證很複雜而jQuery Validation很難使用的話,你就可以用它。

 

依賴跟蹤如何工作的

新手沒必要知道太清楚,但是高級開發人員可以需要知道爲什麼依賴監控屬性能夠自動跟蹤並且自動更新UI…

事實上,非常簡單,甚至說可愛。跟蹤的邏輯是這樣的:

當你聲明一個依賴監控屬性的時候,KO會立即調用執行函數並且獲取初始化值。

當你的執行函數運行的時候,KO會把所有需要依賴的依賴屬性(或者監控依賴屬性)都記錄到一個Log列表裏。

執行函數結束以後,KO會向所有Log裏需要依賴到的對象進行訂閱。訂閱的callback函數是重新運行你的執行函數。然後回頭重新執行上面的第一步操作(並且註銷不再使用的訂閱)。

最後KO會通知上游所有訂閱它的訂閱者,告訴它們我已經設置了新值。

所有說,KO不僅僅是在第一次執行函數執行時候探測你的依賴項,每次它都會探測。舉例來說,你的依賴屬性可以是動態的:依賴屬性A代表你是否依賴於 依賴屬性B或者C,這時候只有當A或者你當前的選擇B或者C改變的時候執行函數才重新執行。你不需要再聲明其它的依賴:運行時會自動探測到的。

另外一個技巧是:一個模板輸出的綁定是依賴監控屬性的簡單實現,如果模板讀取一個監控屬性的值,那模板綁定就會自動變成依賴監控屬性依賴於那個監控 屬性,監控屬性一旦改變,模板綁定的依賴監控屬性就會自動執行。嵌套的模板也是自動的:如果模板X render模板 Y,並且Y需要顯示監控屬性Z的值,當Z改變的時候,由於只有Y依賴它,所以只有Y這部分進行了重新繪製(render)。

3、使用observable數組

如果你要探測和響應一個對象的變化,你應該用observables。如果你需要探測和響應一個集合對象的變化,你應該用observableArray 。在很多場景下,它都非常有用,比如你要在UI上需要顯示/編輯的一個列表數據集合,然後對集合進行添加和刪除。

var myObservableArray = ko.observableArray();    // Initially an empty array
myObservableArray.push('Some value');            // Adds the value and notifies observers

 

關鍵點:監控數組跟蹤的是數組裏的對象,而不是這些對象自身的狀態。

簡單說,將一對象放在observableArray 裏不會使這個對象本身的屬性變化可監控的。當然你自己也可以聲明這個對象的屬性爲observable的,但它就成了一個依賴監控對象了。一個 observableArray 僅僅監控他擁有的對象,並在這些對象添加或者刪除的時候發出通知。

 

預加載一個監控數組observableArray

如果你想讓你的監控數組在開始的時候就有一些初始值,那麼在聲明的時候,你可以在構造器里加入這些初始對象。例如:

// This observable array initially contains three objects
var anotherObservableArray = ko.observableArray([
    { name: "Bungle", type: "Bear" },
    { name: "George", type: "Hippo" },
    { name: "Zippy", type: "Unknown" }
]);

 

從observableArray裏讀取信息

一個observableArray其實就是一個observable的監控對象,只不過他的值是一個數組(observableArray還加了 很多其他特性,稍後介紹)。所以你可以像獲取普通的observable的值一樣,只需要調用無參函數就可以獲取自身的值了。 例如,你可以像下面這樣獲取它的值:

alert('The length of the array is ' + myObservableArray().length);
alert('The first element is ' + myObservableArray()[0]);

理論上你可以使用任何原生的JavaScript數組函數來操作這些數組,但是KO提供了更好的功能等價函數,他們非常有用是因爲:

兼容所有瀏覽器。(例如indexOf不能在IE8和早期版本上使用,但KO自己的indexOf 可以在所有瀏覽器上使用)

在數組操作函數方面(例如push和splice),KO自己的方式可以自動觸發依賴跟蹤,並且通知所有的訂閱者它的變化,然後讓UI界面也相應的自動更新。

語法更方便,調用KO的push方法,只需要這樣寫:myObservableArray.push(...)。 比如原生數組的myObservableArray().push(...)好用多了。

 

下面講解的均是observableArray的讀取和寫入的相關函數。

indexOf

indexOf 函數返回的是第一個等於你參數數組項的索引。例如:myObservableArray.indexOf('Blah')將返回以0爲第一個索引的第一個等於Blah的數組項的索引。如果沒有找到相等的,將返回-1。

slice

slice函數是observableArray相對於JavaScript 原生函數slice的等價函數(返回給定的從開始索引到結束索引之間所有的對象集合)。 調用myObservableArray.slice(...)等價於調用JavaScript原生函數(例 如:myObservableArray().slice(...))。

 

操作observableArray

observableArray 展現的是數組對象相似的函數並通知訂閱者的功能。

pop, push, shift, unshift, reverse, sort, splice

所有這些函數都是和JavaScript數組原生函數等價的,唯一不同的數組改變可以通知訂閱者:

    myObservableArray.push('Some new value') 在數組末尾添加一個新項

    myObservableArray.pop() 刪除數組最後一個項並返回該項

    myObservableArray.unshift('Some new value') 在數組頭部添加一個項

    myObservableArray.shift() 刪除數組頭部第一項並返回該項

    myObservableArray.reverse() 翻轉整個數組的順序

    myObservableArray.sort() 給數組排序

        默認情況下,是按照字符排序(如果是字符)或者數字排序(如果是數字)。

        你可以排序傳入一個排序函數進行排序,該排序函數需要接受2個參數(代表該數組裏需要比較的項),如果第一個項小於第二個項,返回-1,大於則返回1,等 於返回0。例如:用lastname給person排序,你可以這樣寫:myObservableArray.sort (function (left, right) {return left.lastName == right.lastName? 0: (left.lastName < right.lastName? -1: 1) })

     myObservableArray.splice() 刪除指定開始索引和指定數目的數組對象元素。例如myObservableArray.splice(1, 3) 從索引1開始刪除3個元素(第2,3,4個元素)然後將這些元素作爲一個數組對象返回。

更多observableArray 函數的信息,請參考等價的JavaScript數組標準函數

 

remove和removeAll

observableArray 添加了一些JavaScript數組默認沒有但非常有用的函數:

    myObservableArray.remove(someItem) 刪除所有等於someItem的元素並將被刪除元素作爲一個數組返回

    myObservableArray.remove(function(item) { return item.age < 18 }) 刪除所有age屬性小於18的元素並將被刪除元素作爲一個數組返回

    myObservableArray.removeAll(['Chad', 132, undefined]) 刪除所有等於'Chad', 123, or undefined的元素並將被刪除元素作爲一個數組返回

 

destroy和destroyAll(注:通常只和和Ruby on Rails開發者有關)

destroy和destroyAll函數是爲Ruby on Rails開發者方便使用爲開發的:

    myObservableArray.destroy(someItem) 找出所有等於someItem的元素並給他們添加一個屬性_destroy,並賦值爲true

    myObservableArray.destroy(function(someItem) { return someItem.age < 18 }) 找出所有age屬性小於18的元素並給他們添加一個屬性_destroy,並賦值爲true

    myObservableArray.destroyAll(['Chad', 132, undefined]) 找出所有等於'Chad', 123, 或undefined 的元素並給他們添加一個屬性_destroy,並賦值爲true

 

那麼,_destroy是做什麼用的?正如我提到的,這只是爲Rails 開發者準備的。在Rails 開發過程中,如果你傳入一個JSON對象,Rails 框架會自動轉換成ActiveRecord對象並且保存到數據庫。Rails 框架知道哪些對象以及在數據庫中存在,哪些需要添加或更新, 標記_destroy爲true就是告訴框架刪除這條記錄。

 

注意的是:在KO render一個foreach模板的時候,會自動隱藏帶有_destroy屬性並且值爲true的元素。所以如果你的“delete”按鈕調用 destroy(someItem) 方法的話,UI界面上的相對應的元素將自動隱藏,然後等你提交這個JSON對象到Rails上的時候,這個元素項將從數據庫刪除(同時其它的元素項將正常 的插入或者更新)。

Knockout應用開發指南 第三章:綁定語法(1)

2011-11-23 08:23 by 湯姆大叔, 20105 閱讀, 7 評論, 收藏, 編輯

第三章所有代碼都需要啓用KO的ko.applyBindings(viewModel);功能,才能使代碼生效,爲了節約篇幅,所有例子均省略了此行代碼。

1   visible 綁定

目的

visible綁定到DOM元素上,使得該元素的hidden或visible狀態取決於綁定的值。

 

例子

<div data-bind="visible: shouldShowMessage">
    You will see this message only when "shouldShowMessage" holds a true value.
</div>

<script type="text/javascript">
    var viewModel = {
        shouldShowMessage: ko.observable(true) // Message initially visible
    };
    viewModel.shouldShowMessage(false); // ... now it's hidden
    viewModel.shouldShowMessage(true); // ... now it's visible again
</script>

 

參數

    主參數

        當參數設置爲一個假值時(例如:布爾值false, 數字值0, 或者null, 或者undefined) ,該綁定將設置該元素的style.display值爲none,讓元素隱藏。它的優先級高於你在CSS裏定義的任何display樣式。

        當參數設置爲一個真值時(例如:布爾值true,或者非空non-null的對象或者數組) ,該綁定會刪除該元素的style.display值,讓元素可見。然後你在CSS裏自定義的display樣式將會自動生效。

        如果參數是監控屬性observable的,那元素的visible狀態將根據參數值的變化而變化,如果不是,那元素的visible狀態將只設置一次並且以後不在更新。

    其它參數

        無

 

注:使用函數或者表達式來控制元素的可見性

你也可以使用JavaScript函數或者表達式作爲參數。這樣的話,函數或者表達式的結果將決定是否顯示/隱藏這個元素。例如:

<div data-bind="visible: myValues().length > 0">
    You will see this message only when 'myValues' has at least one member.
</div>

<script type="text/javascript">
    var viewModel = {
        myValues: ko.observableArray([]) // Initially empty, so message hidden
    };
    viewModel.myValues.push("some value"); // Now visible
</script>

 

依賴性

除KO核心類庫外,無依賴。

 

2   text 綁定

目的

text 綁定到DOM元素上,使得該元素顯示的文本值爲你綁定的參數。該綁定在顯示<span>或者<em>上非常有用,但是你可以用在任何元素上。

 

例子

Today's message is: <span data-bind="text: myMessage"></span>

<script type="text/javascript">
    var viewModel = {
        myMessage: ko.observable() // Initially blank
    };
    viewModel.myMessage("Hello, world!"); // Text appears
</script>

 

參數

    主參數

    KO將參數值會設置在元素的innerText (IE)或textContent(Firefox和其它相似瀏覽器)屬性上。原來的文本將會被覆蓋。

    如果參數是監控屬性observable的,那元素的text文本將根據參數值的變化而更新,如果不是,那元素的text文本將只設置一次並且以後不在更新。

    如果你傳的是不是數字或者字符串(例如一個對象或者數組),那顯示的文本將是yourParameter.toString()的等價內容。

    其它參數

        無

 

1:使用函數或者表達式來決定text

如果你想讓你的text更可控,那選擇是創建一個依賴監控屬性(dependent observable),然後在它的執行函數裏編碼,決定應該顯示什麼樣的text文本。

例如:

The item is <span data-bind="text: priceRating"></span> today.


<script type="text/javascript">
    var viewModel = {
        price: ko.observable(24.95)
    };

    viewModel.priceRating = ko.dependentObservable(function () {
        returnthis.price() >50?"expensive" : "affordable";
    }, viewModel);
</script>

現在,text文本將在“expensive”和“affordable”之間替換,取決於價格怎麼改變。

然而,如果有類似需求的話其實沒有必要再聲明一個依賴監控屬性(dependent observable), 你只需要按照如下代碼寫JavaScript表達式就可以了:

The item is <span data-bind="text: price() > 50 ? 'expensive' : 'affordable'"></span> today.

結果是一樣的,但我們不需要再聲明依賴監控屬性(dependent observable)。

 

2:關於HTML encoding

因爲該綁定是設置元素的innerText或textContent (而不是innerHTML),所以它是安全的,沒有HTML或者腳本注入的風險。例如:如果你編寫如下代碼:

viewModel.myMessage("<i>Hello, world!</i>");

… 它不會顯示斜體字,而是原樣輸出標籤。如果你需要顯示HTML內容,請參考html綁定.

 

3:關於IE 6的白空格whitespace

IE6有個奇怪的問題,如果 span裏有空格的話,它將自動變成一個空的span。如果你想編寫如下的代碼的話,那Knockout將不起任何作用:

Welcome, <span data-bind="text: userName"></span> to our web site.

… IE6 將不會顯示span中間的那個空格,你可以通過下面這樣的代碼避免這個問題:

Welcome, <span data-bind="text: userName">&nbsp;</span> to our web site.

IE6以後版本和其它瀏覽器都沒有這個問題

 

依賴性

除KO核心類庫外,無依賴。

 

3   html 綁定

目的

html綁定到DOM元素上,使得該元素顯示的HTML值爲你綁定的參數。如果在你的view model裏聲明HTML標記並且render的話,那非常有用。

 

例子

<div data-bind="html: details"></div>

<script type="text/javascript">
    var viewModel = {
        details: ko.observable() // Initially blank
    };

    viewModel.details("<em>For further details, view the report <a href='report.html'>here</a>.</em>");
    // HTML content appears
</script> 

 

參數

    主參數

    KO設置該參數值到元素的innerHTML屬性上,元素之前的內容將被覆蓋。

    如果參數是監控屬性observable的,那元素的內容將根據參數值的變化而更新,如果不是,那元素的內容將只設置一次並且以後不在更新。

    如果你傳的是不是數字或者字符串(例如一個對象或者數組),那顯示的文本將是yourParameter.toString()的等價內容。

    其它參數

        無

 

注:關於HTML encoding

因爲該綁定設置元素的innerHTML,你應該注意不要使用不安全的HTML代碼,因爲有可能引起腳本注入***。如果你不確信是否安全(比如顯示 用戶輸入的內容),那你應該使用text綁定,因爲這個綁定只是設置元素的text 值innerText和textContent。

 

依賴性

除KO核心類庫外,無依賴。

 

4   css 綁定

目的

css綁定是添加或刪除一個或多個CSS class到DOM元素上。 非常有用,比如當數字變成負數時高亮顯示。(注:如果你不想應用CSS class而是想引用style屬性的話,請參考style綁定。)

 

例子

<div data-bind="css: { profitWarning: currentProfit() < 0 }">
   Profit Information
</div>
 

<script type="text/javascript">
    var viewModel = {
        currentProfit: ko.observable(150000)
        // Positive value, so initially we don't apply the "profitWarning" class
    };

    viewModel.currentProfit(-50);
    // Causes the "profitWarning" class to be applied
</script>

效果就是當currentProfit 小於0的時候,添加profitWarning CSS class到元素上,如果大於0則刪除這個CSS class。

 

參數

    主參數

    該參數是一個JavaScript對象,屬性是你的CSS class名稱,值是比較用的true或false,用來決定是否應該使用這個CSS class。

    你可以一次設置多個CSS class。例如,如果你的view model有一個叫isServre的屬性,

<div data-bind="css: { profitWarning: currentProfit() < 0, majorHighlight: isSevere }">

    非布爾值會被解析成布爾值。例如, 0和null被解析成false,21和非null對象被解析成true。

    如果參數是監控屬性observable的,那隨着值的變化將會自動添加或者刪除該元素上的CSS class。如果不是,那CSS class將會只添加或者刪除一次並且以後不在更新。

    你可以使用任何JavaScript表達式或函數作爲參數。KO將用它的執行結果來決定是否應用或刪除CSS class。

    其它參數

        無

 

注:應用的CSS class的名字不是合法的JavaScript變量命名

如果你想使用my-class class,你不能寫成這樣:

<div data-bind="css: { my-class: someValue }">...</div>

… 因爲my-class不是一個合法的命名。解決方案是:在my-class兩邊加引號作爲一個字符串使用。這是一個合法的JavaScript 對象 文字(從JSON技術規格說明來說,你任何時候都應該這樣使用,雖然不是必須的)。例如,

<div data-bind="css: { 'my-class': someValue }">...</div>

 

依賴性

除KO核心類庫外,無依賴。

 

5   style 綁定

目的

style綁定是添加或刪除一個或多個DOM元素上的style值。比如當數字變成負數時高亮顯示,或者根據數字顯示對應寬度的Bar。(注:如果你不是應用style值而是應用CSS class的話,請參考CSS綁定。)

 

例子

<div data-bind="style: { color: currentProfit() < 0 ? 'red' : 'black' }">
   Profit Information
</div>


<script type="text/javascript">
    var viewModel = {
        currentProfit: ko.observable(150000) // Positive value, so initially black
    };
    viewModel.currentProfit(-50); // Causes the DIV's contents to go red
</script>

當currentProfit 小於0的時候div的style.color是紅色,大於的話是黑色。

 

參數

    主參數

    該參數是一個JavaScript對象,屬性是你的style的名稱,值是該style需要應用的值。

    你可以一次設置多個style值。例如,如果你的view model有一個叫isServre的屬性,

<div data-bind="style: { color: currentProfit() < 0 ? 'red' : 'black', fontWeight: isSevere() ? 'bold' : '' }">...</div>

    如果參數是監控屬性observable的,那隨着值的變化將會自動添加或者刪除該元素上的style值。如果不是,那style值將會只應用一次並且以後不在更新。

    你可以使用任何JavaScript表達式或函數作爲參數。KO將用它的執行結果來決定是否應用或刪除style值。

    其它參數

        無

 

注:應用的style的名字不是合法的JavaScript變量命名

如果你需要應用font-weight或者text-decoration,你不能直接使用,而是要使用style對應的JavaScript名稱。

    錯誤: { font-weight: someValue };            正確: { fontWeight: someValue }

    錯誤: { text-decoration: someValue };      正確: { textDecoration: someValue }

參考:style名稱和對應的JavaScript 名稱列表

 

依賴性

除KO核心類庫外,無依賴。

 

6   attr 綁定

目的

attr 綁定提供了一種方式可以設置DOM元素的任何屬性值。你可以設置img的src屬性,連接的href屬性。使用綁定,當模型屬性改變的時候,它會自動更新。

 

例子

<a data-bind="attr: { href: url, title: details }">
    Report
</a>

<script type="text/javascript">
    var viewModel = {
        url: ko.observable("year-end.html"),
        details: ko.observable("Report including final year-end statistics")
    };
</script>

呈現結果是該連接的href屬性被設置爲year-end.html, title屬性被設置爲Report including final year-end statistics。

 

參數

    主參數

    該參數是一個JavaScript對象,屬性是你的attribute名稱,值是該attribute需要應用的值。

    如果參數是監控屬性observable的,那隨着值的變化將會自動添加或者刪除該元素上的attribute值。如果不是,那attribute值將會只應用一次並且以後不在更新。

    其它參數

        無

 

注:應用的屬性名字不是合法的JavaScript變量命名

如果你要用的屬性名稱是data-something的話,你不能這樣寫:

<div data-bind="attr: { data-something: someValue }">...</div>

… 因爲data-something 不是一個合法的命名。解決方案是:在data-something兩邊加引號作爲一個字符串使用。這是一個合法的JavaScript 對象 文字(從JSON技術規格說明來說,你任何時候都應該這樣使用,雖然不是必須的)。例如,

<div data-bind="attr: { ‘data-something’: someValue }">...</div>

 

依賴性

除KO核心類庫外,無依賴。

Knockout應用開發指南 第三章:綁定語法(2)

2011-11-24 09:00 by 湯姆大叔, 16408 閱讀, 22 評論, 收藏, 編輯

7   click 綁定

目的

click綁定在DOM元素上添加事件句柄以便元素被點擊的時候執行定義的JavaScript 函數。大部分是用在button,input和連接a上,但是可以在任意元素上使用。

 

例子

<div>
    You've clicked <span data-bind="text: numberOfClicks"></span> times
    <button data-bind="click: incrementClickCounter">Click me</button>
</div>


<script type="text/javascript">
    var viewModel = {
        numberOfClicks: ko.observable(0),
        incrementClickCounter: function () {
            var previousCount =this.numberOfClicks();
            this.numberOfClicks(previousCount +1);
        }
    };
</script>

每次點擊按鈕的時候,都會調用incrementClickCounter()函數,然後更新自動更新點擊次數。

 

參數

    主參數

    Click點擊事件時所執行的函數。

    你可以聲明任何JavaScript函數 – 不一定非要是view model裏的函數。你可以聲明任意對象上的任何函數,例如: someObject.someFunction。

    View model上的函數在用的時候有一點點特殊,就是不需要引用對象的,直接引用函數本身就行了,比如直接寫incrementClickCounter 就可以了,而無需寫成: viewModel.incrementClickCounter(儘管是合法的)。

    其它參數

        無

 

注1:傳參數給你的click 句柄

最簡單的辦法是傳一個function包裝的匿名函數:

<button data-bind="click: function() { viewModel.myFunction('param1', 'param2') }">
    Click me
</button>

這樣,KO就會調用這個匿名函數,裏面會執行viewModel.myFunction(),並且傳進了'param1' 和'param2'參數。

 

注2:訪問事件源對象

有些情況,你可能需要使用事件源對象,Knockout會將這個對象傳遞到你函數的第一個參數:

<button data-bind="click: myFunction">
    Click me
</button>


 <script type="text/javascript">
     var viewModel = {
         myFunction: function (event) {
             if (event.shiftKey) {
                 //do something different when user has shift key down
             } else {
                 //do normal action
             }
         }
     };
</script>

如果你需要的話,可以使用匿名函數的第一個參數傳進去,然後在裏面調用:

<button data-bind="click: function(event) { viewModel.myFunction(event, 'param1', 'param2') }">
    Click me
</button>

這樣,KO就會將事件源對象傳遞給你的函數並且使用了。

 

注3: 允許執行默認事件

默認情況下,Knockout會阻止冒泡,防止默認的事件繼續執行。例如,如果你點擊一個a連接,在執行完自定義事件時它不會連接到href地址。這特別有用是因爲你的自定義事件主要就是操作你的view model,而不是連接到另外一個頁面。

當然,如果你想讓默認的事件繼續執行,你可以在你click的自定義函數裏返回true。

 

注4:控制this句柄

初學者可以忽略這小節,因爲大部分都用不着,高級用戶可以參考如下內容:

KO在調用你定義的函數時,會將view model傳給this對象(也就是ko.applyBindings使用的view model)。主要是方便你在調用你在view model裏定義的方法的時候可以很容易再調用view model裏定義的其它屬性。例如: this.someOtherViewModelProperty。

如果你想引用其它對象,我們有兩種方式:

你可以和注1裏那樣使用匿名函數,因爲它支持任意JavaScript 對象。

 

你也可以直接引用任何函數對象。你可以使用bind使callback函數設置this爲任何你選擇的對象。例如:

<button data-bind="click: someObject.someFunction.bind(someObject)">
    Click me
</button>

 

如果你是C#或Java開發人員,你可以疑惑爲什麼我們還要用bind函數到一個對象想,特別是像調用 someObject.someFunction。 原因是在JavaScript裏,函數自己不是類的一部分,他們在單獨存在的對象,有可能多個對象都引用同樣的someFunction函數,所以當這個 函數被調用的時候它不知道誰調用的(設置this給誰)。在你bind之前運行時是不會知道的。KO默認情況下設置this對象是view model,但你可以用bind語法重定義它。

 

在注1裏使用匿名函數的時候沒有具體的要求,因爲JavaScript代碼 someObject.someFunction()就意味着調用someFunction,然後設置this到 someObject對象上。

 

注5:防止事件冒泡

默認情況下,Knockout允許click事件繼續在更高一層的事件句柄上冒泡執行。例如,如果你的元素和父元素都綁定了click事件,那當你點擊該元素的時候兩個事件都會觸發的。如果需要,你可以通過額外的綁定clickBubble來禁止冒泡。例如:

<div data-bind="click: myDivHandler">
    <button data-bind="click: myButtonHandler, clickBubble: false">
        Click me
    </button>
</div>

默認情況下,myButtonHandler會先執行,然後會冒泡執行myDivHandler。但一旦你設置了clickBubble爲false的時候,冒泡事件會被禁止。

 

依賴性

除KO核心類庫外,無依賴。

 

8   event 綁定

目的

event綁定在DOM元素上添加指定的事件句柄以便元素被觸發的時候執行定義的JavaScript 函數。大部分情況下是用在keypress,mouseover和mouseout上。

 

例子

<div>
    <div data-bind="event: { mouseover: enableDetails, mouseout: disableDetails }">
        Mouse over me
    </div>
    <div data-bind="visible: detailsEnabled">
        Details
    </div>
</div>


<script type="text/javascript">
    var viewModel = {
        detailsEnabled: ko.observable(false),
        enableDetails: function () {
            this.detailsEnabled(true);
        },
        disableDetails: function () {
            this.detailsEnabled(false);
        }
    };
</script>

每次鼠標在第一個元素上移入移出的時候都會調用view model上的方法來toggle detailsEnabled的值,而第二個元素會根據detailsEnabled的值自動顯示或者隱藏。

 

參數

    主參數

    你需要傳入的是一個JavaScript對象,他的屬性名是事件名稱,值是你所需要執行的函數。

    你可以聲明任何JavaScript函數 – 不一定非要是view model裏的函數。你可以聲明任意對象上的任何函數,例如: event: { mouseover: someObject.someFunction }。

    View model上的函數在用的時候有一點點特殊,就是不需要引用對象的,直接引用函數本身就行了,比如直接寫event: { mouseover: enableDetails } 就可以了,而無需寫成: event: { mouseover: viewModel.enableDetails }(儘管是合法的)。

    其它參數

        無

 

注1:傳參數給你的click 句柄

最簡單的辦法是傳一個function包裝的匿名函數:

<button data-bind="event: { mouseover: function() { viewModel.myFunction('param1', 'param2') } }">
    Click me
</button>

這樣,KO就會調用這個匿名函數,裏面會執行viewModel.myFunction(),並且傳進了'param1' 和'param2'參數。

 

注2:訪問事件源對象

有些情況,你可能需要使用事件源對象,Knockout會將這個對象傳遞到你函數的第一個參數:

<div data-bind="event: { mouseover: myFunction }">
    Mouse over me
</div>

 
 <script type="text/javascript">
     var viewModel = {
         myFunction: function (event) {
             if (event.shiftKey) {
                 //do something different when user has shift key down
             } else {
                 //do normal action
             }
         }
     };
</script>

如果你需要的話,可以使用匿名函數的第一個參數傳進去,然後在裏面調用:

<div data-bind="event: { mouseover: function(event) { viewModel.myFunction(event, 'param1', 'param2') } }">
    Mouse over me
</div>

這樣,KO就會將事件源對象傳遞給你的函數並且使用了。

 

注3: 允許執行默認事件

默認情況下,Knockout會阻止冒泡,防止默認的事件繼續執行。例如,如果在一個input標籤上綁定一個keypress事件,當你輸入內容 的時候,瀏覽器只會調用你的函數而不是天價你輸入的值。另外一個例子click綁定,當你點擊一個a連接,在執行完自定義事件時它不會連接到href地 址。因爲你的自定義事件主要就是操作你的view model,而不是連接到另外一個頁面。

當然,如果你想讓默認的事件繼續執行,你可以在你event的自定義函數裏返回true。

 

注4:控制this句柄

初學者可以忽略這小節,因爲大部分都用不着,高級用戶可以參考如下內容:

KO在調用你定義的event綁定函數時,會將view model傳給this對象(也就是ko.applyBindings使用的view model)。主要是方便你在調用你在view model裏定義的方法的時候可以很容易再調用view model裏定義的其它屬性。例如: this.someOtherViewModelProperty。

 

如果你想引用其它對象,我們有兩種方式:

你可以和注1裏那樣使用匿名函數,因爲它支持任意JavaScript 對象。

你也可以直接引用任何函數對象。你可以使用bind使callback函數設置this爲任何你選擇的對象。例如:

<div data-bind="event: { mouseover: someObject.someFunction.bind(someObject) }">
    Mouse over me
</div>

如果你是C#或Java開發人員,你可以疑惑爲什麼我們還要用bind函數到一個對象想,特別是像調用 someObject.someFunction。 原因是在JavaScript裏,函數自己不是類的一部分,他們在單獨存在的對象,有可能多個對象都引用同樣的someFunction函數,所以當這個 函數被調用的時候它不知道誰調用的(設置this給誰)。在你bind之前運行時是不會知道的。KO默認情況下設置this對象是view model,但你可以用bind語法重定義它。

 

在注1裏使用匿名函數的時候沒有具體的要求,因爲JavaScript代碼 someObject.someFunction()就意味着調用someFunction,然後設置this到 someObject對象上。

 

注5:防止事件冒泡

默認情況下,Knockout允許event事件繼續在更高一層的事件句柄上冒泡執行。例如,如果你的元素和父元素都綁定了mouseover事 件,那麼如果你的鼠標在該元素移動的時候兩個事件都會觸發的。如果需要,你可以通過額外的綁定youreventBubble來禁止冒泡。例如:

<div data-bind="event: { mouseover: myDivHandler }">
    <button data-bind="event: { mouseover: myButtonHandler }, mouseoverBubble: false">
        Click me
    </button>
</div>

默認情況下,myButtonHandler會先執行,然後會冒泡執行myDivHandler。但一旦你設置了mouseoverBubble爲false的時候,冒泡事件會被禁止。

 

依賴性

除KO核心類庫外,無依賴。

 

9   submit 綁定

目的

submit綁定在form表單上添加指定的事件句柄以便該form被提交的時候執行定義的JavaScript 函數。只能用在表單form元素上。

當你使用submit綁定的時候, Knockout會阻止form表單默認的submit動作。換句話說,瀏覽器會執行你定義的綁定函數而不會提交這個form表單到服務器上。可以很好地 解釋這個,使用submit綁定就是爲了處理view model的自定義函數的,而不是再使用普通的HTML form表單。如果你要繼續執行默認的HTML form表單操作,你可以在你的submit句柄裏返回true。

 

例子

<form data-bind="submit: doSomething">
    ... form contents go here ...
    <button type="submit">Submit</button>
</div>

<script type="text/javascript">
    var viewModel = {
        doSomething: function (formElement) {
            // ... now do something
        }
    };
</script>

這個例子裏,KO將把整個form表單元素作爲參數傳遞到你的submit綁定函數裏。 你可以忽略不管,但是有些例子裏是否有用,參考:ko.postJson工具。

 

爲什麼不在submit按鈕上使用click綁定?

在form上,你可以使用click綁定代替submit綁定。不過submit可以handle其它的submit行爲,比如在輸入框裏輸入回車的時候可以提交表單。

 

參數

    主參數

    你綁定到submit事件上的函數

    你可以聲明任何JavaScript函數 – 不一定非要是view model裏的函數。你可以聲明任意對象上的任何函數,例如: submit: someObject.someFunction。

    View model上的函數在用的時候有一點點特殊,就是不需要引用對象的,直接引用函數本身就行了,比如直接寫submit: doSomething就可以了,而無需寫成: submit: viewModel. doSomething(儘管是合法的)。

    其它參數

        無

 

備註:

關於如果傳遞更多的參數給submit綁定函數,或者當調用非view model裏的函數的時如何控制this,請參考click綁定。所有click綁定相關的notes也都適用於submit綁定。

 

依賴性

除KO核心類庫外,無依賴。

 

10   enable 綁定

目的

enable綁定使DOM元素只有在參數值爲 true的時候才enabled。在form表單元素input,select,和textarea上非常有用。

 

例子

<p>
    <input type='checkbox' data-bind="checked: hasCellphone"/>
    I have a cellphone
</p>

<p>
    Your cellphone number:
    <input type='text' data-bind="value: cellphoneNumber, enable: hasCellphone"/>
</p>
 
<script type="text/javascript">
    var viewModel = {
        hasCellphone: ko.observable(false),
        cellphoneNumber: ""
    };
</script>

這個例子裏,“Your cellphone number”後的text box 初始情況下是禁用的,只有當用戶點擊標籤 “I have a cellphone”的時候纔可用。

 

參數

    主參數

    聲明DOM元素是否可用enabled。

    非布爾值會被解析成布爾值。例如0和null被解析成false,21和非null對象被解析給true。

    如果你的參數是observable的,那綁定會隨着observable值的改變而自動更新enabled/disabled狀態。如果不是,則只會設置一次並且以後不再更新。

    其它參數

         無

 

注:任意使用JavaScript表達式

不緊緊限制於變量 – 你可以使用任何JavaScript表達式來控制元素是否可用。例如,

<button data-bind="enabled: parseAreaCode(viewModel.cellphoneNumber()) != '555'">
    Do something
</button>

 

依賴性

除KO核心類庫外,無依賴。

 

11   disable 綁定

目的

disable綁定使DOM元素只有在參數值爲 true的時候才disabled。在form表單元素input,select,和textarea上非常有用。

disable綁定和enable綁定正好相反,詳情請參考enable綁定。

Knockout應用開發指南 第三章:綁定語法(3)

2011-11-24 12:52 by 湯姆大叔, 18882 閱讀, 9 評論, 收藏, 編輯

12   value 綁定

目的

value綁定是關聯DOM元素的值到view model的屬性上。主要是用在表單控件<input>,<select>和<textarea>上。

當用戶編輯表單控件的時候, view model對應的屬性值會自動更新。同樣,當你更新view model屬性的時候,相對應的元素值在頁面上也會自動更新。

注:如果你在checkbox或者radio button上使用checked綁定來讀取或者寫入元素的 checked狀態,而不是value 值的綁定。

 

例子

<p>Login name: <input data-bind="value: userName"/></p>
<p>Password: <input type="password" data-bind="value: userPassword"/></p>
 

<script type="text/javascript">
    var viewModel = {
        userName: ko.observable(""),        // Initially blank
        userPassword: ko.observable("abc"), // Prepopulate
    };
</script>

 

參數

    主參數

    KO設置此參數爲元素的value值。之前的值將被覆蓋。

    如果參數是監控屬性observable的,那元素的value值將根據參數值的變化而更新,如果不是,那元素的value值將只設置一次並且以後不在更新。

    如果你提供的參數不是一個數字或者字符串(而是對象或者數組)的話,那顯示的value值就是yourParameter.toString() 的內容(通常沒用,所以最好都設置爲數字或者字符串)。

    不管什麼時候,只要你更新了元素的值,那 KO都會將view model對應的屬性值自動更新。默認情況下當用戶離開焦點(例如onchange事件)的時候,KO才更新這個值,但是你可以通過第2個參數valueUpdate來特別指定改變值的時機。

 

    其它參數

        valueUpdate

        如果你使用valueUpdate參數,那就是意味着KO將使用自定義的事件而不是默認的離開焦點事件。下面是一些最常用的選項:

            “change”(默認值) - 當失去焦點的時候更新view model的值,或者是<select> 元素被選擇的時候。

            “keyup” – 當用戶敲完一個字符以後立即更新view model。

            “keypress” – 當用戶正在敲一個字符但沒有釋放鍵盤的時候就立即更新view model。不像 keyup,這個更新和keydown是一樣的。

            “afterkeydown” – 當用戶開始輸入字符的時候就更新view model。主要是捕獲瀏覽器的keydown事件或異步handle事件。

        上述這些選項,如果你想讓你的view model進行實時更新,使用“afterkeydown”是最好的選擇。

 

例子:

<p>Your value: <input data-bind="value: someValue, valueUpdate: 'afterkeydown'"/></p>
<p>You have typed: <span data-bind="text: someValue"></span></p> <!-- updates in real-time -->

<script type="text/javascript">
    var viewModel = {
        someValue: ko.observable("edit me")
    };
</script>

 

注1:綁定下拉菜單drop-down list(例如SELECT)

Knockout對下拉菜單drop-down list綁定有一個特殊的支持,那就是在讀取和寫入綁定的時候,這個值可以是任意JavaScript對象,而不必非得是字符串。在你讓你用戶選擇一組model對象的時候非常有用。具體例子,參考options綁定。

類似,如果你想創建一個multi-select list,參考selectedOptions綁定。

 

注2:更新observable和non-observable屬性值

如果你用value綁定將你的表單元素和你的observable屬性關聯起來,KO設置的2-way的雙向綁定,任何一方改變都會更新另外一方的值。

但是,如果你的元素綁定的是一個non-observable屬性(例如是一個原始的字符串或者JavaScript表達式) ,KO會這樣執行:

如果你綁定的non-observable屬性是簡單對象,例如一個常見的屬性值,KO會設置這個值爲form表單元素的初始值,如果你改變 form表單元素的值,KO會將值重新寫回到view mode的這個屬性。但當這個屬性自己改變的時候,元素卻不會再變化了(因爲不是observable的),所以它僅僅是1-way綁定。

如果你綁定的non-observable屬性是複雜對象,例如複雜的JavaScript 表達式或者子屬性,KO也會設置這個值爲form表單元素的初始值,但是改變form表單元素的值的時候,KO不會再寫會view model屬性,這種情況叫one-time-only value setter,不是真正的綁定。

 

例子:

<p>First value: <input data-bind="value: firstValue"/></p>          <!-- two-way binding -->
<p>Second value: <input data-bind="value: secondValue"/></p>        <!-- one-way binding -->
<p>Third value: <input data-bind="value: secondValue.length"/></p>  <!-- no binding -->

<script type="text/javascript">
    var viewModel = {
        firstValue: ko.observable("hello"), // Observable
        secondValue: "hello, again"// Not observable
    };
    ko.applyBindings(viewModel);
</script>

 

依賴性

除KO核心類庫外,無依賴。

 

13   checked 綁定

目的

checked綁定是關聯到checkable的form表單控件到view model上 - 例如checkbox(<input type='checkbox'>)或者radio button(<input type='radio'>) 。當用戶check關聯的form表單控件的時候,view model對應的值也會自動更新,相反,如果view model的值改變了,那控件元素的check/uncheck狀態也會跟着改變。

注:對text box,drop-down list和所有non-checkable的form表單控件,用value綁定來讀取和寫入是該元素的值,而不是checked綁定。

例子

<p>Send me spam: <input type="checkbox" data-bind="checked: wantsSpam"/></p>

<script type="text/javascript">
    var viewModel = {
        wantsSpam: ko.observable(true) // Initially checked
    };

     // ... then later ...
    viewModel.wantsSpam(false); // The checkbox becomes unchecked
</script>

 

Checkbox關聯到數組

<p>Send me spam: <input type="checkbox" data-bind="checked: wantsSpam"/></p>
<div data-bind="visible: wantsSpam">
    Preferred flavors of spam:
    <div><input type="checkbox" value="cherry" data-bind="checked: spamFlavors"/> Cherry</div>
    <div><input type="checkbox" value="almond" data-bind="checked: spamFlavors"/> Almond</div>
    <div><input type="checkbox" value="msg" data-bind="checked: spamFlavors"/> Monosodium Glutamate</div>
</div>

 
<script type="text/javascript">

    var viewModel = {
        wantsSpam: ko.observable(true),
        spamFlavors: ko.observableArray(["cherry", "almond"]) // Initially checks the Cherry and Almond checkboxes
    };

    // ... then later ...
    viewModel.spamFlavors.push("msg"); // Now additionally checks the Monosodium Glutamate checkbox
</script>

 

添加radio button

<p>Send me spam: <input type="checkbox" data-bind="checked: wantsSpam"/></p>

<div data-bind="visible: wantsSpam">
    Preferred flavor of spam:
    <div><input type="radio" name="flavorGroup" value="cherry" data-bind="checked: spamFlavor"/> Cherry</div>
    <div><input type="radio" name="flavorGroup" value="almond" data-bind="checked: spamFlavor"/> Almond</div>
    <div><input type="radio" name="flavorGroup" value="msg" data-bind="checked: spamFlavor"/> Monosodium Glutamate</div>
</div>

 
<script type="text/javascript">

    var viewModel = {
        wantsSpam: ko.observable(true),
        spamFlavor: ko.observable("almond") // Initially selects only the Almond radio button
    };

     // ... then later ...
    viewModel.spamFlavor("msg"); // Now only Monosodium Glutamate is checked
</script>

 

參數

    主參數

    KO會設置元素的checked狀態匹配到你的參數上,之前的值將被覆蓋。對參數的解析取決於你元素的類型:

        對於checkbox,當參數爲true的時候,KO會設置元素的狀態爲checked,反正設置爲unchecked。如果你傳的參數不是布爾值,那KO將會解析成布爾值。也就是說非0值和非null對象,非空字符串將被解析成true,其它值都被解析成false。

        當用戶check或者uncheck這個checkbox的時候,KO會將view model的屬性值相應地設置爲true或者false。

        一個特殊情況是參數是一個數組,如果元素的值存在於數組,KO就會將元素設置爲checked,如果數組裏不存在,就設置爲unchecked。如果用戶對checkbox進行check或uncheck,KO就會將元素的值添加數組或者從數組裏刪除。

        對於radio buttons,KO只有當參數值等於radio button value屬性值的時候才設置元素爲checked狀態。所以參數應是字符串。在上面的例子裏只有當view model 的spamFlavor 屬性等於“almond”的時候,該radio button纔會設置爲checked。

        當用戶將一個radio button選擇上的時候 is selected,KO會將該元素的value屬性值更新到view model屬性裏。上面的例子,當點擊value= “cherry”的選項上, viewModel.spamFlavor的值將被設置爲“cherry”。

        當然,最有用的是設置一組radio button元素對應到一個單個的view model 屬性。確保一次只能選擇一個radio button需要將他們的name屬性名都設置成一樣的值(例如上個例子的flavorGroup值)。這樣的話,一次就只能選擇一個了。

    如果參數是監控屬性observable的,那元素的checked狀態將根據參數值的變化而更新,如果不是,那元素的value值將只設置一次並且以後不在更新。

    其它參數

    無

 

依賴性

除KO核心類庫外,無依賴。

 

14   options 綁定

目的

options綁定控制什麼樣的options在drop-down列表裏(例如:<select>)或者 multi-select 列表裏 (例如:<select size='6'>)顯示。此綁定不能用於<select>之外的元素。關聯的數據應是數組(或者是observable數組),& lt;select>會遍歷顯示數組裏的所有的項。

 

注:對於multi-select列表,設置或者獲取選擇的多項需要使用selectedOptions綁定。對於single-select列表,你也可以使用value綁定讀取或者設置元素的selected項。

 

例1:Drop-down list

<p>Destination country: <select data-bind="options: availableCountries"></select></p>

 
<script type="text/javascript">
    var viewModel = {
        availableCountries: ko.observableArray(['France', 'Germany', 'Spain']) // These are the initial options
    };

    // ... then later ...
    viewModel.availableCountries.push('China'); // Adds another option
</script>

 

例2:Multi-select list

<p>Choose some countries you'd like to visit: <select data-bind="options: availableCountries" size="5" multiple="true"></select></p>
 
<script type="text/javascript">
    var viewModel = {
        availableCountries: ko.observableArray(['France', 'Germany', 'Spain'])
    };
</script>

 

例3:Drop-down list展示的任意JavaScript對象,不僅僅是字符串

<p>
    Your country:
    <select data-bind="options: availableCountries,
              optionsText: 'countryName', value: selectedCountry, optionsCaption: 'Choose...'"></select>
</p>

<div data-bind="visible: selectedCountry"> <!-- Appears when you select something -->
    You have chosen a country with population
    <span data-bind="text: selectedCountry() ? selectedCountry().countryPopulation : 'unknown'"></span>.
</div>

<script type="text/javascript">
    // Constructor for an object with two properties
var country =function (name, population) {
        this.countryName = name;
        this.countryPopulation = population;
    };

     var viewModel = {
        availableCountries: ko.observableArray([
            new country("UK", 65000000),
            new country("USA", 320000000),
            new country("Sweden", 29000000)
        ]),
        selectedCountry: ko.observable() // Nothing selected by default
    };
</script>

 

例4:Drop-down list展示的任意JavaScript對象,顯示text是function的返回值

<!-- Same as example 3, except the <select> box expressed as follows: -->

<select data-bind="options: availableCountries,
                   optionsText: function(item) {
                       return item.countryName + ' (pop: ' + item.countryPopulation + ')'
                   },
                   value: selectedCountry,
                   optionsCaption: 'Choose...'"></select>

 

注意例3和例4在optionsText值定義上的不同。

 

參數

    主參數

    該參數是一個數組(或者observable數組)。對每個item,KO都會將它作爲一個<option> 添加到<select>裏,之前的options都將被刪除。

    如果參數是一個string數組,那你不需要再聲明任何其它參數。<select>元素會將每個string顯示爲一個option。不過, 如果你讓用戶選擇的是一個JavaScript對象數組(不僅僅是string),那就需要設置optionsText和optionsValue這兩個 參數了。

    如果參數是監控屬性observable的,那元素的options項將根據參數值的變化而更新,如果不是,那元素的value值將只設置一次並且以後不在更新。

 

    其它參數

        optionsCaption

        有時候,默認情況下不想選擇任何option項。但是single-select drop-down列表由於每次都要默認選擇以項目,怎麼避免這個問題呢?常用的方案是加一個“請選擇的”或者“Select an item”的提示語,或者其它類似的,然後讓這個項作爲默認選項。

        我們使用optionsCaption參數就能很容易實現,它的值是字符串型,作爲默認項顯示。例如:

        <select data-bind='options: myOptions, optionsCaption: "Select an item...", value: myChosenValue'></select>

        KO會在所有選項上加上這一個項,並且設置value值爲undefined。所以,如果myChosenValue被設置爲undefined(默認是observable的),那麼上述的第一個項就會被選中。

 

        optionsText

        上面的例3展示的綁定JavaScript對象到option上 – 不僅僅是字符串。這時候你需要設置這個對象的那個屬性作爲drop-down列表或multi-select列表的text來顯示。例如,例3中使用的是 設置額外的參數optionsText將對象的屬性名countryName作爲顯示的文本。

        如果不想僅僅顯示對象的屬性值作爲每個item項的text值,那你可以設置optionsText 爲JavaScript 函數,然後再函數裏通過自己的邏輯返回相應的值(該函數參數爲item項本身)。例4展示的就是返回item的2個屬性值合併的結果。

 

        optionsValue

        和optionsText類似, 你也可以通過額外參數optionsValue來聲明對象的那個屬性值作爲該<option>的value值。

        經典場景:如在更新options的時候想保留原來的已經選擇的項。例如,當你重複多次調用Ajax獲取car列表的時候,你要確保已經選擇的某個car 一直都是被選擇上,那你就需要設置optionsValue爲“carId”或者其它的unique標示符,否則的話KO找不知道之前選擇的car是新 options裏的哪一項。

 

        selectedOptions

        對於multi-select列表,你可以用selectedOptions讀取和設置多個選擇項。技術上看它是一個單獨的綁定,有自己的文檔,請參考: selectedOptions綁定。

 

注:已經被選擇的項會再options改變的時候保留

當使用options綁定<select>元素的時候,如果options改變,KO將儘可能第保留之前已經被選擇的項不變(除非是你 事先手工刪除一個或多個已經選擇的項)。這是因爲options 綁定嘗試依賴value值的綁定(single-select列表)和selectedOptions綁定(multi-select列表)。

 

依賴性

除KO核心類庫外,無依賴。

 

15   selectedOptions 綁定

目的

selectedOptions綁定用於控制multi-select列表已經被選擇的元素,用在使用options綁定的<select>元素上。

當用戶在multi-select列表選擇或反選一個項的時候,會將view model的數組進行相應的添加或者刪除。同樣,如果view model上的這個數組是observable數組的話,你添加或者刪除任何item(通過push或者splice)的時候,相應的UI界面裏的 option項也會被選擇上或者反選。這種方式是2-way綁定。

注:控制single-select下拉菜單選擇項,你可以使用value綁定。

 

例子

<p>
    Choose some countries you'd like to visit:
    <select data-bind="options: availableCountries, selectedOptions: chosenCountries" size="5" multiple="true"></select>
</p>

<script type="text/javascript">
    var viewModel = {
        availableCountries: ko.observableArray(['France', 'Germany', 'Spain']),
        chosenCountries: ko.observableArray(['Germany']) // Initially, only Germany is selected
    };

    // ... then later ...
    viewModel.chosenCountries.push('France'); // Now France is selected too
</script>

 

參數

    主參數

    該參數是數組(或observable數組)。KO設置元素的已選項爲和數組裏match的項,之前的已選擇項將被覆蓋。

    如果參數是依賴監控屬性observable數組,那元素的已選擇項selected options項將根據參數值的變化(通過push,pop,或其它observable數組方法)而更新,如果不是,那元素的已選擇項selected options將只設置一次並且以後不在更新。

    不管該參數是不是observable數組,用戶在multi-select列表裏選擇或者反選的時候,KO都會探測到,並且更新數組裏的對象以達到同步的結果。這樣你就可以獲取options已選項。

    其它參數

           無

 

注:支持讓用戶選擇任意JavaScript對象

在上面的例子裏,用戶可以選擇數組裏的字符串值,但是選擇不限於字符串,如果你願意你可以聲明包含任意JavaScript對象的數組,查看options綁定如何顯示JavaScript對象到列表裏。

這種場景,你可以用selectedOptions來讀取或設置這些對象本身,而不是頁面上顯示的option表示形式,這樣做在大部分情況下都非 常清晰。view model就可以探測到你從數組對象裏選擇的項了,而不必關注每個項和頁面上展示的option項是如何map的。

 

依賴性

除KO核心類庫外,無依賴。

 

16   uniqueName 綁定

目的

uniqueName綁定確保所綁定的元素有一個非空的name屬性。如果該元素沒有name屬性,那綁定會給它設置一個unique的字符串值作爲name屬性。你不會經常用到它,只有在某些特殊的場景下纔用到,例如:

在使用KO的時候,一些技術可能依賴於某些元素的name屬性,儘快他們沒有什麼意義。例如,jQuery Validation驗證當前只驗證有name屬性的元素。爲配合Knockout UI使用,有些時候需要使用uniqueName綁定避免讓jQuery Validation驗證出錯。

IE 6下,如果radio button沒有name屬性是不允許被checked了。大部分時候都沒問題,因爲大部分時候radio button元素都會有name屬性的作爲一組互相的group。不過,如果你沒聲明,KO內部會在這些元素上使用uniqueName那麼以確保他們可 以被checked。

 

例子

<input data-bind="value: someModelProperty, uniqueName: true"/>

 

參數

    主參數

    就像上面的例子一樣,傳入true(或者可以轉成true的值)以啓用uniqueName綁定。

    其它參數

        無

 

依賴性

除KO核心類庫外,無依賴。

Knockout應用開發指南 第四章:模板綁定

2011-11-25 09:01 by 湯姆大叔, 14468 閱讀, 17 評論, 收藏, 編輯

模板綁定The template binding

目的

template綁定通過模板將數據render到頁面。模板綁定對於構建嵌套結構的頁面非常方便。默認情況, Knockout用的是流行的jquery.tmpl模板引擎。使用它的話,需要在安裝頁面下載和引用jquery.tmpl和jQuery框架。或者你 也可以集成其它的模板引擎(雖然需要了解Knockout 內部知識才行)。

 

例子

<div data-bind='template: "personTemplate"'> </div>
<script id='personTemplate' type='text/html'>
    ${ name } is ${ age } years old
    <button data-bind='click: makeOlder'>Make older</button>
</script>

<script type='text/javascript'>
    var viewModel = {
        name: ko.observable('Bert'),
        age: ko.observable(78),
        makeOlder: function () {
            this.age(this.age() +1);
        }
    };
    ko.applyBindings(viewModel);
</script>

當引用的observable(dependent observable)數據改變的時候,Knockout會自動重新render模板。在這個例子裏,每次點擊button的時候都會重新render模板。

 

語法

你可以使用任何你模板引擎支持的語法。jquery.tmpl執行如下語法:

${ someValue } — 參考文檔

`html someValue` — 參考文檔

{{if someCondition}} — 參考文檔

{{else someCondition}} — 參考文檔

`each someArray` — 參考文檔

 

和observable數組一起使用`each`

當然使用{{each someArray}}的時候,如果你的值是observableArray,你必須使用JavaScript類型的基礎數組類型{{each myObservableArray()}},而不是`each myObservableArray`。

 

參數

    主參數

        語法快速記憶:如果你聲明的僅僅是字符串(上個例子),KO會使用模板的ID來render。應用在模板上的數據是你的整個view model對象(例如ko.applyBindings 綁定的對象)。

        更多控件,你可以傳帶有如下屬性的JavaScript對象:

            name(必選項) — 需要render的模板ID – 參考 注5 如何使用function函數聲明ID。

            data(可選項) — 需要render到模板的數據。如果你忽略整個參數,KO將查找foreach參數,或者是應用整個view model對象。

            foreach(可選項) — 指定KO按照“foreach”模式render模板 – 參考 注3。

            afterAdd或beforeRemove(可選項) — 在foreach模式下使用callback函數。

            templateOptions(可選項) — 在render模板的時候,傳遞額外數據以便使用。參考 注6。

 

傳遞多個參數的例子:

<div data-bind='template: { name: "personTemplate", data: someObject }'> </div>

 

注1:Render嵌套模板

因爲在模板裏使用的是data-bind屬性來聲明的,所以嵌套模板你可以再次使用data-bind='template: ...',在上層模板的元素裏。

這比模板引起的原生語法好用多了(例如jquery.tmpl裏的`tmpl`)。Knockout語法的好處在於可以在每層模板的跟着相關的依賴值,所以如果依賴改變了,KO將只會重新render依賴所在的那個模板。這將很大地改善了性能。

 

注2:${ val }和<span data-bind='text: val'></span>有何不同?

當你在模板內部使用data-bind屬性的時候,KO是單獨爲這個綁定單獨跟蹤依賴項的。當model改變的時候,KO只會更新綁定的元素以及子元素而不需要重新render整個模板。所以如果你聲明這樣的代碼是<span data-bind='text: someObservableValue'></span>,當 someObservableValue改變的時候,KO將只是簡單地更新<span>元素的text值而不需要重新render整個模板。

不過,如果模板內部使用的observable值(例如${ someObservableValue }),如果這個observable值改變了,那KO將重新render整個模板。

這就是說,很多情況下<span data-bind='text: someObservableValue'></span>性能要比${ someObservableValue }要好,因爲值改變的話不會影響臨近元素的狀態。不過${ someObservableValue }語法比較簡潔,如果你的模板比較小的話,還是更合適的,不會帶來大的性能問題。

 

注3:使用foreach

如果需要爲集合裏的每一個item render一次模板,有2種方式:

    你可以使用模板引擎裏的原生“each”語法,對jquery.tmpl來說就是用`each`語法迭代數組。

    另外一種方式就是用Knockout的foreach模式來render。

 

例子:

<div data-bind='template: { name: "personTemplate",
                            foreach: someObservableArrayOfPeople }'> </div>

 

foreach模板模式的好處是:

當往你的collection集合裏添加新item項的時候,KO只會對這個新item進行render模板,並且將結果附加到現有的DOM上。

當從collection集合裏刪除item的時候,KO將不會重新render任何模板,而只是簡單地刪除相關的元素。

KO允許通過自定義的方式聲明afterAdd和beforeRemove的callback函數添加/刪除DOM元素。然後這個callback會在刪除元素的時候進行一些動畫或者其它操作。

與原生的each不同之處是:在改變之後,模板引擎強制重新render模板裏所有的內容,因爲它根本就不關注KO裏所謂的依賴跟蹤內容。

關於使用foreach模式的例子,參考grid editor和animated transitions。

 

注4:使用afterRender選項

有時候,你需要在模板生成的DOM元素上深度定義邏輯。例如,你可能想再模板輸出的時候進行截獲,然後在render的元素上允許jQuery UI命令(比如date picker,slider,或其它)。

你可以使用afterRender選項,簡單聲明一個function函數(匿名函數或者view model裏的函數),在render或者重新render模板之後Knockout會重新調用它。如果你使用的是foreach,那在每個item添加 到observable數組之後, Knockout會立即調用afterRender的callback函數。例如,

<div data-bind='template: { name: "personTemplate",
                            data: myData,
                            afterRender: myPostProcessingLogic }'> </div>

… 在view model裏聲明一個類似的函數(例如,對象包含myData):

viewModel.myPostProcessingLogic = function (elements) {
    // "elements" is an array of DOM nodes just rendered by the template
    // You can add custom post-processing logic here
}

 

注5:動態決定使用哪個模板

有時候,你可能需要根據數據的狀態來決定使用哪個模板的ID。可以通過function的返回ID應用到name選擇上。如果你用的是 foreach模板模式, Knockout會對每個item執行function(將item作爲參數)從而將返回值作爲ID,否則,該function接受的參數是整個 data option或者是整個view model。

 

例子:

<ul data-bind='template: { name: displayMode,
                           foreach: employees }'> </ul>
<script type='text/javascript'>
var viewModel = {
    employees: ko.observableArray([
        { name: "Kari", active: ko.observable(true) },
        { name: "Brynn", active: ko.observable(false) },
        { name: "Nora", active: ko.observable(false) }
    ]),
    displayMode: function (employee) {
        return employee.active() ?"active" : "inactive";
        // Initially "Kari" uses the "active" template, while the others use "inactive"
    }
};

// ... then later ...
viewModel.employees()[1].active(true);
// Now "Brynn" is also rendered using the "active" template.
</script>

如果你的function引用的是observable值,那當這些值改變的時候,綁定的值會隨着改變的。這將導致相應的模板重新render。

 

注6:使用templateOptions傳遞額外的參數

如果你在綁定模板的時候需要傳入額外的數據的話,你可以使用templateOptions對象來傳遞這些值。這可以幫助你通過一些 不屬於view model過濾條件或者字符來重用模板。另外一個好處是用在範圍控制,你可以引用通過你的模板訪問怒道的數據。

 

例子,

<ul data-bind='template: { name: "personTemplate",
                           foreach: employees,
                           templateOptions: { label: "Employee:",
                                              selectedPerson: selectedEmployee } }'> </ul>


<script id='personTemplate' type='text/html'>
    <div data-bind="css: { selected: $data === $item.selectedPerson()" }">
        ${ $item.label } <input data-bind="value: name" />
    </div>
</script>

在整個例子裏,personTemplate有可能都使用employee和自定義對象。通過templateOptions我們可以傳遞一個字符 label和當前已選擇項作爲selectedPerson來控制style。在jquery.tmpl模板裏,這些值可以通過訪問$item對象的屬性 得到。

 

注7:模板是被預編譯和緩存的

爲了最大性能,Knockout內嵌模板引擎jquery.tmpl會利用自身的功能對你的模板進行預編譯成可執行的JavaScript代碼,然後從編譯流程裏緩存輸出。這將使模板更快更加具有可執行性,尤其是是使用foreach循環來render相同模板的時候。

一般情況你不會注意到這個,所以經常會忘記。不過,當你在某種原因下通過編程重寫模板<script>元素的時候並且該模板之前已經用 過一次的話,你的改變不會帶來任何render的變化,因爲在第一次使用的時候已經預編譯了並且緩存起來了。(如果這些會帶來問題,我們將考慮在KO新版 本里提供一個禁用或重設模板緩存的功能,不過好像沒有好的原因去動態改變模板<script>元素的內容)。

 

注8:使用不同的模板引擎

如果你想使用不同的JavaScript模板引擎(或者是因爲某些原因你不想在jQuery上使用依賴)。我們可以去爲KO來寫一個不同的模板引 擎,例如,在KO源代碼裏的jqueryTmplTemplateEngine.js,儘管他是爲了支持多個版本的jquery.tmpl而編譯。支持一 個單獨的模板引擎版本相對簡單多了。

 

依賴性

template綁定只能在引用合適的模板引擎情況下才能工作。例如提到的jquery.tmpl引擎。

Knockout應用開發指南 第五章:創建自定義綁定

2011-11-26 19:20 by 湯姆大叔, 9751 閱讀, 3 評論, 收藏, 編輯

創建自定義綁定

你可以創建自己的自定義綁定 – 沒有必要非要使用內嵌的綁定(像click,value等)。你可以你封裝複雜的邏輯或行爲,自定義很容易使用和重用的綁定。例如,你可以在form表單裏自定義像grid,tabset等這樣的綁定。

重要:以下文檔只應用在Knockout 1.1.1和更高版本,Knockout 1.1.0和以前的版本在註冊API上是不同的。

 

註冊你的綁定

添加子屬性到ko.bindingHandlers來註冊你的綁定:

ko.bindingHandlers.yourBindingName = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
        // This will be called when the binding is first applied to an element
        // Set up any initial state, event handlers, etc. here
    },

    update: function(element, valueAccessor, allBindingsAccessor, viewModel) {
        // This will be called once when the binding is first applied to an element,
        // and again whenever the associated observable changes value.
        // Update the DOM element based on the supplied values here.
    }
};

… 然後就可以在任何DOM元素上使用了:

<div data-bind="yourBindingName: someValue"> </div>

注:你實際上沒必要把init和update這兩個callbacks都定義,你可以只定義其中的任意一個。

 

update 回調

當管理的observable改變的時候,KO會調用你的update callback函數,然後傳遞以下參數:

element — 使用這個綁定的DOM元素

valueAccessor —JavaScript函數,通過valueAccessor()可以得到應用到這個綁定的model上的當前屬性值。

allBindingsAccessor —JavaScript函數,通過allBindingsAccessor ()得到這個元素上所有model的屬性值。

viewModel — 傳遞給ko.applyBindings使用的 view model參數,如果是模板內部的話,那這個參數就是傳遞給該模板的數據。

 

例如,你可能想通過 visible綁定來控制一個元素的可見性,但是你想讓該元素在隱藏或者顯示的時候加入動畫效果。那你可以自定義自己的綁定來調用jQuery的slideUp/slideDown 函數:

ko.bindingHandlers.slideVisible = {
    update: function(element, valueAccessor, allBindingsAccessor) {
        // First get the latest data that we're bound to
        var value = valueAccessor(), allBindings = allBindingsAccessor();       

        // Next, whether or not the supplied model property is observable, get its current value
        var valueUnwrapped = ko.utils.unwrapObservable(value);

        // Grab some more data from another binding property
        var duration = allBindings.slideDuration || 400;

        // 400ms is default duration unless otherwise specified
    
        // Now manipulate the DOM element

        if (valueUnwrapped == true)
            $(element).slideDown(duration); // Make the element visible
        else
            $(element).slideUp(duration);   // Make the element invisible
    }
};

 

然後你可以像這樣使用你的綁定:

<div data-bind="slideVisible: giftWrap, slideDuration:600">You have selected the option</div>
<label><input type="checkbox" data-bind="checked: giftWrap"/> Gift wrap</label>

<script type="text/javascript">
    var viewModel = {
        giftWrap: ko.observable(true)
    };
    ko.applyBindings(viewModel);
</script>

當然,看來可能代碼很多,但是一旦你創建了自定義綁定,你就可以在很多地方重用它。

 

init 回調

Knockout在DOM元素使用自定義綁定的時候會調用你的init函數。init有兩個重要的用途:

爲DOM元素設置初始值

註冊事件句柄,例如當用戶點擊或者編輯DOM元素的時候,你可以改變相關的observable值的狀態。

 KO會傳遞和update回調函數一樣的參數。

繼續上面的例子,你可以像讓slideVisible在頁面第一次顯示的時候設置該元素的狀態(但是不使用任何動畫效果),而只是讓動畫在以後改變的時候再執行。你可以這樣來做:

ko.bindingHandlers.slideVisible = {
    init: function(element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());
        // Get the current value of the current property we're bound to
        $(element).toggle(value);
        // jQuery will hide/show the element depending on whether "value" or true or false
    },

    update: function(element, valueAccessor, allBindingsAccessor) {
        // Leave as before
    }
};

這就是說giftWrap的初始值聲明的是false(例如giftWrap: ko.observable(false)),然後讓初始值會讓關聯的DIV隱藏,之後用戶點擊checkbox的時候會讓元素顯示出來。

 

DOM事件之後更新observable值

你已經值得了如何使用update回調,當observable值改變的時候,你可以更新相關的DOM元素。但是其它形式的事件怎麼做呢?比如當用戶對某個DOM元素有某些action操作的時候,你想更新相關的observable值。

你可以使用init回調來註冊一個事件句柄,這樣可以改變相關的observable值,例如,

ko.bindingHandlers.hasFocus = {

    init: function (element, valueAccessor) {
        $(element).focus(function () {
            var value = valueAccessor();
            value(true);
        });

        $(element).blur(function () {
            var value = valueAccessor();
            value(false);
        });
    },

    update: function (element, valueAccessor) {
        var value = valueAccessor();
        if (ko.utils.unwrapObservable(value))
            element.focus();
        else
            element.blur();
    }
};

現在你可以通過hasFocus綁定來讀取或者寫入這個observable值了:

<p>Name: <input data-bind="hasFocus: editingName"/></p>
<!-- Showing that we can both read and write the focus state -->
<div data-bind="visible: editingName">You're editing the name</div>
<button data-bind="enable: !editingName(), click:function() { editingName(true) }">Edit name</button>

<script type="text/javascript">
    var viewModel = {
        editingName: ko.observable()
    };
    ko.applyBindings(viewModel);
</script>

 

Knockout應用開發指南 第六章:加載或保存JSON數據

2011-11-28 09:02 by 湯姆大叔, 12004 閱讀, 3 評論, 收藏, 編輯

加載或保存JSON數據

Knockout可以實現很複雜的客戶端交互,但是幾乎所有的web應用程序都要和服務器端交換數據(至少爲了本地存儲需要序列化數據),交換數據最方便的就是使用JSON格式 – 大多數的Ajax應用程序也是使用這種格式。

 

加載或保存數據

Knockout不限制你用任何技術加載和保存數據。你可以使用任何技術和服務器來交互。用的最多的是使用jQuery的Ajax幫助,例如:getJSON,post和ajax。你可以通過這些方法從服務器端獲取數據:

$.getJSON("/some/url", function (data) {
    // Now use this data to update your view models,
    // and Knockout will update your UI automatically
})

… 或者向服務器端發送數據:

var data = /* Your data in JSON format - see below */;
$.post("/some/url", data, function(returnedData) {
    // This callback is executed if the post was successful  
})

或者,如果你不想用jQuery,你可以用任何其它的方式來讀取或保存JSON數據。所以, Knockout需要你做的僅僅是:

    對於保存,讓你的view model數據轉換成簡單的JSON格式,以方便使用上面的技術來保存數據。

    對於加載,更新你接收到的數據到你的view model上。

 

轉化View Model數據到JSON格式

由於view model都是JavaScript對象,所以你需要使用標準的JSON序列化工具讓轉化view model爲JSON格式。例如,可以使用JSON.serialize()(新版本瀏覽器才支持的原生方法),或者使用json2.js類庫。不過你的view model可能包括observables,依賴對象dependent observables和observable數組,有可能不能很好的序列化,你需要自己額外的處理一下數據。

 

爲了使view model數據序列化方便(包括序列化observables等格式),Knockout提供了2個幫助函數:

    ko.toJS — 克隆你的view model對象,並且替換所有的observable 對象爲當前的值,這樣你可以得到一個乾淨的和Knockout無關的數據copy。

    ko.toJSON — 將view model對象轉化成JSON字符串。原理就是:先調在view model上調用ko.toJS,然後調用瀏覽器原生的JSON 序列化器得到結果。注:一些老瀏覽器版本不支持原生的JSON 序列化器(例如:IE7和以前的版本),你需要引用json2.js類庫。

 

聲明一個view model:

var viewModel = {
    firstName: ko.observable("Bert"),
    lastName: ko.observable("Smith"),
    pets: ko.observableArray(["Cat", "Dog", "Fish"]),
    type: "Customer"
};

viewModel.hasALotOfPets = ko.dependentObservable(function () {
    return this.pets().length > 2
}, viewModel)

該view model包含observable類型的值,依賴類型的值dependent observable以及依賴數組observable array,和普通對象。你可以像如下代碼一樣使用ko.toJSON將此轉化成服務器端使用的JSON 字符串:

var jsonData = ko.toJSON(viewModel);

// Result: jsonData is now a string equal to the following value
// '{"firstName":"Bert","lastName":"Smith","pets":["Cat","Dog","Fish"],"type":"Customer","hasALotOfPets":true}'

或者,序列化之前,你想得到JavaScript簡單對象的話,直接使用像這樣一樣使用ko.toJS:

var plainJs = ko.toJS(viewModel);

// Result: plainJS is now a plain JavaScript object in which nothing is observable. It's just data.
// The object is equivalent to the following:
//   {
//      firstName: "Bert",
//      lastName: "Smith",
//      pets: ["Cat","Dog","Fish"],
//      type: "Customer",
//      hasALotOfPets: true
//   }

 

使用JSON更新View Model數據

如果你從服務器端獲取數據並且更新到view model上,最簡單的方式是自己實現。例如,

// Load and parse the JSON
var someJSON = /* Omitted: fetch it from the server however you want */;
var parsed = JSON.parse(someJSON);

// Update view model properties
viewModel.firstName(parsed.firstName);
viewModel.pets(parsed.pets);

很多情況下,最直接的方法就是最簡單而且最靈活的方式。當然,如果你更新了view model的屬性,Knockout會自動幫你更新相關的UI元素的。

 

不過,很多開發人員還是喜歡使用一種好用而不是每次都寫代碼的方式來轉化數據到view model上,尤其是view model有很多屬性或者嵌套的數據結構的時候,這很有用,因爲可以節約很多代碼量。knockout.mapping插件可以幫你做到這一點。

Knockout應用開發指南 第七章:Mapping插件

2011-11-29 09:08 by 湯姆大叔, 12908 閱讀, 16 評論, 收藏, 編輯

Mapping插件

Knockout設計成允許你使用任何JavaScript對象作爲view model。必須view model的一些屬性是observable的,你可以使用KO綁定他們到你的UI元素上,當這些observable值改變的時候,這些UI元素就會自動更新。

絕大多數程序都需要從服務器端獲取數據,但是由於服務器不知道observable的概念是什麼,它只支持簡單的JavaScript對象(通常是 序列化以後的JSON),mapping插件可以讓你很方便地將簡單JavaScript對象mapp到帶有observable值的view model。你也可以自己寫JavaScript代碼將從服務器獲取的數據來構建 view model,mapping插件只是一種很好的替代而已。

 下載

    Version 2.0 (最小版本8.6kb)

 

例子:手工mapping

顯示當前服務器時間和你網站上的當前用戶數。你應該使用如下的view model來代表你的這些信息:

var viewModel = {
    serverTime: ko.observable(),
    numUsers: ko.observable()
}

然後綁定view model到HTML元素上,如下:

The time on the server is: <span data-bind='text: serverTime'></span>
and <span data-bind='text: numUsers'></span> user(s) are connected.

由於view model屬性是observable的,在他們變化的時候,KO會自動更新綁定的HTML元素。

接下來,從服務器獲取最新的數據。或許每隔5秒你要調用一次Ajax請求(例如,使用jQuery的$.getJSON或$.ajax函授):

var data = getDataUsingAjax();          // Gets the data from the server

然後,服務器返回和下面相似的JSON數據:

{
    serverTime: '2010-01-07',
    numUsers: 3
}

最後,用這些數據更新你的view model(不使用mapping插件),代碼如下:

// Every time data is received from the server:
viewModel.serverTime(data.serverTime);
viewModel.numUsers(data.numUsers);

爲了使數據顯示在頁面上,所有的屬性都要像這樣寫代碼。如果你的數據結構很複雜的話(例如,包含子對象或者數組),那就維護起來就相當痛苦。 mapping插件就是來讓你讓你的JavaScript簡單對象(或JSON結構)轉換成observable的view model的。

 

例子:使用ko.mapping

通過mapping插件創建view model,直接使用ko.mapping.fromJS函數來創建:

var viewModel = ko.mapping.fromJS(data);

它會自動將data裏所有的屬性創建成observable類型的屬性。你可以通過ko.mapping.fromJS 函數定期從服務器獲取數據,然後更新你的view model:

// Every time data is received from the server:
ko.mapping.fromJS(data, viewModel);

 

如何mapping?

    對象的所有屬性都被轉換成observable類型值,如果獲取的對象的值改變了,就會更新這個observable類型的值.

    數組也被轉換成了observable數組,如果服務器更新改變了數組的個數,mapping插件也會添加或者刪除相應的item項,也會盡量保持和原生JavaScript數組相同的order順序。

 

Unmapping

如果你想讓map過的對象轉換成原來的JavaScript對象,使用如下方式:

var unmapped = ko.mapping.toJS(viewModel);

會創建一個unmapped對象,只包含你之前map過的對象屬性,換句話說,你在view model上手工添加的屬性或者函數都會被忽略的,唯一例外的是_destroy屬性是可以unmapped回來的,因爲你從 ko.observableArray裏destroy一個item項的時候會生成這個屬性。 請參考“高級用戶”小節如何配置使用。

 

與JSON字符串一起使用

如果你的Ajax調用返回的是JSON字符串(而不是反序列化後的JavaScript對象),你可以使用ko.mapping.fromJSON函數來創建或者更新你的view model。用ko.mapping.toJSON實現unmap。

使用.from/toJSON函數處理JSON字符串和使用.from/toJS函數處理JS對象是等價的。

 

高級用法

有時候,在使用ko.mapping.fromJS的時候,可能有必要去使用mapping的高級用法來定義mapping的詳細過程,以後定義了,以後再調用的時候就不必再定義了。這裏有一些情形,你可能需要使用這些option。

 

用法1:使用keys來使對象unique化

你有一個JavaScript對象,如下:

var data = {
    name: 'Scot',
    children: [
        { id: 1, name: 'Alicw' }
    ]
}

使用map插件,你可以將它map到view model上(沒任何問題):

var viewModel = ko.mapping.fromJS(data);

現在,數據被更新成如下這樣:

var data = {
    name: 'Scott',
    children: [
        { id: 1, name: 'Alice' }
    ]
}

這裏發生了兩件事:name從Scot變成了Scott,children[0].name從Alicw變成了Alice。你可以用如下代碼更新view model:

ko.mapping.fromJS(data, viewModel);

於是,name像我們期望的一樣更新了,但是在children數組裏,子項Alicw被刪除而新項Alice被添加到數組裏。這不是我們所期望 的,我們期望的是隻是把name從Alicw更新成Alice,不是替換整個item項。發生的原因是,默認情況下mapping plugin插件只是簡單地比較數組裏的兩個對象是否相等。 因爲JavaScript裏{ id : 1, name : 'Alicw' }和{ id : 1, name : 'Alice' }是不相等的,所以它認爲喜歡將新項替換掉老項。

解決這個問題,你需要聲明一個key讓mapping插件使用,用來判斷一個對象是新對象還是舊對象。代碼如下:

var mapping = {
    'children': {
        key: function (data) {
            return ko.utils.unwrapObservable(data.id);
        }
    }
}

var viewModel = ko.mapping.fromJS(data, mapping);

這樣,每次map的時候,mapping插件都會檢查數組項的id屬性來判斷這個數組項是需要合併的還是全新replace的。

 

用法2:用create自定義對象的構造器

如果你想自己控制mapping,你也可以使用create回調。使用回調可以讓你自己控制mapping。

舉例,你有一個像這樣的JavaScript對象:

var data = {
    name: 'Graham',
    children: [
        { id: 1, name: 'Lisa' }
    ]
}

如果你想自己map children 數組,你可以這樣聲明:

var mapping = {
    'children': {
        create: function (options) {
            return new myChildModel(options.data);
        }
    }
}

var viewModel = ko.mapping.fromJS(data, mapping);

支持create回調的options參數是一個JavaScript對象,包含如下:

data: JavaScript對象,包含child用到的數據

parent:child對象所屬的父對象或者數組

 當然,在內部的create回調裏,你也可以再次調用ko.mapping.fromJS。一個例子就是:如果你想讓初始的JavaScript對象帶有額外的依賴屬性dependent observables:

var myChildModel = function (data) {
    ko.mapping.fromJS(data, {}, this);

    this.nameLength = ko.dependentObservable(function () {
        return this.name().length;
    }, this);
}

 

用法3:用update自定義對象的updating

你也可以使用update 回調來自定義一個對象如何更新。它接受一個需要替代的對象以及和create 回調一樣的options參數,你應該return更新後的值。

update 回調使用的options參數是一個JavaScript對象,包含如下內容:

data:JavaScript對象,包含child用到的數據

parent:child對象所屬的父對象或者數組

observable:如果屬性是observable的,這將會寫入到實際的observable裏

 例子,在數據顯示之前,在新數據後面附加額外的字符串:

var data = {
    name: 'Graham',
}

var mapping = {
    'name': {
        update: function(options) {
            return options.data + 'foo!';
        }
    }
}

var viewModel = ko.mapping.fromJS(data, mapping);
alert(viewModel.name());

alert的結果是:Grahamfoo!。

 

用法4:使用ignore忽略不需要map的屬性

如果在map的時候,你想忽略一些屬性,你可以使用ignore累聲明需要忽略的屬性名稱集合:

var mapping = {
    'ignore': ["propertyToIgnore", "alsoIgnoreThis"]
}

var viewModel = ko.mapping.fromJS(data, mapping);

你聲明的忽略數組被編譯到默認的ignore數組裏。你可以像下面代碼一樣來維護它:

var oldOptions = ko.mapping.defaultOptions().ignore;
ko.mapping.defaultOptions().ignore = ["alwaysIgnoreThis"];

 

用法5:使用include聲明需要map的屬性

默認情況下,當map你的view model回到JS對象是時候,只map原始view model裏擁有的屬性(除了例外的_destroy屬性),不過,你可以使用include參數來定製:

var mapping = {
    'include': ["propertyToInclude", "alsoIncludeThis"]
}

var viewModel = ko.mapping.fromJS(data, mapping);

你聲明的include數組被編譯到默認的include數組裏,默認只有_destroy。 你可以像這樣來維護:

var oldOptions = ko.mapping.defaultOptions().include;
ko.mapping.defaultOptions().include = ["alwaysIncludeThis"];

 

用法6:使用copy來複制屬性

默認情況下,map的時候是把所有的值都轉換成observable的,如果你只是想copy屬性值而不是替換成observable的,你可以將屬性名稱添加到copy數組:

var mapping = {
    'copy': ["propertyToCopy"]
}

var viewModel = ko.mapping.fromJS(data, mapping);

你聲明的copy數組被編譯到默認的copy數組裏,默認值是空。你可以像這樣來維護:

var oldOptions = ko.mapping.defaultOptions().copy;
ko.mapping.defaultOptions().copy = ["alwaysCopyThis"];

 

用法7:Specifying the update target

在上面的例子,如果你想再一個class內map,你可以使用第三個參數作爲操作的目標,例如:

ko.mapping.fromJS(data, {}, someObject); // overwrites properties on someObject

所以,如果你想map一個JavaScript對象到this上,你可以這樣聲明:

ko.mapping.fromJS(data, {}, this);

 

從多數據源map

你可以通過多次使用ko.mapping.fromJS 來將多個JS對象的數據源map到一起,例如:

var viewModel = ko.mapping.fromJS(alice, aliceMappingOptions);
ko.mapping.fromJS(bob, bobMappingOptions, viewModel);

你聲明的mapping選項option在每次調用的時候都會合並。

 

Map以後的observable數組

map插件map以後生產的observable數組,帶有幾個額外的函數來處理帶有keys的mapping:

mappedRemove

mappedRemoveAll

mappedDestroy

mappedDestroyAll

mappedIndexOf

 它們是和ko.observableArray裏的函數等價的,不同是他們通過key來處理對象。例如:

var obj = [
    { id: 1 },
    { id: 2 }
]

var result = ko.mapping.fromJS(obj, {
    key: function (item) {
        return ko.utils.unwrapObservable(item.id);
    }
});

result.mappedRemove({ id: 2 });

map過的observable數組,除了上面的函數還支持一個mappedCreate函數:

var newItem = result.mappedCreate({ id: 3 });

首先會檢查key(id=3)在數組裏是否存在(如果存在則拋出異常),然後,如果有create和 update回調的話會調用他們,最後創建一個新對象,並將新對象添加到數組然後返回該新對象。

Knockout應用開發指南 第八章:簡單應用舉例(1)

2011-11-30 09:04 by 湯姆大叔, 11697 閱讀, 5 評論, 收藏, 編輯

本章展示的4個例子主要是利用了Knockout的基本語法特性,讓大家感受到使用Kncokout的快感。

1   Hello world

這個例子裏,2個輸入框都被綁定到data model上的observable變量上。“full name”顯示的是一個dependent observable,它的值是前面2個輸入框的值合併一起的結果。

 

 無論哪個輸入框更新,都會看到“full name” 顯示結果都會自動更新。查看HTML源代碼可以看到我們不需要聲明onchange事件。Knockout知道什麼時候該更新UI。

 

代碼: View

<p>First name: <input data-bind="value: firstName"/></p>
<p>Last name: <input data-bind="value: lastName"/></p>
<h2>Hello, <span data-bind="text: fullName"> </span>!</h2>

代碼: View model

// 這裏是聲明的view model

var viewModel = {
    firstName: ko.observable("Planet"),
    lastName: ko.observable("Earth")
};

viewModel.fullName = ko.dependentObservable(function () {
    // Knockout tracks dependencies automatically.
    //It knows that fullName depends on firstName and lastName,          
    //because these get called when evaluating fullName.
    return viewModel.firstName() + " " + viewModel.lastName();
});

ko.applyBindings(viewModel); // This makes Knockout get to work

 

2   Click counter

這個例子展示的創建一個view model並且綁定各種綁定到HTML元素標記上,以便展示和修改view model的狀態。

Knockout根據依賴項。在內部,hasClickedTooManyTimes在numberOfClicks上有個訂閱,以便當 numberOfClicks改變的時候,強制hasClickedTooManyTimes重新執行。相似的,UI界面上多個地方引用 hasClickedTooManyTimes,所以當hasClickedTooManyTimes 改變的時候,也講導致UI界面更新。

不需要手工聲明或者訂閱這些subscription訂閱,他們由KO框架自己創建和銷燬。參考如下代碼實現:

 

 

代碼: View

<div>You've clicked <span data-bind="text: numberOfClicks">&nbsp;</span> times</div>

<button data-bind="click: registerClick, enable: !hasClickedTooManyTimes()">Click me</button>

<div data-bind="visible: hasClickedTooManyTimes">
    That's too many clicks! Please stop before you wear out your fingers.
    <button data-bind="click: function() { numberOfClicks(0) }">Reset clicks</button>
</div>

代碼: View model

var clickCounterViewModel = function () {
    this.numberOfClicks = ko.observable(0);

    this.registerClick = function () {
        this.numberOfClicks(this.numberOfClicks() + 1);
    }

    this.hasClickedTooManyTimes = ko.dependentObservable(function () {
        return this.numberOfClicks() >= 3;
    }, this);
};

ko.applyBindings(new clickCounterViewModel());

 

3   Simple list

這個例子展示的是綁定到數組上。

注意到,只有當你在輸入框裏輸入一些值的時候,Add按鈕纔可用。參考下面的HTML代碼是如何使用enable 綁定。

 

 

代碼: View

<form data-bind="submit: addItem">
    New item:
    <input data-bind='value: itemToAdd, valueUpdate: "afterkeydown"' />
    <button type="submit" data-bind="enable: itemToAdd().length > 0">Add</button>
    <p>Your items:</p>
    <select multiple="multiple" width="50" data-bind="options: items"> </select>
</form>

代碼: View model

var viewModel = {};
viewModel.items = ko.observableArray(["Alpha", "Beta", "Gamma"]);
viewModel.itemToAdd = ko.observable("");
viewModel.addItem = function () {
    if (viewModel.itemToAdd() != "") {
        viewModel.items.push(viewModel.itemToAdd());
        // Adds the item. Writing to the "items" observableArray causes any associated UI to update.

        viewModel.itemToAdd("");                 
        // Clears the text box, because it's bound to the "itemToAdd" observable
    }
}

ko.applyBindings(viewModel);

 

4   Better list

這個例子是在上個例子的基礎上添加remove item功能(multi-selection)和排序功能。 “remove”和“sort”按鈕在不能用的時候會變成disabled狀態(例如,沒有足夠的item來排序)。

參考HTML代碼是如何實現這些功能的,另外這個例子也展示瞭如何使用匿名函數來實現排序。

 

 

代碼: View

<form data-bind="submit:addItem">
    Add item: <input type="text" data-bind='value:itemToAdd, valueUpdate: "afterkeydown"' />
    <button type="submit" data-bind="enable: itemToAdd().length > 0">Add</button>
</form>

<p>Your values:</p>
<select multiple="multiple" height="5" data-bind="options:allItems, selectedOptions:selectedItems"> </select>

<div>
    <button data-bind="click: removeSelected, enable: selectedItems().length > 0">Remove</button>
    <button data-bind="click: function() { allItems.sort() }, enable: allItems().length > 1">Sort</button>
</div>

代碼: View model

// In this example, betterListModel is a class, and the view model is an instance of it.

// See simpleList.html for an example of how to construct a view model without defining a class for it. Either technique works fine.

var betterListModel = function () {
    this.itemToAdd = new ko.observable("");
    this.allItems = new ko.observableArray(["Fries", "Eggs Benedict", "Ham", "Cheese"]);

// Initial items

this.selectedItems = new ko.observableArray(["Ham"]);                              

// Initial selection

    this.addItem = function () {
        if ((this.itemToAdd() != "") && (this.allItems.indexOf(this.itemToAdd()) < 0))
    // Prevent blanks and duplicates
        this.allItems.push(this.itemToAdd());
        this.itemToAdd(""); // Clear the text box
    }

    this.removeSelected = function () {
        this.allItems.removeAll(this.selectedItems());
        this.selectedItems([]); // Clear selection
    }
};

ko.applyBindings(new betterListModel());

 

Knockout應用開發指南 第八章:簡單應用舉例(2)

2011-12-01 09:47 by 湯姆大叔, 8437 閱讀, 8 評論, 收藏, 編輯

5   Control types

這個例子,對view model沒有什麼特殊的展示,只是展示如何綁定到各種元素上(例如,select, radio button等)。

 

代碼: View

View Code

 

代碼: View model

var viewModel = {
    stringValue: ko.observable("Hello"),
    passwordValue: ko.observable("mypass"),
    booleanValue: ko.observable(true),
    optionValues: ["Alpha", "Beta", "Gamma"],
    selectedOptionValue: ko.observable("Gamma"),
    multipleSelectedOptionValues: ko.observable(["Alpha"]),
    radioSelectedOptionValue: ko.observable("Beta")
};

ko.applyBindings(viewModel);

 

6   Templating

這個例子展示的render模板,以及在模板內部如何使用data binding屬性的。

Template很容易嵌套,當任何依賴數據改變的時候,Knockout會自動重新render模板。參考演示(啓用‘Show render times’),Knockout知道只需要重新render改變的那些數據所綁定的最近的模板。

 

代碼: View

<div data-bind='template: "peopleTemplate"'>
</div>
<label>
    <input type="checkbox" data-bind="checked: showRenderTimes"/>
    Show render times</label>
<script type="text/html" id="peopleTemplate">
<h2>People</h2>
<ul>
  `each people`
  <li>
  <div>
    ${ name } has <span data-bind="text: children().length">&nbsp;</span> children:
    <a href="#" data-bind="click: addChild ">Add child</a>
    <span class="renderTime" data-bind="visible: showRenderTimes">
                        (person rendered at <span data-bind="text: new Date().getSeconds()"></span>)
    </span>
  </div>
  <div data-bind='template: { name: "childrenTemplate", data: children }'></div>
  </li>
{{/each}}
</ul>
</script>
<script type="text/html" id="childrenTemplate">
    <ul>
        {{each $data}}
            <li>
                ${ this }
                <span class="renderTime" data-bind="visible: viewModel.showRenderTimes">
                    (child rendered at <span data-bind="text: new Date().getSeconds()"></span>)
               </span>
           </li>
        {{/each}}
</ul>
</script>

代碼: View model

// Define a "person" class that tracks its own name and children, and has a method to add a new child

var person = function (name, children) {
    this.name = name;
    this.children = ko.observableArray(children);

    this.addChild = function () {
        this.children.push("New child");
    } .bind(this);
}

// The view model is an abstract description of the state of the UI, but without any knowledge of the UI technology (HTML)

var viewModel = {
    people: [
        new person("Annabelle", ["Arnie", "Anders", "Apple"]),
        new person("Bertie", ["Boutros-Boutros", "Brianna", "Barbie", "Bee-bop"]),
        new person("Charles", ["Cayenne", "Cleopatra"])
    ],
    showRenderTimes: ko.observable(false)
};

ko.applyBindings(viewModel);

 

7   Paged grid

data-bind="..."綁定(像text, visible, 和click不是固定死的) - 你可以很容易自定義自己的綁定。如果你的自定義綁定僅僅是添加事件或者更新DOM元素的屬性,那幾行就可以實現。不過,你依然可以自定義可以重用的綁定 (或插件),就行本例的simpleGrid綁定。

如果一個插件需要自己標準的view model(例如本例的ko.simpleGrid.viewModel ),它提供既提供了該如何配置插件實例(分頁大小,列聲明)工作,也提供了view model上的屬性是否是observable 的(例如currentpage索引)。也可以擴展代碼讓這些屬性很容易地改變,並且讓UI自動更新。例如,“Jump to first page”按鈕的工作原理。

查看HTML源代碼可以看到非常容易使用這個simple grid插件。simpleGrid源碼地址是:http://knockoutjs.com/examples/resources/knockout.simpleGrid.js

 

代碼: View

<div data-bind="simpleGrid: gridViewModel"> </div>

<button data-bind='click: function() { items.push({ name: "New item", sales: 0, price: 100 }) }'>
    Add item 
</button>

<button data-bind="click: sortByName">
    Sort by name
</button>

<button data-bind="click: function() { gridViewModel.currentPageIndex(0) }">
    Jump to first page
</button>

代碼: View model

var myModel = {
    items: ko.observableArray([
        { name: "Well-Travelled Kitten", sales: 352, price: 75.95 },
        { name: "Speedy Coyote", sales: 89, price: 190.00 },
        { name: "Furious Lizard", sales: 152, price: 25.00 },
        { name: "Indifferent Monkey", sales: 1, price: 99.95 },
        { name: "Brooding Dragon", sales: 0, price: 6350 },
        { name: "Ingenious Tadpole", sales: 39450, price: 0.35 },
        { name: "Optimistic Snail", sales: 420, price: 1.50 }
    ]),

    sortByName: function () {
        this.items.sort(function (a, b) {
            return a.name < b.name ? -1 : 1;
        });
    }
};

myModel.gridViewModel = new ko.simpleGrid.viewModel({
    data: myModel.items,
    columns: [
        { headerText: "Item Name", rowText: "name" },
        { headerText: "Sales Count", rowText: "sales" },
        { headerText: "Price", rowText: function (item) { return "$" + item.price.toFixed(2) } }
    ],
    pageSize: 4
});

ko.applyBindings(myModel);

 

8   Animated transitions

該例子展示了2種方式實現動畫過渡效果:

當使用template/foreach綁定的時候,你可以使用afterAdd和beforeRemove回調函數,他們可以讓你寫代碼真實操作 添加和刪除元素,這樣你就可以使用像jQuery的 slideUp/slideDown()這樣的動畫效果。在planet types之間切換或添加新的planet可以看到效果。

通過observable 類型的值,我們不難定義自己的Knockout綁定,查看HTML源代碼可以看到一個自定義綁定fadeVisible,不管什麼時候它改變了, jQuery就會在相關的元素上執行fadeIn/fadeOut動畫效果。點擊“advanced options” checkbox 可以看到效果。

 

 

 

代碼: View

<h2>Planets</h2>
<p>
    <label>
        <input type="checkbox" data-bind="checked: displayAdvancedOptions"/>
        Display advanced options
    </label>
</p>
<p data-bind="fadeVisible: displayAdvancedOptions">
    Show:
    <label><input type="radio" value="all" data-bind="checked: typeToShow"/>All</label>
    <label><input type="radio" value="rock" data-bind="checked: typeToShow"/>Rocky planets</label>
    <label><input type="radio" value="gasgiant" data-bind="checked: typeToShow"/>Gas giants</label>
</p>
<div data-bind='template: { name: "planetsTemplate",
                            foreach: planetsToShow,
                            beforeRemove: function(elem) { $(elem).slideUp(function() { $(elem).remove(); }) },
                            afterAdd: function(elem) { $(elem).hide().slideDown() } }'>
</div>
<script type="text/html" id="planetsTemplate">
    <div class="planet ${ type }">${ name }</div>
</script>
<p data-bind="fadeVisible: displayAdvancedOptions">
    <button data-bind='click: function() { addPlanet("rock") }'>
        Add rocky planet</button>
    <button data-bind='click: function() { addPlanet("gasgiant") }'>
        Add gas giant</button>
</p>

代碼: View model

var viewModel = {
    planets: ko.observableArray([
        { name: "Mercury", type: "rock" },
        { name: "Venus", type: "rock" },
        { name: "Earth", type: "rock" },
        { name: "Mars", type: "rock" },
        { name: "Jupiter", type: "gasgiant" },
        { name: "Saturn", type: "gasgiant" },
        { name: "Uranus", type: "gasgiant" },
        { name: "Neptune", type: "gasgiant" },
        { name: "Pluto", type: "rock" }
    ]),

    typeToShow: ko.observable("all"),
    displayAdvancedOptions: ko.observable(false),

    addPlanet: function (type) { this.planets.push({ name: "New planet", type: type }); }
};

viewModel.planetsToShow = ko.dependentObservable(function () {
    // Represents a filtered list of planets
    // i.e., only those matching the "typeToShow" condition

    var desiredType = this.typeToShow();

    if (desiredType == "all")
        return this.planets();

    return ko.utils.arrayFilter(this.planets(), function (planet) {
        return planet.type == desiredType;
    });
} .bind(viewModel));

// Here's a custom Knockout binding that makes elements shown/hidden via jQuery's fadeIn()/fadeOut() methods
// Could be stored in a separate utility library

ko.bindingHandlers.fadeVisible = {
    init: function (element, valueAccessor) {
        // Initially set the element to be instantly visible/hidden depending on the value
        var value = valueAccessor();

        $(element).toggle(ko.utils.unwrapObservable(value));
        // Use "unwrapObservable" so we can handle values that may or may not be observable
    },

    update: function (element, valueAccessor) {
        // Whenever the value subsequently changes, slowly fade the element in or out
        var value = valueAccessor();
        ko.utils.unwrapObservable(value) ? $(element).fadeIn() : $(element).fadeOut();
    }
};

ko.applyBindings(viewModel);

 

Knockout應用開發指南 第九章:高級應用舉例

2011-12-02 15:04 by 湯姆大叔, 12079 閱讀, 9 評論, 收藏, 編輯

1   Contacts editor

這個例子和微軟爲演示jQuery Data Linking Proposal例子提供的例子一樣的提供的,我們可以看看Knockout實現是難了還是容易了。

代碼量的多少不重要(儘快Knockout 的實現很簡潔),重要的看起來是否容易理解且可讀。查看HTML源代碼,看看如何實現的view model以及綁定的。

 

 

代碼: View

View Code

 

代碼: View model

View Code

 

2   Editable grid

該例是使用“foreach”綁定爲數組裏的每一項來render到 template上。好處(相對於模板內部使用for循環)是當你添加或者刪除item項的時候,Knockout不需要重新render – 只需要render新的item項。就是說UI上其它控件的狀態(比如驗證狀態)不會丟失。

如何一步一步構建這個例子並集成ASP.NET MVC,請參閱此貼

 

 

代碼: View

View Code

 

代碼: View model

View Code

 

3   Shopping cart screen

這個例子展示的是依賴監控屬性(dependent observable)怎麼樣鏈在一起。每個cart對象都有一個dependentObservable對象去計算自己的subtotal,這些又被一 個進一步的dependentObservable對象依賴計算總的價格。當改變數據的時候,整個鏈上的依賴監控屬性都會改變,所有相關的UI元素也會被 更新。

這個例子也展示瞭如何創建聯動的下拉菜單。

 

 

代碼: View

View Code

 

代碼: View model

View Code

 

4   Twitter client

這是一個複雜的例子,展示了幾乎所有Knockout特性來構建一個富客戶端。

用戶數據存在一個JavaScript模型裏,通過模板來展示。就是說我們可以通過清理用戶列表裏的數據來達到隱藏用戶信息的目的,而不需要手動去隱藏DOM元素。

按鈕將根據他們是否可操作來自動變成enabled或disabled狀態。例如,有一個叫hasUnsavedChanges的依賴監控屬性(dependentObservable)控制這“Save”按鈕的enabled狀態。

可以非常方便地從外部JSON服務獲取數據,並集成到view model裏,然後顯示在頁面上。

 

 

代碼: View

 

 

<div class="loadingIndicator">
    Loading...</div>
<div class="configuration">
    <div class="listChooser">
        <button data-bind='click: deleteList, enable: editingList.name'>
            Delete</button>
        <button data-bind='click: saveChanges, enable: hasUnsavedChanges'>
            Save</button>
        <select data-bind='options: savedLists, optionsValue: "name", value: editingList.name'>
        </select>
    </div>
    <p>
        Currently viewing <span data-bind="text: editingList.userNames().length">&nbsp;</span>
        user(s):</p>
    <div class="currentUsers" data-bind='template: { name: "usersTemplate", data: editingList }'>
    </div>
    <form data-bind="submit: addUser">
    <label>
        Add user:</label>
    <input data-bind='value: userNameToAdd, valueUpdate: "keyup", css: { invalid: !userNameToAddIsValid() }' />
    <button type="submit" data-bind='enable: userNameToAddIsValid() && userNameToAdd() != ""'>
        Add</button>
    </form>
</div>
<div class="tweets" data-bind='template: { name: "tweetsTemplate", data: currentTweets }'>
</div>
<script type="text/html" id="tweetsTemplate">
    <table width="100%">
        {{each $data}}
            <tr>
                <td><img src="${ profile_image_url }"/></td>
                <td>
                    <a class="twitterUser" href="http://twitter.com/${ from_user }">${ from_user }</a>
                    ${ text }
                    <div class="tweetInfo">${ created_at }</div>
</td>
</tr>
        {{/each}}
</table>
</script>
<script type="text/html" id="usersTemplate">
    <ul>
        {{each(i, userName) userNames()}}
            <li><button data-bind="click: function() { userNames.remove(userName) }">Remove</button> <div>${ userName }</div></li>
        {{/each}}
</ul>
</script>

 

 

代碼: View model

 

 

// The view model holds all the state we're working with. It also has methods that can edit it, and it uses
// dependentObservables to compute more state in terms of the underlying data
// --
// The view (i.e., the HTML UI) binds to this using data-bind attributes, so it always stays up-to-date with
// the view model, even though the view model does not know or care about any view that binds to it

var viewModel = {
    savedLists: ko.observableArray([
        { name: "Celebrities", userNames: ['JohnCleese', 'MCHammer', 'StephenFry', 'algore', 'StevenSanderson'] },
        { name: "Microsoft people", userNames: ['BillGates', 'shanselman', 'haacked', 'ScottGu'] },
        { name: "Tech pundits", userNames: ['Scobleizer', 'LeoLaporte', 'techcrunch', 'BoingBoing', 'timoreilly', 'codinghorror'] }
    ]),

    editingList: {
        name: ko.observable("Tech pundits"),
        userNames: ko.observableArray()
    },

    userNameToAdd: ko.observable(""),
    currentTweets: ko.observableArray([])
};

viewModel.findSavedList = function (name) {
    var lists = this.savedLists();

    for (var i = 0; i < lists.length; i++)
        if (lists[i].name === name)
            return lists[i];
};

// Methods
viewModel.addUser = function () {
    if (this.userNameToAdd() && this.userNameToAddIsValid()) {
        this.editingList.userNames.push(this.userNameToAdd());
        this.userNameToAdd("");
    }
}

viewModel.saveChanges = function () {
    var saveAs = prompt("Save as", this.editingList.name());

    if (saveAs) {
        var dataToSave = this.editingList.userNames().slice(0);
        var existingSavedList = this.findSavedList(saveAs);
        if (existingSavedList)
            existingSavedList.userNames = dataToSave; // Overwrite existing list
        else
            this.savedLists.push({ name: saveAs, userNames: dataToSave }); // Add new list

        this.editingList.name(saveAs);
    }
}

viewModel.deleteList = function () {
    var nameToDelete = this.editingList.name();
    var savedListsExceptOneToDelete = $.grep(this.savedLists(), function (list) { return list.name != nameToDelete });
    this.editingList.name(savedListsExceptOneToDelete.length == 0 ? null : savedListsExceptOneToDelete[0].name);
    this.savedLists(savedListsExceptOneToDelete);
};

ko.dependentObservable(function () {
    // Observe viewModel.editingList.name(), so when it changes (i.e., user selects a different list) we know to copy the saved list into the editing list
    var savedList = viewModel.findSavedList(viewModel.editingList.name());

    if (savedList) {
        var userNamesCopy = savedList.userNames.slice(0);
        viewModel.editingList.userNames(userNamesCopy);
    } else
        viewModel.editingList.userNames([]);
});

viewModel.hasUnsavedChanges = ko.dependentObservable(function () {
    if (!this.editingList.name())
        return this.editingList.userNames().length > 0;

    var savedData = this.findSavedList(this.editingList.name()).userNames;
    var editingData = this.editingList.userNames();
    return savedData.join("|") != editingData.join("|");
}, viewModel);

viewModel.userNameToAddIsValid = ko.dependentObservable(function () {
    return (this.userNameToAdd() == "") || (this.userNameToAdd().match(/^\s*[a-zA-Z0-9_]{1,15}\s*$/) != null);
}, viewModel);

// The active user tweets are (asynchronously) computed from editingList.userNames
ko.dependentObservable(function () {
    twitterApi.getTweetsForUsers(this.editingList.userNames(), function (data) { viewModel.currentTweets(data) })
}, viewModel);

ko.applyBindings(viewModel);

// Using jQuery for Ajax loading indicator - nothing to do with Knockout
$(".loadingIndicator").ajaxStart(function () { $(this).fadeIn(); })
                      .ajaxComplete(function () { $(this).fadeOut(); });

 

Knockout應用開發指南 第十章:更多信息(完結篇)

2011-12-05 09:16 by 湯姆大叔, 7762 閱讀, 2 評論, 收藏, 編輯

1   瀏覽器支持

Knockout在如下瀏覽器通過測試:

Mozilla Firefox 2.0+(最新測試版本:3.6.8)

Google Chrome(通過Windows and Mac 下的version 5測試;其它低版本也該可以工作)

Microsoft Internet Explorer 6, 7, 8

Apple Safari(Windows下的Safari 5測試,Mac OS X下的 Safari 3.1.2測試,以及iPhone下的Safari for iOS 4測試;高低版本應該都可以工作)

Opera 10 for Windows

Knockout應該在以上這個瀏覽器的各版本上工作,但是由於太多版本,沒有逐一測試。最新測試結果顯示, Knockout在如下瀏覽器也是可以工作的(儘管沒有對每個版本逐一測試):

    Opera Mini

    Google Android OS browser (OS version 2.2)

測試Knockout能否在一個新平臺或瀏覽器下工作,只需要下載源代碼,然後在該瀏覽器裏運行裏面的/spec/runner.html文件即可測試。這個文件可以驗證超過100個行爲特性,如果有問題則會生成報表。上述瀏覽器的測試結果都應該是100%通過。

2      尋求幫助

有任何問題,你可以在Google group進去尋求幫助。

地址:http://groups.google.com/group/knockoutjs

3      更多教程和例子

這裏有更多使用Knockout和其它相關技術的頁面和例子:

    Knock Me Out — Ryan Niemeyer的博客,包括KnockoutJS和相關技術的很多好創意、想法和討論

    Editing a variable-length list, Knockout-style — Steve Sanderson展示的在ASP.NET MVC下使用Knockout的好處

    Knockout+WebSockets — Carl Hrberg使用Knockout,Sinatra,SQLite和WebSockets實現的實時討論版

    Knockout – quick asp.net mvc sample — Steve Gentile提供的另外一篇博客, 關於ASP.NET MVC 下如何使用Knockout

    Log4Play: Log4j Live Streaming with Play Framework, Knockout.js and WebSockets — Felipe Oliveira關於使用KO和WebSockets創建實時server log功能

    Wiki – 攻略 — 網友貢獻的攻略和例子

    Wiki – 插件 — 網友貢獻的各種插件列表

 

此文列表正在收集中,如果你想讓你的帖子連接放在這裏,請告訴我。

用Javascript評估用戶輸入密碼的強度(Knockout版)

2011-11-27 10:40 by 湯姆大叔, 8090 閱讀, 10 評論, 收藏, 編輯

早上看到博友6點多發的一篇關於密碼強度的文章(連接),甚是感動(週末大早上還來發文)。

我們來看看如果使用Knockout更簡單的來實現密碼強度的驗證。

原有代碼請查看:

 

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
</head>
<body>
    <script type="text/javascript">
        //CharMode函數
function CharMode(iN) {
            if (iN >=48&& iN <=57) //數字
return1;
            if (iN >=65&& iN <=90) //大寫字母
return2;
            if (iN >=97&& iN <=122) //小寫
return4;
            else
                return8; //特殊字符
        }

        //bitTotal函數
function bitTotal(num) {
            modes =0;
            for (i =0; i <4; i++) {
                if (num &1) modes++;
                num >>>=1;
            }
            return modes;
        }

        //checkStrong函數
function checkStrong(sPW) {
            if (sPW.length <=4)
                return0; //密碼太短
            Modes =0;
            for (i =0; i < sPW.length; i++) {
                Modes |= CharMode(sPW.charCodeAt(i));
            }
            return bitTotal(Modes);
        }


        //pwStrength函數
function pwStrength(pwd) {
            O_color ="#eeeeee";
            L_color ="#FF0000";
            M_color ="#FF9900";
            H_color ="#33CC00";
            if (pwd ==null|| pwd =='') {
                Lcolor = Mcolor = Hcolor = O_color;
            } else {
                S_level = checkStrong(pwd);
                switch (S_level) {
                    case0:
                        Lcolor = Mcolor = Hcolor = O_color;
                    case1:
                        Lcolor = L_color;
                        Mcolor = Hcolor = O_color;
                        break;
                    case2:
                        Lcolor = Mcolor = M_color;
                        Hcolor = O_color;
                        break;
                    default:
                        Lcolor = Mcolor = Hcolor = H_color;
                }

                document.getElementById("strength_L").style.background = Lcolor;
                document.getElementById("strength_M").style.background = Mcolor;
                document.getElementById("strength_H").style.background = Hcolor;
                return;
            }
        } </script>
    <form name="form1" action="">
    輸入密碼:<input type="password" size="10" onkeyup="pwStrength(this.value)" onblur="pwStrength(this.value)">
    <br>
    密碼強度:
    <table width="217" border="1" cellspacing="0" cellpadding="1" bordercolor="#cccccc"
        height="23" style='display: inline'>
        <tr align="center" bgcolor="#eeeeee">
            <td width="33%" id="strength_L">
                弱
            </td>
            <td width="33%" id="strength_M">
                中
            </td>
            <td width="33%" id="strength_H">
                強
            </td>
        </tr>
    </table>
    </form>
</body>
</html>

 

 

首先我們來改善一下上面博友的驗證函數爲如下代碼:

var Page = Page || {};
Page.Utility = Page.Utility || {};
Page.Utility.Registration = Page.Utility.Registration || {};

//獲取密碼強度
Page.Utility.Registration.getPasswordLevel = function (password) {
    if (password == null || password == '')
        return 0;

    if (password.length <= 4)
        return 0; //密碼太短

    var Modes = 0;
    for (i = 0; i < password.length; i++) {
        Modes |= CharMode(password.charCodeAt(i));
    }
    return bitTotal(Modes);

    //CharMode函數
    function CharMode(iN) {
        if (iN >= 48 && iN <= 57) //數字
            return 1;
        if (iN >= 65 && iN <= 90) //大寫字母
            return 2;
        if (iN >= 97 && iN <= 122) //小寫
            return 4;
        else
            return 8; //特殊字符
    }

    //bitTotal函數
    function bitTotal(num) {
        modes = 0;
        for (i = 0; i < 4; i++) {
            if (num & 1) modes++;
            num >>>= 1;
        }
        return modes;
    }
};

 

然後來創建View Model,但是引用Knockout之前,我們首先要引用Knockout的Js類庫(具體介紹請查看Knockout應用開發指南的系列教程)
View model代碼如下:

var viewModel = {
    Password: ko.observable(""),
    Ocolor: "#eeeeee"
};

對於密碼強度以及顏色的值依賴於密碼字符串的值,所以我們需要爲他們聲明依賴屬性,代碼如下:

viewModel.PasswordLevel = ko.dependentObservable(function () {
    return Page.Utility.Registration.getPasswordLevel(this.Password());
}, viewModel);

viewModel.Lcolor = ko.dependentObservable(function () {
    //根據密碼強度判斷第一個格顯示的背景色
    return this.PasswordLevel() == 0 ? this.Ocolor : (this.PasswordLevel() == 1 ? "#FF0000" : (this.PasswordLevel() == 2 ? "#FF9900" : "#33CC00"))
}, viewModel);

viewModel.Mcolor = ko.dependentObservable(function () {
    //根據密碼強度判斷第二個格顯示的背景色
    return this.PasswordLevel() < 2 ? this.Ocolor : (this.PasswordLevel() == 2 ? "#FF9900" : "#33CC00")
}, viewModel);

viewModel.Hcolor = ko.dependentObservable(function () {
    //根據密碼強度判斷第三個格顯示的背景色
    return this.PasswordLevel() < 3 ? this.Ocolor : "#33CC00"
}, viewModel);

然後使用applyBindings方法將view model綁定到該頁面,你可以使用jQuery的ready函數來執行該綁定代碼,也可以在頁面最下方執行綁定代碼,我們這裏使用了jQuery,代碼如下:

$((function () {
    ko.applyBindings(viewModel);
}));

 

最後,我們再看看這些值怎麼動態綁定到HTML元素上的,請查看如下代碼(其中使用了afterkeydown代替了onKeyUp和onBlur):

<form name="form1" action="">
輸入密碼:
<input type="text" size="10" data-bind="value:Password, valueUpdate: 'afterkeydown'">
<br>
密碼強度:
<table width="217" border="1" cellspacing="0" cellpadding="1" bordercolor="#cccccc"
    height="23" style='display: inline'>
    <tr align="center" bgcolor="#eeeeee">
        <td width="50"data-bind="style: { backgroundColor: Lcolor }">弱</td>
        <td width="50"data-bind="style: { backgroundColor: Mcolor }">中</td>
        <td width="50"data-bind="style: { backgroundColor: Hcolor }">強</td>
    </tr>
</table>
</form>

然後就OK,運行代碼查看,一模一樣的功能展示出來了。

如果去掉爲驗證而改善的代碼,總代碼肯定是比原有的方式少的。

 

完整版代碼如下:

 

 

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
    <script type="text/javascript" src="http://knockoutjs.com/js/jquery-1.4.2.min.js"></script>
    <script type="text/javascript" src="http://knockoutjs.com/js/jquery.tmpl.js"></script>
    <script type="text/javascript" src="http://knockoutjs.com/js/knockout-1.2.1.js"></script>
</head>
<body>
    <script type="text/javascript">
        var Page = Page || {};
        Page.Utility = Page.Utility || {};
        Page.Utility.Registration = Page.Utility.Registration || {};

        //獲取密碼強度
        Page.Utility.Registration.getPasswordLevel =function (password) {
            if (password ==null|| password =='')
                return0;

            if (password.length <=4)
                return0; //密碼太短

            var Modes =0;
            for (i =0; i < password.length; i++) {
                Modes |= CharMode(password.charCodeAt(i));
            }
            return bitTotal(Modes);

            //CharMode函數
function CharMode(iN) {
                if (iN >=48&& iN <=57) //數字
return1;
                if (iN >=65&& iN <=90) //大寫字母
return2;
                if (iN >=97&& iN <=122) //小寫
return4;
                else
                    return8; //特殊字符
            }

            //bitTotal函數
function bitTotal(num) {
                modes =0;
                for (i =0; i <4; i++) {
                    if (num &1) modes++;
                    num >>>=1;
                }
                return modes;
            }
        };

        var viewModel = {
            Password: ko.observable(""),
            Ocolor: "#eeeeee"
        };

        viewModel.PasswordLevel = ko.dependentObservable(function () {
            return Page.Utility.Registration.getPasswordLevel(this.Password());
        }, viewModel);

        viewModel.Lcolor = ko.dependentObservable(function () {
            //根據密碼強度判斷第一個格顯示的背景色
returnthis.PasswordLevel() ==0?this.Ocolor : (this.PasswordLevel() ==1?"#FF0000" : (this.PasswordLevel() ==2?"#FF9900" : "#33CC00"))
        }, viewModel);

        viewModel.Mcolor = ko.dependentObservable(function () {
            //根據密碼強度判斷第二個格顯示的背景色
returnthis.PasswordLevel() <2?this.Ocolor : (this.PasswordLevel() ==2?"#FF9900" : "#33CC00")
        }, viewModel);

        viewModel.Hcolor = ko.dependentObservable(function () {
            //根據密碼強度判斷第二個格顯示的背景色
returnthis.PasswordLevel() <3?this.Ocolor : "#33CC00"
        }, viewModel);

        $((function () {
            ko.applyBindings(viewModel);
        }));

      
    </script>
    <form name="form1" action="">
    輸入密碼:<input type="text" size="10" data-bind="value:Password, valueUpdate: 'afterkeydown'">
    <br>
    密碼強度:
    <table width="217" border="1" cellspacing="0" cellpadding="1" bordercolor="#cccccc"
        height="23" style='display: inline'>
        <tr align="center" bgcolor="#eeeeee">
            <td width="50" id="strength_L" data-bind="style: { backgroundColor: Lcolor }">
                弱
            </td>
            <td width="50" id="strength_M" data-bind="style: { backgroundColor: Mcolor }">
                中
            </td>
            <td width="50" id="strength_H" data-bind="style: { backgroundColor: Hcolor }">
                強
            </td>
        </tr>
    </table>
    </form>
</body>
</html>

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章