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

来源:互联网 发布:快读免费小说网络错误 编辑:程序博客网 时间:2024/05/29 07:03

1   Contacts editor

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

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

 

 

代码: View

View Code
复制代码
<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

View Code
复制代码
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

View Code
复制代码
<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

View Code
复制代码
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

View Code
复制代码
<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

View Code
复制代码
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

View Code
复制代码
<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

View Code
复制代码
// 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 itvar 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(); });
复制代码

 

0 0
原创粉丝点击