Проверка уникальных значений с помощью плагина Knockout-Validation

Я использую KnockoutJS с плагином Knockout-Validation для проверки полей в форме. У меня возникают проблемы с проверкой уникальности значения с использованием собственного правила проверки — unique

Я использую шаблон редактора от Райана Нимейера, чтобы позволить пользователю редактировать или создавать файл Location. Вот моя скрипка, чтобы полностью увидеть мою проблему.

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());
    })); 
}

У меня проблема. Поскольку редактируемый Location "отсоединен" от locations observableArray (см. метод Location.edit), когда я вношу изменения в name в отсоединенном Location, это значение не обновляется в вычисляемом массиве names. Поэтому, когда правило проверки сравнивает его с массивом names, оно всегда будет возвращать действительное состояние true, поскольку счетчик всегда будет только 1 или 0. (Пожалуйста, см. Алгоритм проверки нокаута ниже)

В аргументе options для правила проверки unique я могу передать свойство для externalValue. Если это значение не неопределенно, то он проверит, больше или равно ли количество совпадающих имен 1 вместо 2. Это работает, за исключением случаев, когда пользователь меняет имя, переходит к другому полю, а затем возвращается на имя и хочет изменить его обратно на исходное значение. Правило просто видит, что значение уже существует в массиве names, и возвращает допустимое состояние false.

Вот алгоритм из Knockout.validation.js, который обрабатывает правило unique...

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);
}

Я думал об использовании этого в качестве основы для создания пользовательского правила проверки, но я продолжаю застревать в том, как справиться с ситуацией, когда пользователь хочет вернуться к исходному значению.

Я ценю любую помощь.


person bflemi3    schedule 08.04.2013    source источник


Ответы (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.

person nemesv    schedule 11.04.2013
comment
Да, но я могу обработать новый элемент, вызвав self.selectedLocationForEditing(new Location({}, self.types, self.namesExceptCurrent('')));. Это элегантное и простое решение. Спасибо, nemesv! - person bflemi3; 12.04.2013