• 翻译:Knockout 快速上手


    许多时候,学会一种技术的有效方式就是使用它解决实际中的问题。在这一节,我们将学习使用 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- 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 函数来执行。

  • 相关阅读:
    JQuery的Dom操作
    JQuer的简单应用
    JSBom联合Dom的应用
    Bom—浏览器对象模型
    正则表达式(其实就是预习)
    Js关于表单的事件应用
    JavaScript事件练习
    微信小程序实现微信登录
    Azure 数据资源管理器 -- 当 ADX 遇上 ML
    多快好省 -- Azure VMSS AI 推理篇
  • 原文地址:https://www.cnblogs.com/haogj/p/3444504.html
Copyright © 2020-2023  润新知