翻譯:Knockout 快速上手 - 3: knockoutJS 快速上手


許多時候,學會一種技術的有效方式就是使用它解決實際中的問題。在這一節,我們將學習使用 Knockout 來創建一個常見的應用,庫存管理應用。

應用概覽

在創建我們的應用之前,我們需要一個公司,來理解應用解決的問題。我們的應用將能夠完成下列任務:

  • 瀏覽公司銷售的每種產品,跟蹤 SKU 數量和說明。
  • 對每種產品的價格,費用和數量進行賦值。
  • 當公司決定銷售某種新產品的時候,可以創建新的產品。
  • 當公司停售某種產品的時候,可以刪除這種產品。

 

第一步 定義命名空間

在我們實際開始開發應用之前,很重要的一個問題就是規划我們如何組織我們的程序,將我們應用的代碼與瀏覽器界面和本地函數進行分離。你可能奇怪對於這么小的應用我們為什么要這么做。對於 JavaScript 應用的最佳實踐來說,這么做無論如何都是非常重要的。通過命名空間,即使對於一個很小的應用來說,在以后隨着應用的不斷擴展,也可以確保容易進行維護,並且與第三方的組件進行分隔。( 例如許多的腳本插件 )

我們將在前面創建的 app.js 中定義我們的命名空間。下面代碼就是定義定名空間的代碼。

// Define the namespace

window.myApp = {};

 

第二步 創建模型

我們創建的第一個模型將用來表示我們的產品對象。我們通過創建一個名為 Product.js 的文件來完成這個任務。文件的內容如下所示。

 

(function (myApp) {

    // Product Constructor Function

    function Product() {

        var self = this;

        // "SKU" property

        self.sku = ko.observable("");

        // "Description" property

        self.description = ko.observable("");

        // "Price" property

        self.price = ko.observable(0.00);

        // "Cost" property

        self.cost = ko.observable(0.00);

        // "Quantity" property

        self.quantity = ko.observable(0);

    }

    // add to our namespace

    myApp.Product = Product;

 

}(window.myApp));

 

在這段代碼中,我們定義了一個函數作為 Product 的構造器。如你所見,我們將這個函數定義在一個稱為立即執行的函數表達式中 ( IIFE )。我們為了如下的原因使用這個模式:

  • 這使得我們定義了一個 JavaScript 的作用域,防止污染全局命名空間 ( 像 window 和 document 所處的命名空間 )。這使得我們在調試的時候,不會在本地的函數,比如 windows 中看到和使用我們定義的 Product 函數。
  • 這使得我們可以創建私有的函數,在其他的代碼中禁止訪問。如果我們定義了 Product 函數之后,沒有將它添加到 myApp 命名空間中,就沒有代碼可以在 IIFE 之外訪問我們的 Product 構造器。這在創建復雜邏輯的時候非常理想,在某種程度上可以防止其它的對象訪問和重寫我們的邏輯。

在構造器函數內部,每個屬性都創建在 self 對象之上。self 對象是一個指向新創建的 Product 對象的引用。在 JavaScript 中,this 是一個關鍵字,但是程序員經常被它不同的含義所困惑。這使由於它可以表示多種不同的對象 ( 比如調用對象,全局對象等等 )。為了防止這個問題,我們創建一個局部變量 self ,這樣,我們就可以確信它總是表示我們當前的對象實例。

最后,每個屬性的值被賦予一個 Knockout 的 Observable 實例。Observable 是 Knockout 中創建可以在屬性發生變化的時候觸發事件的屬性的簡單方式 ( 這是 Knockout 中的一個核心概念,我們在后繼內容中還要深入討論 )。通過將屬性的初始值傳遞給這個函數,我們得到一個包裝了初始值的函數返回值。可以通過調用這個包裝函數來為屬性賦值和取值。下面的實例演示了如何使用我們的構造器和屬性。

// Usage

// create an instance of the Product class

var productA = new myApp.Product();

// "set" the 'sku' property

productA.sku('12345')

// "get" the 'sku' property value

var skuNumber = productA.sku();

 

第三步 創建模型使用的視圖

現在,我們已經定義了我們的模型類。我們需要創建一個視圖在屏幕上顯示模型,以便用戶可以看到我們的產品數據。我們將使用 HTML 來創建這個視圖。我們將使用很簡單的布局來顯示產品的信息。

<div id="productView">

    <p>

        SKU: <span data-bind="text: sku"></span>

    </p>

    <p>

        Description: <span data-bind="text: description"></span>

    </p>

    <p>

        Cost: <span data-bind="text: cost"></span>

    </p>

    <p>

        Price: <span data-bind="text: price"></span>

    </p>

    <p>

        Quantity: <span data-bind="text: quantity"></span>

    </p>

</div>

 

這里,我們使用 Knockout 的 text 綁定來顯示產品的信息。text 綁定將屬性的值轉化為 string 之后,設置 HTML 元素的 innerText 屬性 ( 通常使用 span 元素 )。

第四步 創建 ViewModel 管理模型

這里,我們將會需要創建業務邏輯,來處理創建產品,刪除產品來管理我們的產品列表。我們還需要某種數組來來管理我們的產品列表。因此,我們將建新的類來實現所有的功能、數組、對象以便綁定到用戶界面上。我們需要的類就是 ViewModel.

像我們現在創建應用一樣,剛開始的 ViewModel 我們僅僅定義一個屬性 selectedProduct。這個屬性表示我們當前顯示在屏幕上進行處理的單個產品,在 js 文件夾中添加一個名為 ProductsViewModel.js 的腳本文件,在其中添加如下代碼。

// Products ViewModel

(function (myApp) {

    // constructor function

    function ProductsViewModel() {

        var self = this;

        // the product that we want to view/edit

        self.selectedProduct = ko.observable();

    }

    // add our ViewModel to the public namespace

    myApp.ProductsViewModel = ProductsViewModel;

 

}(window.myApp));

 

第五步 使用 Observable 數組

我們公司的業務需要銷售多種產品,所以,我們需要保持一個當前產品的列表。在 JavaScript 中,管理和維護一個對象集合的數據結構就是數組。Knockout 更進一步,提供了一個名為 ObservableArray 的對象。后面我會進一步討論這個對象,這個對象在成員發生變化的時候,會拋出相應的事件通知,這就允許 Knockout 可以在 ObservableArray 發生變化的時候保持用戶界面和我們數據結構的同步。

Knockout 的 ObservableArray 與標准的 JavaScript 數組擁有相同的使用方式,包括 ( push, pop, slice, splice ) 等等。所以,如果你使用過 JavaScript 的 Array 話,使用起來非常自然和流暢。

為了創建公司產品的主列表,為們需要為我們的視圖模型添加一個新的屬性 productCollection 。

 

// the product that we want to view/edit

self.selectedProduct = ko.observable();

 

// the product collection

self.productCollection = ko.observableArray([]);

 

第六步 從 ObservableArray 中添加和刪除模型

現在,我們已經擁有了一個公司所有產品的列表,下面我們實現向這個列表添加產品和刪除產品的邏輯。

添加產品的邏輯仍然比較簡單,可以在這個過程中添加一些驗證和檢查。但是盡可能地簡單和清楚。

// creates a new product and sets it up

// for editing

self.addNewProduct = function () {

    // create a new instance of a Product

    var p = new myApp.Product();

    // set the selected Product to our new instance

    self.selectedProduct(p);

};

// logic that is called whenever a user is done editing

// a product or done adding a product

self.doneEditingProduct = function () {

    // get a reference to our currently selected product

    var p = self.selectedProduct();

    // ignore if it is null

    if (!p) {

        return;

    }

    // check to see that the product

    // doesn't already exist in our list

if (self.productCollection.indexOf(p) > -1) {

    self.selectedProduct(null);

        return;

    }

    // add the product to the collection

    self.productCollection.push(p);

    // clear out the selected product

    self.selectedProduct(null);

};

 

在這些代碼中,我們計划在用戶添加新的產品調用addNewProduct 的時候,使用新創建的 Product 對象填充我們當前選中的對象selectedProduct,然后可以開始進行編輯。在用戶完成編輯之后,調用doneEditingProduct 的時候,注意需要檢查selectedProduct 是否為空,不為空的話,將這個對象添加到產品列表中。

刪除產品的邏輯更加簡單一些,我們直接檢查selectedProduct 是否為空,如果不為空,就直接從列表中刪除它。

// logic that removes the selected product

// from the collection

self.removeProduct = function () {

    // get a reference to our currently selected product

    var p = self.selectedProduct();

    // ignore if it is null

    if (!p) {

        return;

    }

    // empty the selectedProduct

    self.selectedProduct(null);

    // simply remove the item from the collection

    return self.productCollection.remove(p);

};

 

最后,在用戶界面上,我們需要提供一些按鈕,用戶可以通過它們調用這些業務邏輯。我們添加按鈕,綁定按鈕的 click 事件到視圖模型的相關屬性上,如下所示:

<div id="content">

    <div id="productView" data-bind="with: selectedProduct">

        <p>

            SKU: <span data-bind="text: sku"></span>

        </p>

        <p>

            Description: <span data-bind="text: description"></span>

        </p>

        <p>

            Cost: <span data-bind="text: cost"></span>

        </p>

        <p>

            Price: <span data-bind="text: price"></span>

        </p>

        <p>

            Quantity: <span data-bind="text: quantity"></span>

        </p>

    </div>

    <div id="buttonContainer">

        <button type="button" data-bind="click: addNewProduct">Add</button>

        <button type="button" data-bind="click: removeProduct">Remove</button>

        <button type="button" data-bind="click: doneEditingProduct">Done</button>

    </div>

</div>

 

第七步 編輯模型的屬性

到現在為止,我們仍然沒有辦法編輯產品列表中每個產品的屬性。所以,需要修改我們的視圖以便實現雙向的綁定。Knockout 的 value 綁定可以幫助我們實現這個目的,但是只能在 input 元素上使用這個綁定。下面我們修改一下我們的視圖,如下所示:

<div id="productView">

    <form>

        <fieldset>

            <legend>Product Details</legend>

            <label>

                SKU:

                <input type="text" data-bind="value: sku" />

            </label>

            <br />

            <label>

                Description:

                <input type="text" data-bind="value: description" />

            </label>

            <br />

            <label>

                Cost:

                <input type="text" data-bind="value: cost" />

            </label>

            <br />

            <label>

                Price:

                <input type="text" data-bind="value: price" />

 

            </label>

            <br />

            <label>

                Quantity:

                <input type="text" data-bind="value: quantity" />

            </label>

        </fieldset>

    </form>

</div>

 

現在,我們基於表單的視圖可以支持編輯產品的屬性了。我將會提到這一點,我們需要添加一些輸入的驗證來保證 Cost 和 Price 中提供了正確的金額,還有Quantity 中是正確的整數。實際上這些問題有些超出了本教程的范圍,在互聯網上你可以找到很多實現這些功能的腳本庫。

第八步 創建主從視圖

終於,我們已經創建了管理數據的邏輯,以及通過 HTML 提供了一個非常友好的用戶界面,實現了管理公司產品的功能。讓我們繼續前進,為用戶創建一個好用的主從界面視圖。

首先,我們需要確認產品視圖正確綁定在我們選定的產品上,而且,產品視圖只有在選中產品實例之后,才會顯示出來。Knockout 提供了一個稱為  with 的綁定來實現這些功能。后面我們會詳細討論這些問題。但是 with 綁定不僅提供選中產品的 null 檢測,還實現了將綁定的上下文從 ProductViewModel 切換到 selectedProduct ( 這樣我們就可以在數據綁定的語法中直接引用這些屬性 )。

由於只有在我們選中一個產品的時候,Remove 和 Done 按鈕才是可見的,我們將為這兩個按鈕添加一個 visible 綁定,用來檢查 selectedProduct 屬性是否已經有值。也可以為 Add 按鈕做類似的工作,完成這些功能的代碼如下所示。

<div id="buttonContainer">

    <button type="button"

        data-bind="click: addNewProduct, visible: (selectedProduct() ? false : true)">Add</button>

    <button type="button"

        data-bind="click: removeProduct, visible: (selectedProduct() ? true : false)">Remove</button>

    <button type="button"

        data-bind="click: doneEditingProduct, visible: (selectedProduct() ? true : false)">Done</button>

</div>

 

最后,我們還需要提供一個顯示產品列表的視圖來方便用戶管理產品。通常是一個表格,列表等等。或者一些控件來實現這些功能。Knockout 足夠強大,我們可以直接使用原始的 HTML 來顯示產品列表 ProductCollection。

我們使用基本的 select 元素來實現基本的列表。Knockout 提供了一個 options綁定,支持我們將一個 ObservableArray 綁定到 select 元素。我們還將會提供第二個 Observable 綁定來保持視圖中選中的產品。為了達到這個目的,我們在 select 元素中使用 value 綁定來綁定到選中的項目,在視圖模型中,我們增加一個新的綁定屬性listViewSelectedItem,,下面的代碼演示了新建的屬性。屬性后面的 subscription 用來傳遞這個屬性的任何變化到我們的selectedProduct 屬性中。

// the product that we want to view/edit

self.selectedProduct = ko.observable();

 

// the product collection

self.productCollection = ko.observableArray([]);

 

// product list view selected item

self.listViewSelectedItem = ko.observable(null);

// push any changes in the list view to our

// main selectedProduct

 

self.listViewSelectedItem.subscribe(function (product) {

    if (product) {

        self.selectedProduct(product);

    }

});

 

我們的列表視圖實現如下所示:

<div id="productListView">

    <select id="productList" size="10" style="min-width: 120px;"

        data-bind="options: productCollection,  value: listViewSelectedItem, optionsText: 'sku'">

    </select>

</div>

 

在前面代碼中,使用了optionsText 綁定來綁定 ObservableArray 中每個元素的屬性,開始的時候,我們設置 Product 的 sku 屬性,但是我們如何能夠同時看到 sku 屬性和 description 屬性的值呢?我們可以通過 Computed Observable 來實現,很快我們就會討論這個特性,現在,我們在 Product 類中添加一個計算出 sku 屬性和 description 屬性的新屬性。

// Computed Observables

// simply combines the Sku and Description properties

self.skuAndDescription = ko.computed(function () {

    var sku = self.sku() || "";

    var description = self.description() || "";

    return sku + ": " + description;

});

 

在添加了skuAndDescription 屬性之后,應該更新一下產品列表視圖,可以將optionsText 屬性的值重新設置為skuAndDescription 來代替原來的 sku。

第九步 應用綁定

為了讓我們的應用能夠實際運行,我們需要啟動 Knockout 的綁定處理,我們需要確認在所有的腳本正確加載之后,在 ViewModel 初始化之后,執行綁定處理過程。我建議的方式是在 app.js 中如下處理。

// Define the namespace

window.myApp = {};

(function (myApp) {

    // constructor functio for App

    function App() {

        // core logic to run when all

        // dependencies are loaded

        this.run = function () {

            // create an instance of our ViewModel

            var vm = new myApp.ProductsViewModel();

            // tell Knockout to process our bindings

            ko.applyBindings(vm);

        }

    }

    // make sure its public

    myApp.App = App;

}(window.myApp));

 

在 app.js 中創建了初始化邏輯之后,我們需要創建 app 的實例,然后調用 run 方法,在頁面最后的位置添加如下的代碼。

<script type="text/javascript">

    var app = new myApp.App();

    app.run();

</script>

 

為了教學的目的,我將這段代碼放在頁面幾乎最后的位置,我們還有其他的方式可以使用,比如通過 jQuery 的 ready 函數來執行。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM