Knockout Editor Pattern with Validation
HTML
<script src="https://rawgit.com/ericmbarnard/Knockout-Validation/master/Src/knockout.validation.js"></script>
<p>Select "Location1" and change the name to "Location2". It will become invalid.</p>
<p>Now change that same field back to "Location1". It should be valid but is still marked invalid</p>
<hr>
<table>
<thead>
<tr>
<th>Name</th>
<th>State</th>
<th>Headline</th>
<th>Type</th>
</tr>
</thead>
<tbody data-bind="foreach: locations">
<tr data-bind="click: $root.edit">
<td data-bind="text: name"></td>
<td data-bind="text: state"></td>
<td data-bind="text: headline"></td>
<td data-bind="text: type().name"></td>
</tr>
</tbody>
</table>
<div class="edit-form" data-bind="visible: selectedLocationForEditing, with: selectedLocationForEditing">
<p>Edit Selected Record</p>
Errors: <span data-bind="text: errors().length"></span>
<div>
<label>Id</label>
<span data-bind="text: id"></span>
</div>
<div>
<label>Name</label>
<input type="text" data-bind="value: name"></input>
</div>
<div>
<label>State</label>
<input type="text" data-bind="value: state"></input>
</div>
<div>
<label>Headline</label>
<input type="text" data-bind="value: headline"></input>
</div>
<div>
<label>Type</label>
<select data-bind="options: $root.types, optionsText: 'name', value: type, optionsCaption: 'Choose...'"></select>
</div>
<a data-bind="click: $root.update">Update</a>
<a data-bind="click: $root.cancel">Cancel</a>
<p>names: <pre data-bind="text: ko.toJSON($root.names)"></pre></p>
</div>
CSS
.edit-form label { color: #888; }
.edit-form a { cursor: pointer; }
.edit-form a:hover { color: #ff0066; }
JavaScript
var seedData = [
{ id: 1, name: 'Location1', state: 'md', headline: 'headline1', type: { id: 1, name: 'Public' } },
{ id: 2, name: 'Location2', state: 'va', headline: 'headline2', type: { id: 2, name: 'Private' } },
{ id: 3, name: 'Location3', state: 'md', headline: 'headline3', type: { id: 1, name: 'Public' } }
];
function Location(data, types, names) {
var self = this;
self.id = data.id;
self.name = ko.observable(data.name).extend({ maxLength: 10, unique: { collection: names, externalValue: true } });
self.state = ko.observable(data.state).extend({ pattern: '^[A-Za-z]{2}$'});
self.headline = ko.observable();
self.type = ko.observable();
self.errors = ko.validation.group(self);
//update the data at any time
self.update = function(data) {
self.name(data.name);
self.state(data.state);
self.headline(data.headline);
//set type to the correct reference
var id = data.type && data.type.id,
match = id && ko.utils.arrayFirst(types, function(type) {
return type.id === id;
});
self.type(match);
};
//initialize with our initial data
self.update(data);
};
function ViewModel() {
var self = this;
self.types = [
{ id: 1, name: 'Public' },
{ id: 2, name: 'Private' }
];
self.locations = ko.observableArray([]);
self.selectedLocation = ko.observable();
self.selectedLocationForEditing = ko.observable();
self.names = ko.computed(function(){
return ko.utils.arrayMap(self.locations(), function(item) {
return item.name();
});
});
self.namesExcept = function(name){
return ko.utils.arrayMap(self.locations(), function(item) {
if (item.name() !== name)
return item.name();
});
}
self.edit = function(item) {
self.selectedLocation(item);
...