1

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.

4

1 に答える 1

1

考えられる解決策の 1 つは、一意のバリデーターに現在編集中のアイテムを含めない nameことです (もちろん、新しいアイテムを作成するときは完全なリストが必要です)。

したがって、場所の名前を元の値に戻すと、一意のチェックはトリガーされません。

self.namesExceptCurrent = function(name){
    return ko.utils.arrayMap(self.locations(), function(item) {
        if (item.name() !== name)
            return item.name();
    });
}

self.edit = function(item) {
    self.selectedLocation(item);
    self.selectedLocationForEditing(
        new Location(ko.toJS(item), 
        self.types, 
        self.namesExceptCurrent(item.name())));
};

JSFiddle のデモ。

于 2013-04-11T16:18:29.580 に答える