Tutorial : How to create editable grids using Knockout JS

by Jon Kittell

HTML

<script src="http://cloud.github.com/downloads/knockout/knockout/knockout-2.1.0.debug.js"></script>
<button class="button addNewButton" data-bind="click: $root.approveAll">Approve All</button>
    <hr />
<table class="requestGrid">
        <thead>
            <tr>
                <th class="headRow headColumn">
                    Policy Number
                </th>
                <th class="headRow headColumn">
                    Status
                </th>
                <th class="headRow headColumn">
                    Reason
                </th>
                <th class="headRow tools">
                </th>
            </tr>
        </thead>
        <tbody data-bind="foreach: leadRequests">
            <tr class="row">
                <td class="rowItem">
                    <label class="read" data-bind="text: name" />
                </td>
                <td class="rowItem">
                    <label class="read" data-bind="text: status" />
                </td>
                <td class="rowItem">
                    <input type="text" class="edit" data-bind="value: reason.editValue, visible: $root.isItemEditing($data)"  />
                    <label class="read" data-bind="text: reason, visible: !$root.isItemEditing($data)" />
                </td>
                <td class="tools">
                    <a class="button toolButton" href="#" data-bind="click: $root.approveLead.bind($root), visible: !$root.isItemEditing($data)">Approve</a>
                    <a class="button toolButton" href="#" data-bind="click: $root.denyLeadRequest.bind($root), visible: !$root.isItemEditing($data)">Deny</a> 
                    <a class="button toolButton" href="#" data-bind="click: $root.applyLeadRequest.bind($root), visible: $root.isItemEditing($data)">Apply</a>
                    <a class="button toolButton" href="#" data-bind="click: $root.cancelEdit.bind($root), visible: $root.isItemEditing($data)">Cancel</a>
                </td>
            </tr>
     ...

CSS

/* Grid */

.requestGrid { 
    /* structure */
    width: 850px; 
    padding: 5px; 
}

.requestGrid .headColumn { 
    /* structure */
    padding: 10px; 
    
    /* skin */
    color: White;
    background-color: #888;

}

.requestGrid .row:nth-child(even) { 
    /* skin */
    background-color:#eee; 
}

.requestGrid .rowItem { 
    /* structure */
    padding: 10px; 

}

.requestGrid .rowItem .read   { 
    /* structure */
    display: block;
    width: 150px;
}

.requestGrid .tools { 
    /* skin */
    background-color:#ffffff;
    padding: 5px;
}

/* Buttons */

.button {
    /* structure */
    padding: 8px 12px;
    
    /* skin */
    border: 1px solid gainsboro;
    cursor: pointer;  
    text-decoration: none; 
}

.addNewButton {
    /* skin */
    background-color: #FB8F3D;   
    background-image: -webkit-linear-gradient(top, #FDA352, #FB8F3D);
    background-image: -moz-linear-gradient(top, #FDA352, #FB8F3D);
    background-image: -ms-linear-gradient(top, #FDA352, #FB8F3D);
    background-image: -o-linear-gradient(top, #FDA352, #FB8F3D);
    background-image: linear-gradient(top, #FDA352, #FB8F3D);
    color: White;
}

.toolButton {
    /* skin */
    background-color: #F9F9F9;
    background-image: -webkit-linear-gradient(top, white, #F9F9F9);
    background-image: -moz-linear-gradient(top, white, #F9F9F9);
    background-image: -ms-linear-gradient(top, white, #F9F9F9);
    background-image: -o-linear-gradient(top, white, #F9F9F9);
    background-image: linear-gradient(top, white, #F9F9F9);
    color: #444;
}

JavaScript

debugger;
/*----------------------------------------------------------------------*/
/* Observable Extention for Editing
/*----------------------------------------------------------------------*/
ko.observable.fn.beginEdit = function (transaction) {
    
    var self = this;
    var commitSubscription, 
        rollbackSubscription;
    
    // get the current value and store it for editing
    if (self.slice)
        self.editValue = ko.observableArray(self.slice());
    else
        self.editValue = ko.observable(self());
        
    self.dispose = function () {        
        // kill this subscriptions
        commitSubscription.dispose();
        rollbackSubscription.dispose(); 
    };
    
    self.commit = function () {
        // update the actual value with the edit value
        self(self.editValue());
        
        // dispose the subscriptions
        self.dispose();
    };
        
    self.rollback = function () {
        // rollback the edit value
        self.editValue(self());
        
        // dispose the subscriptions
        self.dispose();
    };
    
    //  subscribe to the transation commit and reject calls
    commitSubscription = transaction.subscribe(self.commit,
                                                self,
                                                "commit");
    
    rollbackSubscription = transaction.subscribe(self.rollback,
                                                    self,
                                                    "rollback");
    
    return self;
}

/*----------------------------------------------------------------------*/
/* Item Model
/*----------------------------------------------------------------------*/

function LeadRequest(name, status, reason) {
    var self = this;
    self.name = ko.observable(name);
    self.status = ko.observable(status);
    self.reason = ko.observable(reason);
};

LeadRequest.prototype.beginEdit = function(transaction) {
   ...