Guard your model

http://www.knockmeout.net/2011/03/guard-your-model-accept-or-cancel-edits.html

by smichelotti

HTML

<script src="http://rniemeyer.github.com/KnockMeOut/Scripts/jquery.tmpl.js"></script>
<script src="http://rniemeyer.github.com/KnockMeOut/Scripts/knockout-latest.debug.js"></script>
First:
<ul>
    <!-- ko foreach: items -->
    <li>
        <span data-bind="text: name"></span>
    </li>
    <!-- /ko -->
</ul>
Second:
<ul data-bind="template: { name: 'itemTempl2', foreach: items }">
</ul>
<script id="itemTempl2" type="text/html">
    <li>
        <span data-bind="text: name"></span>
    </li>  
</script>

<table>
    <tr>
        <th>Name</th>
        <th>Quantity</th>
        <th></th>
        <th></th>
    </tr>            
    <tbody data-bind="template: { name: templateToUse, foreach: items}"></tbody>
</table>

<script id="itemTmpl" type="text/html">
    <tr>
        <td data-bind="text: name"></td>
        <td data-bind="text: quantity"></td>
        <td class="buttons">
            <button data-bind="click: function() { viewModel.editItem($data); }">Edit</button>
            <button data-bind="click: function() { viewModel.deleteItem($data); }">Delete</button>
        </td>
    </tr>
</script>

<button data-bind="click: addItem">New Item</button>

<script id="editTmpl" type="text/html">
    <tr>
        <td>
            <input data-bind="value: name" />
        </td>
        <td>
            <input data-bind="value: quantity" />
        </td>
        <td class="buttons">
            <button data-bind="click: function() { viewModel.acceptItemEdit() }">Accept</button>
            <button data-bind="click: function() { viewModel.cancelItemEdit() }">Cancel</button>
        </td>
    </tr>
</script>

CSS

input { width: 75px; }
td { width: 75px; }
th { color: #666; font-size: .8em; }
.buttons { width: 150px; }

JavaScript

//wrapper for an observable that protects value until committed
ko.protectedObservable = function(initialValue) {
    //private variables
    var _temp = initialValue;
    var _actual = ko.observable(initialValue);

    var result = ko.dependentObservable({
        read: function() {
            return _actual();
        },
        write: function(newValue) {
            _temp = newValue;
        }
    });

    //commit the temporary value to our observable, if it is different
    result.commit = function() {
        if (_temp !== _actual()) {
            _actual(_temp);
        }
    };

    //notify subscribers to update their value with the original
    result.reset = function() {
        _actual.valueHasMutated();
        _temp = _actual();
    };

    return result;
};

//construct an Item

function Item(name, quantity) {
    this.name = ko.protectedObservable(name);
    this.quantity = ko.protectedObservable(quantity);
}

var viewModel = {
    selectedItem: ko.observable(),
    addItem: function() {
        var newItem = new Item("new item", 0);
        this.items.push(newItem);
        this.selectedItem(newItem);
    },
    deleteItem: function(itemToDelete) {
        this.items.remove(itemToDelete);
        this.selectedItem(null);
    },
    editItem: function(item) {
        this.selectedItem(item);
    },
    acceptItemEdit: function() {
        this.selectedItem().name.commit();
        this.selectedItem().quantity.commit();
        this.selectedItem(null);
    },
    cancelItemEdit: function() {
        this.selectedItem().name.reset();
        this.selectedItem().quantity.reset();
        this.selectedItem(null);
    },
    templateToUse: function(item) {
        return viewModel.selectedItem() === item ? "editTmpl" : "itemTmpl";
    }
};

viewModel.items = ko.observableArray([
    new Item("one", 1),
    new Item("two", 2),
    new Item("three", 3)
    ]);

ko.applyBindings(viewModel);