原文:Knockout应用开发指南 第九章:高级应用举例

1   Contacts editor

这个例子和微软为演示jQuery Data Linking Proposal例子提供的例子一样的提供的,我们可以看看Knockout实现是难了还是容易了。

代码量的多少不重要(尽快Knockout 的实现很简洁),重要的看起来是否容易理解且可读。查看HTML源代码,看看如何实现的view model以及绑定的。

代码: View

<h2>Contacts</h2>
<div id="contactsList" data-bind='template: "contactsListTemplate"'>
</div>
<script type="text/html" id="contactsListTemplate">
<table class='contactsEditor'>
<tr>
<th>First name</th>
<th>Last name</th>
<th>Phone numbers</th>
</tr>

{{each(i, contact) contacts()}}
<tr>
<td>
<input data-bind="value: firstName"/>
<div><a href="#" data-bind="click: function() { viewModel.removeContact(contact) }">Delete</a></div>
</td>
<td><input data-bind="value: lastName"/></td>
<td>
<table>
{{each(i, phone) phones}}
<tr>
<td><input data-bind="value: type"/></td>
<td><input data-bind="value: number"/></td>
<td><a href="#" data-bind="click: function() { viewModel.removePhone(contact, phone) }">Delete</a></td>
</tr>
{{/each}}
</table>
<a href="#" data-bind="click: function() { viewModel.addPhone(contact) }">Add number</a>
</td>
</tr>
{{/each}}
</table>
</script>
<p>
<button data-bind="click: addContact">
Add a contact</button>
<button data-bind="click: save, enable: contacts().length > 0">
Save to JSON</button>
</p>
<textarea data-bind="value: lastSavedJson" rows="5" cols="60" disabled="disabled"> </textarea>


代码: View model

var viewModel = {
contacts: new ko.observableArray([
{ firstName: "Danny", lastName: "LaRusso", phones: [
{ type: "Mobile", number: "(555) 121-2121" },
{ type: "Home", number: "(555) 123-4567"}]
},

{ firstName: "Sensei", lastName: "Miyagi", phones: [
{ type: "Mobile", number: "(555) 444-2222" },
{ type: "Home", number: "(555) 999-1212"}]
}
]),

addContact: function () {
viewModel.contacts.push({ firstName: "", lastName: "", phones: [] });
},

removeContact: function (contact) {
viewModel.contacts.remove(contact);
},

addPhone: function (contact) {
contact.phones.push({ type: "", number: "" });
viewModel.contacts.valueHasMutated();
},

removePhone: function (contact, phone) {
ko.utils.arrayRemoveItem(contact.phones, phone);
viewModel.contacts.valueHasMutated();
},

save: function () {
viewModel.lastSavedJson(JSON.stringify(viewModel.contacts(), null, 2));
},

lastSavedJson: new ko.observable("")
};

ko.applyBindings(viewModel);

2   Editable grid

该例是使用“foreach”绑定为数组里的每一项来render到 template上。好处(相对于模板内部使用for循环)是当你添加或者删除item项的时候,Knockout不需要重新render – 只需要render新的item项。就是说UI上其它控件的状态(比如验证状态)不会丢失。

如何一步一步构建这个例子并集成ASP.NET MVC,请参阅此贴

代码: View

<form action="/someServerSideHandler">
<p>
You have asked for <span data-bind="text: gifts().length">&nbsp;</span> gift(s)</p>
<table data-bind="visible: gifts().length > 0">
<thead>
<tr>
<th>Gift name</th>
<th>Price</th>
<th></th>
</tr>
</thead>
<tbody data-bind='template: { name: "giftRowTemplate", foreach: gifts }'>
</tbody>
</table>
<button data-bind="click: addGift">
Add Gift</button>
<button data-bind="enable: gifts().length > 0" type="submit">
Submit</button>
</form>
<script type="text/html" id="giftRowTemplate">
<tr>
<td><input class="required" data-bind="value: name, uniqueName: true"/></td>
<td><input class="required number" data-bind="value: price, uniqueName: true"/></td>
<td><a href="#" data-bind="click: function() { viewModel.removeGift($data) }">Delete</a></td>
</tr>
</script>


代码: View model

var viewModel = {
gifts: ko.observableArray([
{ name: "Tall Hat", price: "39.95" },
{ name: "Long Cloak", price: "120.00" }
]),

addGift: function () {
this.gifts.push({ name: "", price: "" });
},

removeGift: function (gift) {
this.gifts.remove(gift);
},

save: function (form) {
alert("Could now transmit to server: " + ko.utils.stringifyJson(this.gifts));
// To transmit to server, write this: ko.utils.postJson($("form")[0], this.gifts);
}
};

ko.applyBindings(viewModel);

$("form").validate({ submitHandler: function () { viewModel.save() } });


3   Shopping cart screen

这个例子展示的是依赖监控属性(dependent observable)怎么样链在一起。每个cart对象都有一个dependentObservable对象去计算自己的subtotal,这些又被一个进一步的dependentObservable对象依赖计算总的价格。当改变数据的时候,整个链上的依赖监控属性都会改变,所有相关的UI元素也会被更新。

这个例子也展示了如何创建联动的下拉菜单。

代码: View

<div id="cartEditor">
<table width="100%">
<thead>
<tr>
<th width="25%">Category</th>
<th width="25%">Product</th>
<th width="15%" class='price'>Price</th>
<th width="10%" class='quantity'>Quantity</th>
<th width="15%" class='price'>Subtotal</th>
<th width="10%"></th>
</tr>
</thead>
<tbody data-bind='template: {name: "cartRowTemplate", foreach: lines}'>
</tbody>
</table>
<p class="grandTotal">
Total value: <span data-bind="text: formatCurrency(grandTotal())"></span>
</p>
<button data-bind="click: addLine">
Add product</button>
<button data-bind="click: save">
Submit order</button>
</div>
<script type="text/html" id="cartRowTemplate">
<tr>
<td><select data-bind='options: sampleProductCategories, optionsText: "name", optionsCaption: "Select...", value: category'></select></td>
<td><select data-bind='visible: category, options: category() ? category().products : null, optionsText: "name", optionsCaption: "Select...", value: product'></select></td>
<td class='price'><span data-bind='text: product() ? formatCurrency(product().price) : ""'></span></td>
<td class='quantity'><input data-bind='visible: product, value: quantity, valueUpdate: "afterkeydown"'/></td>
<td class='price'><span data-bind='visible: product, text: formatCurrency(subtotal())'></span></td>
<td><a href="#" data-bind='click: function() { cartViewModel.removeLine($data) }'>Remove</a></td>
</tr>
</script>


代码: View model

function formatCurrency(value) { return "$" + value.toFixed(2); }

var cartLine = function () {
this.category = ko.observable();
this.product = ko.observable();
this.quantity = ko.observable(1);
this.subtotal = ko.dependentObservable(function () {
return this.product() ? this.product().price * parseInt("0" + this.quantity(), 10) : 0;
} .bind(this));

// Whenever the category changes, reset the product selection
this.category.subscribe(function () { this.product(undefined); } .bind(this));
};

var cart = function () {
// Stores an array of lines, and from these, can work out the grandTotal
this.lines = ko.observableArray([new cartLine()]); // Put one line in by default
this.grandTotal = ko.dependentObservable(function () {
var total = 0;
for (var i = 0; i < this.lines().length; i++)
total += this.lines()[i].subtotal();
return total;
} .bind(this));

// Operations
this.addLine = function () { this.lines.push(new cartLine()) };
this.removeLine = function (line) { this.lines.remove(line) };

this.save = function () {
var dataToSave = $.map(this.lines(), function (line) {
return line.product() ? { productName: line.product().name, quantity: line.quantity()} : undefined
});

alert("Could now send this to server: " + JSON.stringify(dataToSave));
};
};

var cartViewModel = new cart();

ko.applyBindings(cartViewModel, document.getElementById("cartEditor"));

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应用开发指南 第九章:高级应用举例的更多相关文章

  1. Knockout应用开发指南 第二章:监控属性(Observables)

    原文:Knockout应用开发指南 第二章:监控属性(Observables) 关于Knockout的3个重要概念(Observables,DependentObservables,Observabl ...

  2. Knockout应用开发指南 第一章:入门

    2011-11-21 14:20 by 汤姆大叔, 20165 阅读, 17 评论, 收藏,  编辑 1    Knockout简介 (Introduction) Knockout是一个轻量级的UI类 ...

  3. Knockout应用开发指南 应用举例(简单、高级)

    Knockout应用开发指南 第八章:简单应用举例(1)http://www.cnblogs.com/TomXu/archive/2011/11/30/2257067.htmlKnockout应用开发 ...

  4. Knockout应用开发指南(完整版) 目录索引

    http://learn.knockoutjs.com/  所有示例和代码都在在上面直接运行预览 注意:因为它用了google的cdn加速,所要要用代_理+_翻_墙才能正常加载 使用Knockout有 ...

  5. Knockout应用开发指南

    Knockout应用开发指南 第一章:入门 2011-11-21 14:20 by 汤姆大叔, 20799 阅读, 17 评论, 收藏, 编辑 1    Knockout简介 (Introductio ...

  6. Knockout应用开发指南(完整版) 目录索引(转)

    使用Knockout有一段时间了(确切的说从MIX11大会宣传该JavaScript类库以来,我们就在使用,目前已经在正式的asp.net MVC项目中使用),Knockout使用js代码达到双向绑定 ...

  7. Knockout应用开发指南 第七章:Mapping插件

    原文:Knockout应用开发指南 第七章:Mapping插件 Mapping插件 Knockout设计成允许你使用任何JavaScript对象作为view model.必须view model的一些 ...

  8. Knockout应用开发指南 第三章:绑定语法(2)

    原文:Knockout应用开发指南 第三章:绑定语法(2) 7   click 绑定 目的 click绑定在DOM元素上添加事件句柄以便元素被点击的时候执行定义的JavaScript 函数.大部分是用 ...

  9. Knockout应用开发指南 第六章:加载或保存JSON数据

    原文:Knockout应用开发指南 第六章:加载或保存JSON数据 加载或保存JSON数据 Knockout可以实现很复杂的客户端交互,但是几乎所有的web应用程序都要和服务器端交换数据(至少为了本地 ...

随机推荐

  1. HDU4706:Children's Day

    Problem Description Today is Children's Day. Some children ask you to output a big letter 'N'. 'N' i ...

  2. TensorFlow实现与优化深度神经网络

    TensorFlow实现与优化深度神经网络 转载请注明作者:梦里风林Github工程地址:https://github.com/ahangchen/GDLnotes欢迎star,有问题可以到Issue ...

  3. asp.net web api帮助文档的说明

    为asp.net的mvc web api填写自己的帮助文档 1. 加入Help的area(能够通过命令行或其它方式加入) 命令行:Install-Package Microsoft.AspNet.We ...

  4. SetBkMode可设置文字背景色:TRANSPARENT或OPAQUE

    感受一下区别: procedure TForm1.Timer2Timer(Sender: TObject); var cvs: TCanvas; Rect: TRect; Str: string; b ...

  5. javascript中外部js文件取得自身完整路径得办法

    原文:javascript中外部js文件取得自身完整路径得办法 有时候我们需要引入一个外部js文件,这个js文件又需要用到自己的路径或者是所在的目录,别问怎么又这么变态的需求,开发做久了各种奇葩需求也 ...

  6. 怎样使用jsp实现header和footer与网页内容的分离

    好多显示层框架都自带这样的功能JSF,Wicket,页面布局取决于项目使用的显示层框架. 前台即客户端的布局,通常用在无需跳转的页面.比如同样是用户管理页面,增删改查的操作都希望停留在同一个页面.这时 ...

  7. IE6_一些简单bug

    1.IE6调整窗口大小的 Bug 当把body居中放置,改变IE浏览器大小的时候,任何在body里面的相对定位元素都会固定不动了.给body定义position:relative;就行了. 2.避免百 ...

  8. Java方法区和运行时常量池溢出问题分析(转)

    运行时常量池是方法区的一部分,方法区用于存放Class的相关信息,如类名.访问修饰符.常量池.字段描述.方法描述等. String.intern()是一个native方法,它的作用是:如果字符串常量池 ...

  9. linux route命令的使用详解

    route命令用于显示和操作IP路由表.要实现两个不同的子网之间的通信,需要一台连接两个网络的路由器,或者同时位于两个网络的网关来实现.在Linux系统中,设置路由通常是 为了解决以下问题:该Linu ...

  10. 《Head First 设计模式》学习笔记——模板方法模式

    模板方法模式是类的行为模式.准备一个抽象类,将部分逻辑以详细方法以及详细构造函数的形式实现.然后声明一些抽象方法来迫使子类实现剩余的逻辑.不同的子类能够以不同的方式实现这些抽象方法,从而对剩余的逻辑有 ...