protectedObjectObservable

https://groups.google.com/d/topic/knockoutjs/_HTI5QvG7Ls/discussion

by photo_tom

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>
<script src="https://raw.github.com/SteveSanderson/knockout.mapping/master/build/output/knockout.mapping-latest.js"></script>
<ul data-bind="template: { name: 'itemTmpl', foreach: items }"></ul>

<script id="itemTmpl" type="text/html">
    <li>
        <a href="#" data-bind="text: name, click: function() { viewModel.selectedItem.init($data); }"></a>
    </li>
</script>

<hr />

<div data-bind="template: { name: 'editorTmpl', data: selectedItem.temp }"></div>

<script id="editorTmpl" type="text/html">
    <input data-bind="value: name" />
    <input data-bind="value: description" /><br/>
    <button data-bind="click: viewModel.save">Save</button>
    <button data-bind="click: viewModel.cancel">Cancel</button>
</script>

<div data-bind="text: ko.toJSON(viewModel.items)"></div>

JavaScript

ko.protectedObjectObservable = function(initialValue) {
    var result = ko.observable(initialValue);  
    
    //expose temp value for binding.  ko.toJS is an easy way to get a clean copy
    result.temp = ko.observable(ko.toJS(initialValue));  
    
    //apply edits to the original (only goes one level deep) 
    result.commit = function() {
        var original = result(),
            edited = result.temp();
        
        for (var prop in edited) {
            if (original.hasOwnProperty(prop)) {
                if (ko.isWriteableObservable(original[prop])) {
                   original[prop](edited[prop]);  
                   //ignore dependentObservables + methods 
                } else if (typeof original[prop] !== "function") {  
                   original[prop] = edited[prop]; 
                }
            } 
        } 
        
        return result; //for chaining
    };  
    
    //start over with a new value
    result.init = function(newValue) {
        result.temp(ko.toJS(newValue));
        result(newValue); 
        return result;  //for chaining 
    }
    
    //revert back to a fresh copy
    result.reset = function(newValue){  
        result.temp(ko.toJS(result));   
        return result;  //for chaining
    };  
        
    //clear it out, so it is ready for a new one
    result.clear = function() {
       result.temp(null);
       result(null);  
       return result; //for chaining
    }

    return result;
};

function Item(id, name, description) {
    return {
        id: id,
        name: ko.observable(name), 
        description: ko.observable(description) 
    };
}

var viewModel = {
    items: ko.observableArray([
        new Item(1, "one", "one description"),
        new Item(2, "two", "two description"),
        new Item(3, "three","three description")
       ]),
    selectedItem: ko.protectedObjectObservable(),
    save: function() {
       viewModel.selectedItem.commit().clear();
    },
    cancel: function() {
      ...