如何使用Backbone.js报告无效的表单字段

如何使用Backbone.js报告无效的表单字段,backbone.js,validation,Backbone.js,Validation,我使用主干来管理HTML表单的状态。模型的作用是处理验证。视图的作用是包装HTML表单并响应模型发出的更改或错误事件 主干似乎只在给定字段实际有效时发出change事件。这导致了一些意想不到的行为,让我觉得我做错了 以下是我所做工作的总结: 1.初始加载序列化表单并将其注入模型 2.当发出错误事件时,我会在无效字段旁边生成错误节点。 3.当发出change事件时,我会删除(现在有效)字段旁边的错误注释 当使用初始有效表单呈现页面,并且用户使字段无效时,消息按预期显示;但是,模型从不在内部更新字段

我使用主干来管理HTML表单的状态。模型的作用是处理验证。视图的作用是包装HTML表单并响应模型发出的
更改
错误
事件

主干似乎只在给定字段实际有效时发出
change
事件。这导致了一些意想不到的行为,让我觉得我做错了

以下是我所做工作的总结: 1.初始加载序列化表单并将其注入模型 2.当发出
错误
事件时,我会在无效字段旁边生成错误节点。 3.当发出
change
事件时,我会删除(现在有效)字段旁边的错误注释

当使用初始有效表单呈现页面,并且用户使字段无效时,消息按预期显示;但是,模型从不在内部更新字段。因此,当用户纠正错误时,永远不会发出
change
事件

当一个页面以一个最初无效的表单呈现时,一切看起来都很正常。。。但这只是因为模型的初始属性为空。更正字段会使消息消失,但如果再次将其更改为无效状态,消息将永远不会消失

我做错了什么?也许我应该用另一种方法来代替

我的模型 我的看法
FooForm=Backbone.View.extend({
活动:{
“更改:输入”:“onFieldChange”
},
初始化:函数(选项){
this.model.on('error',this.renderErrors,this);
this.model.on('change',this.updateFields,this);
//仅调试
this.model.on('all',function()){
console.info(“[Foo all]”,参数,this.toJSON()
});
this.model.set(this.serialize());
},
onFieldChange:函数(事件){
变量字段=event.target,
name=field.name,
value=field.value;
此.model.set(名称、值);
},
渲染器:函数(模型、错误){
_.每个(错误、功能(消息、字段名){
变量el=$(“#”+字段名),
警报=$('').addClass('error');
el.parent().find('.error').remove();
_.每个(消息、功能(消息){
alert.clone().text(message).insertAfter(el);
});
});
},
updateFields:函数(模型、选项){
如果(!options | |!options.changes)返回;
_.each(u.keys(options.changes)、函数(fieldName){
变量el=$(“#”+字段名);
el.parent().find('.error').remove();
});
},
序列化:函数(){
var raw=this.$el.find(':input').serializeArray(),
数据={},
视图=此;
$.each(原始,函数(){
//从表单字段的名称中获取模型的字段名称
var name=this.name;
if(数据[名称]!==未定义){
如果(!data[name].push){
数据[名称]=[数据[名称];
}
数据[名称].push(this.value | |“”);
}
否则{
数据[名称]=this.value | |“”;
}
});
返回数据;
}
});

无法使用本机主干验证来验证单个字段

在我的应用程序中,我使用此验证插件:

然后在模型中为每个字段添加验证规则(这是可选的,因此不需要将其添加到所有模型):


我确实考虑过Brase.ValueStudio,但是它的许可证与我的应用程序不兼容,所以我不得不自己翻滚。我想总体来说,这是大多数应用程序的解决方案。我的问题是,我假设当我
model.set('field',…)
时,验证也会起同样的作用。谢谢你的回答。
var Foo = Backbone.Model.extend({
    validate: function(attr) {
        var errors = {};

        if (_.isEmpty(attr)) return;

        if (attr.foo && attr.foo != 123) {
            errors.foo = ['foo is not equal to 123'];
        }

        if (attr.bar && attr.bar != 456) {
            errors.bar = ['bar is not equal to 456'];
        }

        return _.isEmpty(errors) ? undefined : errors;
    }
});
FooForm = Backbone.View.extend({
    events: {
        'change :input': 'onFieldChange'
    },

    initialize: function(options) {
        this.model.on('error', this.renderErrors, this);
        this.model.on('change', this.updateFields, this);

        // Debugging only
        this.model.on('all', function() {
            console.info('[Foo all]', arguments, this.toJSON())
        });

        this.model.set(this.serialize());
    },

    onFieldChange: function(event) {
        var field = event.target,
            name = field.name,
            value = field.value;

        this.model.set(name, value);
    },

    renderErrors: function(model, errors) {
        _.each(errors, function(messages, fieldName) {
            var el = $('#' + fieldName),
                alert = $('<div/>').addClass('error');

            el.parent().find('.error').remove();

            _.each(messages, function(message) {
                alert.clone().text(message).insertAfter(el);
            });
        });
    },

    updateFields: function(model, options) {
        if (!options || !options.changes) return;

        _.each(_.keys(options.changes), function(fieldName) {
            var el = $('#' + fieldName);

            el.parent().find('.error').remove();
        });
    },

    serialize: function() {
        var raw = this.$el.find(':input').serializeArray(),
            data = {},
            view = this;

        $.each(raw, function() {
            // Get the model's field name from the form field's name
            var name = this.name;

            if (data[name] !== undefined) {
                if (!data[name].push) {
                    data[name] = [data[name]];
                }

                data[name].push(this.value || '');
            }
            else {
                data[name] = this.value || '';
            }
        });
        return data;

    }
});
var NewReview = Backbone.Model.extend({
  initialize: function() {
     /* ... */
  },

  validation: {
    summary: {
      required: true,
      minLength: 10
    },
    pros: {
      required: true,
      minLength: 10
    },
    cons: {
      required: true,
      minLength: 10
    },
    overall: function(value) {
      var text = $(value).text().replace(/\s{2,}/g, ' ');
      if (text.length == 0) text = value;
      if (text.length < 20) return "Overall review is too short";
    },
    rating: {
      range: [0.5, 5]
    },
    product_id: {
      required: true
    }
  }
});
if (this.model.validate()) { ... }
if (this.model.isValid("summary")) { ... }