I'm using KnockoutJS with the Knockout-Validation plugin to validate fields on a form. I'm having problems validating that a value is unique using the native validation rule - unique
I'm using the Editor Pattern from Ryan Niemeyer to allow the user to edit or create a Location
. Here's my fiddle to see my problem in its entirety.
function Location(data, names) {
var self = this;
self.id = data.id;
self.name = ko.observable().extend({ unique: { collection: names }});
// other properties
self.errors = ko.validation.group(self);
// update method left out for brevity
}
function ViewModel() {
var self = this;
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.edit = function(item) {
self.selectedLocation(item);
self.selectedLocationForEditing(new Location(ko.toJS(item), self.types));
};
self.cancel = function() {
self.selectedLocation(null);
self.selectedLocationForEditing(null);
};
self.update = function(item) {
var selected = self.selectedLocation(),
updated = ko.toJS(self.selectedLocationForEditing()); //get a clean copy
if(item.errors().length == 0) {
selected.update(updated);
self.cancel();
}
else
alert("Error");
};
self.locations(ko.utils.arrayMap(seedData, function(item) {
return new Location(item, self.types, self.names());
}));
}
I'm having an issue though. Since the Location
being edited is "detached" from the locations
observableArray (see Location.edit
method), when I make changes to name
in the detached Location
that value isn't updated in the names
computed array. So when the validation rule compares it to the names
array it will always return a valid state of true since the counter will only ever be 1 or 0. (Please see knockout-validation algorithm below)
Within the options argument for the unique
validation rule I can pass in a property for externalValue
. If this value is not undefined then it will check to see if the count of matched names is greater or equal to 1 instead of 2. This works except for cases when the user changes the name, goes on to another field, and then goes back to the name and wants to change it back to the original value. The rule just sees that the value already exists in the names
array and returns a valid state of false.
Here is the algorithm from knockout.validation.js that handles the unique
rule...
function (val, options) {
var c = utils.getValue(options.collection),
external = utils.getValue(options.externalValue),
counter = 0;
if (!val || !c) { return true; }
ko.utils.arrayFilter(ko.utils.unwrapObservable(c), function (item) {
if (val === (options.valueAccessor ? options.valueAccessor(item) : item)) { counter++; }
});
// if value is external even 1 same value in collection means the value is not unique
return counter < (external !== undefined && val !== external ? 1 : 2);
}
I've thought about using this as a base to create a custom validation rule but I keep getting stuck on how to handle the situation when the user wants go back to the original value.
I appreciate any and all help.