翻译: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 函数来执行。

 
分类: javascript

knockoutJS 快速上手的更多相关文章

  1. 翻译:Knockout 快速上手 - 3: knockoutJS 快速上手

    许多时候,学会一种技术的有效方式就是使用它解决实际中的问题.在这一节,我们将学习使用 Knockout 来创建一个常见的应用,库存管理应用. 应用概览 在创建我们的应用之前,我们需要一个公司,来理解应 ...

  2. 【Python五篇慢慢弹】快速上手学python

    快速上手学python 作者:白宁超 2016年10月4日19:59:39 摘要:python语言俨然不算新技术,七八年前甚至更早已有很多人研习,只是没有现在流行罢了.之所以当下如此盛行,我想肯定是多 ...

  3. 快速上手Unity原生Json库

    现在新版的Unity(印象中是从5.3开始)已经提供了原生的Json库,以前一直使用LitJson,研究了一下Unity用的JsonUtility工具类的使用,发现使用还挺方便的,所以打算把项目中的J ...

  4. [译]:Xamarin.Android开发入门——Hello,Android Multiscreen快速上手

    原文链接:Hello, Android Multiscreen Quickstart. 译文链接:Hello,Android Multiscreen快速上手 本部分介绍利用Xamarin.Androi ...

  5. [译]:Xamarin.Android开发入门——Hello,Android快速上手

    返回索引目录 原文链接:Hello, Android_Quickstart. 译文链接:Xamarin.Android开发入门--Hello,Android快速上手 本部分介绍利用Xamarin开发A ...

  6. 快速上手seajs——简单易用Seajs

    快速上手seajs——简单易用Seajs   原文  http://www.cnblogs.com/xjchenhao/p/4021775.html 主题 SeaJS 简易手册 http://yslo ...

  7. Git版本控制Windows版快速上手

    说到版本控制,之前用过VSS,SVN,Git接触不久,感觉用着还行.写篇博文给大家分享一下使用Git的小经验,让大家对Git快速上手. 说白了Git就是一个控制版本的工具,其实没想象中的那么复杂,咱在 ...

  8. Objective-C快速上手

    最近在开发iOS程序,这篇博文的内容是刚学习Objective-C时做的笔记,力图达到用最短的时间了解OC并使用OC.Objective-C是OS X 和 iOS平台上面的主要编程语言,它是C语言的超 ...

  9. Netron开发快速上手(二):Netron序列化

    Netron是一个C#开源图形库,可以帮助开发人员开发出类似Visio的作图软件.本文继前文”Netron开发快速上手(一)“讨论如何利用Netron里的序列化功能快速保存自己开发的图形对象. 一个用 ...

随机推荐

  1. Do a “git export” (like “svn export”)?(转)

    Probably the simplest way to achieve this is with git archive. If you really need just the expanded ...

  2. Unity3D的SerializeField 序列化域名

    SerializeField Inherits from Attribute Force Unity to serialize a private field. 强制Unity去序列化一个私有域. Y ...

  3. ashx的学习

    原文:ashx的学习 嘿嘿,今天我们休息,本来是想总结一下前两周学习的javascript和jquery,但是感觉好困哦,就没有认真地学习啦,于是做了一个小小的练习,刚开始学习html使用在项目中还是 ...

  4. 新版live555代理server

    好久没搞流媒体了,近期又回归了,已经把live555代理服务器更新到最新的live555代码(V0.82). 改进了一大坨问题,还去掉了一个类,代码更精简了. 改进了命令行參数格式,仅仅要这样:rts ...

  5. exit() _exit()

    图 C程序的启动与终止 差别: _exit()函数:直接使进程停止执行,清除其使用的内存空间,并销毁其在内核中的各种数据结构; exit()函 数则在这些基础上作了一些包装,在运行退出之前加了若干道工 ...

  6. HDU1342 Lotto 【深搜】

    Lotto Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Subm ...

  7. Ubuntu 14.04 关机键无效解决方法

         这几天開始研究ubuntu 14.04软件,安装Cairo-Dock后发现右上角的关机.重新启动.注销菜单点击都没了反应仅仅能通过命令实现,后来经过研究,发现仅仅要设置了 Cairo-Doc ...

  8. 一张地图,告诉你NodeJS命令行调试器语句

    NodeJS提供脚本调试. 进入node debug xx.js您可以进入调试模式. 版权声明:本文博客原创文章,博客,未经同意,不得转载.

  9. 如何查看IC卡燃气表读数和剩余量?

    如今新建的楼房都是使用IC卡燃气表,拿到房子入住时也没有见到IC卡燃气表的使用说明书.非常多人可能为此而苦恼.这里就讲一下怎样查看IC卡燃气表读数及剩余金额. 产品外观 可选功能 watermark/ ...

  10. C#修改用户名

    string strCmdText; strCmdText = "useraccount where name='" + 旧密码 + "' rename " + ...