Edit in JSFiddle

//You can now create a bindingProvider that uses something different than data-bind attributes
ko.customBindingProvider = function(bindingObject) {
    this.bindingObject = bindingObject;
    
    //determine if an element has any bindings
    this.nodeHasBindings = function(node) {
        return node.getAttribute ? node.getAttribute("data-class") : false;
    };
    
    //return the bindings given a node and the bindingContext
    this.getBindings = function(node, bindingContext) {
        var i, j, bindingAccessor, binding, 
            result = {},
            classes = node.getAttribute("data-class");
        
        if (classes) {
            classes = classes.split(' ');  
            //evaluate each class, build a single object to return
            for (i = 0, j = classes.length; i < j; i++) {
               bindingAccessor = this.bindingObject[classes[i]];
               if (bindingAccessor) {
                   binding = typeof bindingAccessor == "function" ? bindingAccessor.call(bindingContext.$data) : bindingAccessor;
                   ko.utils.extend(result, binding);               
               }            
            }
        }
    
        return result;
    }; 
};


/*
This is the definition for an extended observable called a "protectedObservable"
-this would generally belong in a separate library
-the idea is that you can still use it just like any observable, but it has more features
-some of the extra features that it provides
  -when someone writes to it, the value is cached until you call its "commit" method
  -a "reset" method will update the bound fields back to the original value
  -the temporary value is exposed, in case you need to send it to the server
  -a dirtyFlag is exposed using a dependentObservable that checks the original vs. temp value
-now you can bind to your observable and any of its sub-observables (temp, isDirty, commit, reset)
-this post explains this idea in more detail: http://www.knockmeout.net/2011/03/guard-your-model-accept-or-cancel-edits.html  
*/
ko.protectedObservable = function(initalValue) {
    //private variables
    var _temp = ko.observable(initalValue),
        _actual = ko.observable(initalValue);

    //what we actually return is a writeable dependentObservable, so we can intercept writes to it
    var result = ko.dependentObservable({
        read: function() {
            return _actual();
        },
        write: function(newValue) {
            _temp(newValue);
        }
    });
    
    //expose the temporary value
    result.temp = _temp;

    //expose a flag to indicate that the values are different
    result.isDirty = ko.dependentObservable(function() {
       return _temp() !== _actual();   
    });
    
    //commit the temporary value to our observable
    result.commit = function() {
         _actual(_temp());
    };

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

    return result;
};

//This is a simple custom binding that will fade in or out an element based on the truthiness of the value passed to it.
//This would likely belong in a separate library
ko.bindingHandlers.showVisible = {
    update: function(element, valueAccessor) {
       var value = ko.utils.unwrapObservable(valueAccessor());
        if (value) {
            $(element).show(500);
        } else {
           $(element).hide();   
        }
    }  
}

//this is our constructor function for a Product object
function Product(product, selectedProduct) {
    this.id = product.id;
     //these editable fields use the protectedObservables that we defined above
    this.name = ko.protectedObservable(product.name); 
    this.description = ko.protectedObservable(product.description);
    
    //we passed in the viewModel's selectedProduct observable, so that we can mark ourself as the selected product
    //this makes it easy to bind our item against select (data-bind="click: select") rather than using anonymous functions
    this.select = function() {
       selectedProduct(this);   
    };
    
    //indicates whether I am the selected product.  
    this.isSelected = ko.dependentObservable(function() {
        return this === selectedProduct();
    }, this);
    
    //some extra observables to track whether it has been saved and any messages returned by the server
    this.saveStatus = ko.observable();
    this.saveMessage = ko.observable();
    
    //this dependentObservable will fire any time that one of our items becomes dirty (or not dirty)
    this.isDirty = ko.dependentObservable(function() {
        return this.name.isDirty() || this.description.isDirty();
    }, this);
    
    //when dirtyFlag fires, clear out save fields, since we now need to save it again
    this.isDirty.subscribe(function(newValue) {
        if (newValue) {
            this.saveStatus(null);
            this.saveMessage(null);
        }
    }, this);
}

//Save the data back to the server
Product.prototype.save = function() {
    var self = ko.utils.unwrapObservable(this);
    
    $.ajax({
            type: 'POST',
            url: '/echo/json/',
            data: {
                json: ko.toJSON({id: self.id, name: self.name.temp(), description: self.name.temp()}),
                delay: .3
            },
            context: self,
            success: function(data) {
                this.name.commit();
                this.description.commit();
                this.saveStatus("success");
                this.saveMessage("Saved!");
            },
            error: function(request, error) {
                this.saveStatus("error");
                this.saveMessage("Error: " + error);  
            },
            dataType: 'json'
     });
}; 

//If the user cancels, then reset our fields.
Product.prototype.cancel = function() {
    var self = ko.utils.unwrapObservable(this);
    self.name.reset();
    self.description.reset();
}
    

//this is the simple view model that we start with
var viewModel = {
    products: ko.observableArray(),
    selectedProduct: ko.observable()
};



//this is the start up for our application
$(function() {
    
    //data to be returned by AJAX request (faking it using jsFiddle's echo service)
    var fakeData = {
        products: [
            { id: 1, name: "One", description: "This is product one" },
            { id: 2, name: "Two", description: "This is product two" },
            { id: 3, name: "Three", description: "This is product three" },
            { id: 4, name: "Four", description: "This is product four" },
            { id: 5, name: "Five", description: "This is product five" },
            { id: 6, name: "Six", description: "This is product six" }
        ]  
    };

    
    //our binding object
    var bindings = {
        products: { foreach: viewModel.products },
        nameText: function() { return { text: this.name }; },
        selectLink: function() { return { visible: !this.isSelected(), click: this.select }; },
        selectSpan: function() { return { visible: this.isSelected }; },
        editor: { visible: viewModel.selectedProduct, with: viewModel.selectedProduct },
        nameVal: function() { return { value: this.name }; },
        descVal: function() { return { value: this.description }; },
        keydown: { valueUpdate: 'afterkeydown' },
        dirty: function() { return { enable: this.isDirty } },
        save: function() { return { click: this.save } },
        cancel: function() { return { click: this.cancel } },
        saveStatus: function() { return { showVisible: this.saveMessage, text: this.saveMessage, attr: { 'class': this.saveStatus } }; }
    };
    
    
    //set ko's current bindingProvider equal to our new binding provider
    ko.bindingProvider.instance = new ko.customBindingProvider(bindings);    
    
    
   //get our data from the server
     $.ajax({
            type: 'POST',  //echo only works with post
            url: '/echo/json/',
            data: {
                json: ko.toJSON(fakeData),
                delay: .1
            },
            success: function(data) {
                //loop through the products returned and send them through the Product contructor function
                viewModel.products(ko.utils.arrayMap(data.products, function(product) {
                   return new Product(product, viewModel.selectedProduct); 
                }));
                
                //finally apply bindings against the UI
                ko.applyBindings(viewModel);
            },
            dataType: 'json'
    });
});

<div id="list">
    <h2>Product</h2>
    <ul data-class="products">
        <li>
            <a href="#" data-class="nameText selectLink"></a>
            <span class="selected" data-class="nameText selectSpan"></span>
        </li>
    </ul>
</div>

<div id="editor" style="display: none" data-class="editor">
    <div>
        Name<br/>
        <input data-class="nameVal keydown" />
    </div>
    <div>
        Description<br/>
        <input data-class="descVal keydown" />
    </div>
    
    <div>
        <button data-class="dirty save">Save</button> 
        <button data-class="dirty cancel">Cancel</button> 
        <span data-class="saveStatus"></span>
    </div>
</div>

input { margin-bottom: 10px; }
h2 { font-size: 1.25em; font-weight: bold; }

.error { color: red }
.success { color: green }
.selected { color: blue; font-weight: bold; }

#list { 
    width: 100px; 
    height: 150px;
    border: solid 1px black; 
    padding: 5px; 
    float: left; 
}
#editor {
    height: 150px;
    border: solid 1px black;
    padding: 5px;
    margin-left: 120px;
}