Tutorial : How to create editable grids using Knockout JS
by kougiland
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.addFruit">Add a new fruit</button>
<hr />
<table class="fruitGrid">
<thead>
<tr>
<th class="headRow headColumn">
Fruit Name
</th>
<th class="headRow headColumn">
Fruit Colour
</th>
<th class="headRow tools">
</th>
</tr>
</thead>
<tbody data-bind="foreach: fruits">
<tr class="row">
<td class="rowItem">
<input type="text" class="edit" data-bind="value: name.editValue, visible: $root.isItemEditing($data)" />
<label class="read" data-bind="text: name, visible: !$root.isItemEditing($data)" />
</td>
<td class="rowItem">
<select class="edit" data-bind="options: $root.availableColours, value: colour.editValue, visible: $root.isItemEditing($data)"></select>
<label class="read" data-bind="text: colour, visible: !$root.isItemEditing($data)" />
</td>
<td class="tools">
<a class="button toolButton" href="#" data-bind="click: $root.editFruit.bind($root), visible: !$root.isItemEditing($data)">Edit</a>
<a class="button toolButton" href="#" data-bind="click: $root.removeFruit.bind($root), visible: !$root.isItemEditing($data)">Remove</a>
<a class="button toolButton" href="#" data-bind="click: $root.applyFruit.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>
</tbody>
</table>
CSS
/* Grid */
.fruitGrid {
/* structure */
width: 550px;
padding: 5px;
}
.fruitGrid .headColumn {
/* structure */
padding: 10px;
/* skin */
color: White;
background-color: #888;
}
.fruitGrid .row:nth-child(even) {
/* skin */
background-color:#eee;
}
.fruitGrid .rowItem {
/* structure */
padding: 10px;
}
.fruitGrid .rowItem .read {
/* structure */
display: block;
width: 150px;
}
.fruitGrid .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
/*----------------------------------------------------------------------*/
/* 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 Fruit( name, colour) {
var self = this;
self.name = ko.observable(name);
self.colour = ko.observable(colour);
};
Fruit.prototype.beginEdit = function(transaction) {
this.name.beginEdit(transaction);
...