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

原文: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];};

// MethodsviewModel.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.userNamesko.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(); });

时间: 2024-10-17 18:50:01

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

Knockout应用开发指南

第一章:入门 1.Knockout简介 (Introduction) Knockout是一个轻量级的UI类库,通过应用MVVM模式使JavaScript前端UI简单化. Knockout有如下4大重要概念: 声明式绑定 (Declarative Bindings):使用简明易读的语法很容易地将模型(model)数据关联到DOM元素上. UI界面自动刷新 (Automatic UI Refresh):当您的模型状态(model state)改变时,您的UI界面将自动更新. 依赖跟踪 (Depend

MiS603开发板 第九章 IP-CORE BRAM

作者:MiS603开发团队 日期:20150911 公司:南京米联电子科技有限公司 论坛:www.osrc.cn 网址:www.milinker.com 网店:http://osrc.taobao.com EAT博客:http://blog.chinaaet.com/whilebreak 博客园:http://www.cnblogs.com/milinker/ MiS603开发板 第九章 IP-CORE BRAM 上一章节笔者讲解了FIFO的简单使用,那么FPGA的Block RAM(BRAM)

Knockout应用开发指南 第十章:更多信息(完结篇)

原文:Knockout应用开发指南 第十章:更多信息(完结篇) 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测试

【Python web 开发】第九章开发总结

第九章开发总结: 这一章节主要是 用户的个人中心模块,比较简单 1.动态的配置premisstion 2.动态的配置serializers 3.用户的个人信息修改 4.个人中心--用户收藏功能的实现 5.个人中心--用户留言功能的实现 6.个人中心--用户收获地址功能的实现 总结:还是要回到前面去去看下 viewsets.GenericViewSet 的源码为什么获取当前用户 要重写get_queryset()? 原文地址:https://www.cnblogs.com/yuanyuan2017

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

Mapping插件 Knockout设计成允许你使用任何JavaScript对象作为view model.必须view model的一些属性是observable的,你可以使用KO绑定他们到你的UI元素上,当这些observable值改变的时候,这些UI元素就会自动更新. 绝大多数程序都需要从服务器端获取数据,但是由于服务器不知道observable的概念是什么,它只支持简单的JavaScript对象(通常是序列化以后的JSON),mapping插件可以让你很方便地将简单JavaScript对象

Knockout应用开发指南 第三章:绑定语法

12   value 绑定 目的 value绑定是关联DOM元素的值到view model的属性上.主要是用在表单控件<input>,<select>和<textarea>上. 当用户编辑表单控件的时候, view model对应的属性值会自动更新.同样,当你更新view model属性的时候,相对应的元素值在页面上也会自动更新. 注:如果你在checkbox或者radio button上使用checked绑定来读取或者写入元素的 checked状态,而不是value

Knockout应用开发指南 第四章:模板绑定

模板绑定The template binding 目的 template绑定通过模板将数据render到页面.模板绑定对于构建嵌套结构的页面非常方便.默认情况, Knockout用的是流行的jquery.tmpl模板引擎.使用它的话,需要在安装页面下载和引用jquery.tmpl和jQuery框架.或者你也可以集成其它的模板引擎(虽然需要了解Knockout 内部知识才行). 例子 <div data-bind='template: "personTemplate"'> &

Knockout应用开发指南 第五章:创建自定义绑定

创建自定义绑定 你可以创建自己的自定义绑定 – 没有必要非要使用内嵌的绑定(像click,value等).你可以你封装复杂的逻辑或行为,自定义很容易使用和重用的绑定.例如,你可以在form表单里自定义像grid,tabset等这样的绑定. 重要:以下文档只应用在Knockout 1.1.1和更高版本,Knockout 1.1.0和以前的版本在注册API上是不同的. 注册你的绑定 添加子属性到ko.bindingHandlers来注册你的绑定: ko.bindingHandlers.yourBin

《HBase权威指南》读书笔记9:第九章 高级用法

行键设计 高表与宽表 高表:表中列少行多 宽表:表中列多行少 Hbase只能按行分片,因此高表更有优势. 把需要检索的条件尽量放到行键rowkey里面去 宽表适合需要行级原子性的需求 辅助索引 由客户端管理索引:缺点比优点更多 带索引的事务型Hbase: ITHbase  (个人认为要远离在原本就不成熟的HBase上的更不成熟的封装) 带索引的Hbase: IHbase (个人认为要远离在原本就不成熟的HBase上的更不成熟的封装) 协处理器: 用钩子维护索引 搜索集成 用HBase存储数据,用