【问题标题】:Using knockout validation to validate specific items inside an observable array使用敲除验证来验证可观察数组中的特定项目
【发布时间】:2015-12-29 20:00:23
【问题描述】:

我有一个可观察的 formFields 数组,它呈现到我的表单上的输入/选择字段列表。

每个字段都是一个单独的 viewModel,并根据其自己的 fieldType 属性(例如 - firstName、lastName、creditCardNumber 等)呈现。

在数据库中,我保留了 formTypes 定义。对于每个 formType,都有一个针对该表单类型显示的所有字段的映射(例如 - formType=Address 将具有此 formField 列表 - 街道、城市、国家/地区、邮政编码等)。

这样,我可以根据表单定义及其对应的formFields动态创建不同的表单。

现在,我的问题是我想使用敲除验证来验证 formFields 的 observableArray 本身(除了每个特定的 formField 单独验证,它工作正常)。我的意思是对具有字段间依赖关系的整个表单进行验证。例如:

银行账户表格:

字段:分行编号、帐号、账户持有人姓名、银行名称 验证:(分行号 + 帐号)* 13 % 6 == 3(只是一个示例)。

字段 BranchNumber 和 AccountNumber 是 formFields observableArray 中的字段 viewModel,每次 observableArray 中的项目发生更改时,我都需要为表单运行验证。

我的问题是,当数组内的字段值发生变化时,字段间验证不会重新评估 errors() 属性。

有人可以帮忙吗?

我自己的示例代码比我描述的要复杂得多,这就是我没有在此处发布的原因。我希望这已经足够清楚了。谢谢!


简短的例子(validate只在表单初始化时调用,每次observableArray内部的值发生变化时如何让它运行)

function FormField(formField, parentForm) {
var self = this;

self.detail = ko.observable().extend({
    validation: {
        validator: function(val) {
            isValid = false;
            if (typeof self.requiredFieldType === "undefined") {
                isValid = true;
            } else if ([3, 5, 22].indexOf(self.requiredFieldType) > -1) { // Email, Product Account Email, Giftcard Delivery Email
                isValid = val !== "" && emailPattern.test($.trim(val));
            } else {
                isValid = $.trim(val) !== "";
            }
            return isValid;
        },
        message: function() {
            if (self.requiredFieldType === 2) {
                return 'Please enter a valid Zip Code.';
            } else if (self.requiredFieldType === 3 || self.requiredFieldType === 22) {
                return 'Invalid Email.';
            } else if (self.requiredFieldType === 5) {
                return 'Invalid Account Email.';
            } else if (self.requiredFieldType === 6) {
                return 'Please select your iTunes store.';
            } else if (self.requiredFieldType === 7) {
                return 'Please enter a phone number.';
            } else if (self.requiredFieldType === 9) {
                return 'Please enter frequent flyer number.';
            } else if (self.requiredFieldType === 10) {
                return 'Please enter street.';
            } else if (self.requiredFieldType === 11) {
                return 'Please enter house #.';
            } else if (self.requiredFieldType === 12) {
                return 'Please enter city.';
            } else if (self.requiredFieldType === 13 || self.requiredFieldType === 23) {
                return 'Please enter country.';
            } else if (self.requiredFieldType === 15) {
                return 'Please enter bank account number.';
            } else if (self.requiredFieldType === 16) {
                return 'Please enter bank account holder name.';
            } else if (self.requiredFieldType === 17) {
                return 'Please enter bank name.';
            } else if (self.requiredFieldType === 18) {
                return 'Please enter bank branch number.';
            } else if (self.requiredFieldType === 19) {
                return 'Please enter first name.';
            } else if (self.requiredFieldType === 20) {
                return 'Please enter last name.';
            } else if (self.requiredFieldType === 21) {
                return 'Please select a currency.';
            }
        }
    }
}).extend({
    required: {
        message: 'This field is required.'
    }
});


self.requiredFieldType = formField.RequiredFieldType;
self.errors = ko.validation.group(self);
}
}

function Form(form) {
var self = this;

self.formName = form.ProductName;
self.formFields = ko.observableArray($.map(form.FormFields, function(jsonFormField) {
    return new FormField(jsonFormField, self)
})).extend({
    validation: {
        validator: function(val) {
            return false; // this is being called only once
        },
        message: function() {}
    }
});

self.errors = ko.validation.group(self);
}

【问题讨论】:

  • 恐怕我们必须查看您的数据结构才能了解它是如何工作的。你能把一个例子(比如你的银行账户表格)提炼成一个问题的最小演示吗?有时在制作这些最小的示例时,解决方案会立即跳出来。
  • @RoyJ 现在好点了吗?谢谢。

标签: validation knockout.js knockout-validation


【解决方案1】:

observableArray 的验证器仅在其上有可订阅事件时才会被调用,其中不包括修改包含数组的元素。你可以告诉数组它已经被更新了

self.formFields.valueHasMutated();

您需要为每个元素附加订阅,并调用valueHasMutated 订阅,可能类似于:

function validateParent () {
  self.formFields.valueHasMutated();
}
ko.utils.arrayForEach(self.formFields(), function (item) {
  item.detail.subscribe(validateParent);
});

更新:进一步调查,我发现this link,其中提到了ko.validation.validateObservable(),这似乎比valueHasMutated 更中肯。

它们都不会导致重新评估所有字段。这是一个包含标签和“最后验证”字段的 sn-p,以说明何时更改。请注意,各个字段的标签每次更改时都会更新,但不会在其他字段更改时更新,即使每次更改都会更新上次验证时间。

我已将 valueHasMutated 注释掉以支持 validateObservable,但它们的工作方式完全相同。

vm = {
    lastValidated: ko.observable(0)
};

vm.v = ko.observableArray([]).extend({
    validation: {
        validator: function (val) {
            vm.lastValidated((new Date()).getMilliseconds());
        }
    }
});

function addMember(name) {
    var item = ko.observable(name);
    item.subscribe(function (newValue) {
        ko.validation.validateObservable(vm.v);
        //vm.v.valueHasMutated();
    });
    vm.v.push({
        data: item
    });
}

addMember('one');
addMember('two');

ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/knockout-validation/2.0.3/knockout.validation.min.js"></script>
<div data-bind="foreach:v">
    <input data-bind="value:data, valueUpdate:'input'" />
</div>
<div data-bind="foreach:v">
    <div data-bind="text:data()+((new Date()).getMilliseconds())"></div>
</div>
<div data-bind="text: lastValidated"></div>

【讨论】:

  • 这不会导致每次更改字段值时刷新所有字段的视图吗?有没有办法在不刷新整个视图的情况下设置这种验证?也许基于订阅或以某种方式手动强制验证逻辑运行?
  • @UriAbramson 公平的问题,让我思考了一下。但是不, valueHasMutated 只会导致突变的 observable 的订阅者更新。查看更新的答案。
猜你喜欢
  • 1970-01-01
  • 2013-01-13
  • 2012-11-02
  • 2013-07-17
  • 2014-09-28
  • 2017-12-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-01
相关资源
最近更新 更多