Edit in place Knockout
by HugeHugh
HTML
<table>
<thead>
<th>ID</th>
<th>Name</th>
<th>Position</th>
<th> </th>
</thead>
<tbody data-bind="foreach:rows">
<tr data-bind="visible:!isEditMode()">
<td data-bind="text:id"></td>
<td data-bind="text:name"></td>
<td data-bind="text:position"></td>
<td><input type="button" data-bind="click:editRow" value="Edit"/></td>
</tr>
<tr data-bind="visible:isEditMode">
<td data-bind="text:id"></td>
<td><input data-bind="value:name"/></td>
<td><select data-bind="options: $parent.rows, optionsText: 'position', optionsValue: 'position', value: position"></select></td>
<td><input type="button" data-bind="click:saveRow" value="Save"/><input type="button" data-bind="click:cancelEdit" value="Cancel"/></td>
</tr>
</tbody>
</table>
<div class="saver" data-bind="visible:isSaving">Saving to the database...</div>
<input type="button" data-bind="click:function(){isAddRowFormVisible(true)}" value="Add a row"/>
<div class="darkener" data-bind="visible: isAddRowFormVisible, click:function(){isAddRowFormVisible(false)}">
<div class="addRowForm" data-bind="click: function(){ return true }, clickBubble: false">
<h3>Add a row</h3>
ID: <input data-bind="value:newID, valueUpdate: 'afterkeydown'"/><br/>
Name: <input data-bind="value:newName, valueUpdate: 'afterkeydown'"/><br/>
Position: <input data-bind="value:newPosition, valueUpdate: 'afterkeydown'"/><br/>
<input type="button" value="Add row" data-bind="click:addRow, disable: !canAddRow()"/>
<input type="button" data-bind="click:function(){isAddRowFormVisible(false)}" value="Close"/>
</div>
</div>
CSS
.saver {position:absolute; top:0;right:0;background:green;color:white;padding:4px;display:inline-block}
.darkener {position:absolute; top:0;right:0;bottom:0;left:0; background:rgba(0,0,0,0.5)}
.addRowForm {width:300px; height:200px; background:#eee; border:1px solid #000; box-shadow:0 0 20px rgba(0,0,0,0.8); padding:8px; position:absolute;left:50%;top:50%; margin-left:-150px; margin-top:-100px; border-radius:8px}
JavaScript
function rowViewModel(initId, initName, initPosition) {
var name = ko.observable(initName);
var position = ko.observable(initPosition);
var isEditMode = ko.observable(false);
var preEditValues = {};
function editRow() {
preEditValues = {
name: name(),
position: position()
};
isEditMode(true);
}
function saveRow() {
isEditMode(false);
pageModel.isSaving(true);
// commit the changes to the DB with an ajax call
// fake it with an artificial 1 second delay
setTimeout(function() {
pageModel.isSaving(false);
}, 1000);
}
function cancelEdit() {
name(preEditValues.name);
position(preEditValues.position);
isEditMode(false);
}
return {
// id is not observable because it is never modified
id: initId,
name: name,
position: position,
isEditMode: isEditMode,
editRow: editRow,
saveRow: saveRow,
cancelEdit: cancelEdit
};
}
var rows = [];
rows.push(rowViewModel(1, "Hugh", "Developer"));
rows.push(rowViewModel(2, "Zach", "Developer"));
rows.push(rowViewModel(3, "Josh", "Designer"));
rows.push(rowViewModel(4, "Laura", "Designer"));
rows.push(rowViewModel(5, "Jake", "Security"));
rows.push(rowViewModel(6, "Bernie", "Real Estate Mogul"));
function addRow() {
pageModel.isSaving(true);
// commit the changes to the DB with an ajax call
// 1 second delay simluates server lag
setTimeout(function() {
pageModel.isAddRowFormVisible(false);
// where to add
var where = 3;
pageModel.rows.splice(where, 0, rowViewModel(pageModel.newID(), pageModel.newName(), pageModel.newPosition()));
pageModel.newID(7);
pageModel.newName('');
pageModel.newPosition('');
pageModel.isSaving(false);
}, 1000);
}
var pageModel = {
rows: ko.observableArray(rows),
...