Backbone:在 VIEW 上的 collection 中显示每个模型的验证错误
Backbone: Show validation errors for each model in a collection on VIEW
我正在开发一个 Backbone 允许用户添加多个项目的应用程序。
这是我的 型号:
//Model
var Item = Backbone.Model.extend({
defaults: {
part1: 'hello',
part2: 'world'
},
validate: function (attr, options) {
var error = '';
//console.log(attr);
if(attr.part2 == "world1"){
this.trigger('err:world1');
error = 'world 1 error';
}
if(attr.part2 == "world3"){
this.trigger('err:world3');
error = 'world 3 error';
}
}
});
Collection:
//Collection
var List = Backbone.Collection.extend({
model: Item,
validateModels: function() {
var cloneCollection = this.clone();
var errorModels = this.filter(function(m) {
if (!m.isValid()) {
return m;
}
});
// cloneCollection.remove(errorModels);
return cloneCollection;
}
});
我允许用户 Add/Delete 项的视图为:
//Item View
var ItemView = Backbone.View.extend({
tagName: 'li', // name of tag to be created
events: {
'click span.swap': 'swap',
'click span.delete': 'remove'
},
initialize: function(){
_.bindAll(this, 'render', 'unrender', 'swap', 'remove'); // every function that uses 'this' as the current object should be in here
this.model.bind('change', this.render);
this.model.bind('remove', this.unrender);
this.model.on('err:world1', this.world1Err);
this.model.on('err:world3', this.world3Err);
},
render: function(){
$(this.el).html('<span style="color:black;">'+this.model.get('part1')+' '+this.model.get('part2')+'</span> <span class="swap" style="font-family:sans-serif; color:blue; cursor:pointer;">[swap]</span> <span class="delete" style="cursor:pointer; color:red; font-family:sans-serif;">[delete]</span> <span class="error" style="color:red; font-family:sans-serif;"></span>');
return this; // for chainable calls, like .render().el
},
unrender: function(){
$(this.el).remove();
},
swap: function(){
var swapped = {
part1: this.model.get('part2'),
part2: this.model.get('part1')
};
this.model.set(swapped);
},
remove: function(){
this.model.destroy();
},
world1Err: function(){
alert('World 1 Err');
//console.log(this);
},
world3Err: function(){
alert('World 3 Err');
}
});
//Composite View
var ListView = Backbone.View.extend({
el: $('body'), // el attaches to existing element
events: {
'click button#add': 'addItem',
'click button#save': 'saveCollection'
},
initialize: function(){
_.bindAll(this, 'render', 'addItem', 'appendItem'); // every function that uses 'this' as the current object should be in here
this.collection = new List();
this.collection.bind('add', this.appendItem); // collection event binder
this.counter = 0;
this.render();
},
render: function(){
var self = this;
$(this.el).append("<button id='add'>Add list item</button>");
$(this.el).append("<button id='save'>SAVE</button>");
$(this.el).append("<ul></ul>");
_(this.collection.models).each(function(item){ // in case collection is not empty
self.appendItem(item);
}, this);
},
addItem: function(){
this.counter++;
var item = new Item();
item.set({
part2: item.get('part2') + this.counter // modify item defaults
});
this.collection.add(item);
},
appendItem: function(item){
var itemView = new ItemView({
model: item
});
$('ul', this.el).append(itemView.render().el);
},
saveCollection: function(){
var collectionLength = this.collection.length;
if(collectionLength > 0){
this.collection.validateModels();
//console.log(status);
}
else
alert('Collection is empty. Please add something.');
}
});
现在,当用户启动应用程序时,he/she 将显示屏幕:
当用户点击添加时,项目将被添加如下:
我已经进行了硬编码验证,当用户单击 SAVE.
时,第一个和第三个添加的元素会 return 出错
卡住的地方 是如何仅在特定项目视图中显示这些错误。 例如,如果第1st和3rd项出现错误,则模型return是那个错误,但我想将该错误映射到 1st 和 3rd list-item,就像这样:
请帮我提出解决方法。提前致谢!
更新:
我找到了解决方法。因此,每当出现验证错误时,我都会执行以下操作:
world1Err: function(){
this.$el.find('span.error').text('Error Occured.')
},
主要注意事项:
- 不要使用
$(this.el)
,而是使用 this.$el
- 使用
listenTo
而不是 on
(绑定)以避免内存泄漏( 额外的好处是回调将以侦听器作为上下文触发,在您的情况下 view
)
- 不要重写
Backbone.View
的 remove
方法,除非您知道自己在做什么并且自己处理它所做的所有事情
聪明的举动:
- 使用backbone事件哈希绑定的事件处理程序的默认上下文是视图本身,以及
listenTo
的使用,无需使用_.bindAll
- Backbone 集合有 lots of underscore methods built in,你可以用
this.collection.each
代替 _(this.collection.models).each
- 您可以随意使用下划线,使用它的
template
方法而不是手动生成模板
- 您可以快速执行
this.$(selector)
而不是 this.$el.find(selector)
、$(selector, this.el)
等
- 无需像
new Item()
那样手动创建模型实例,设置它的属性然后将其添加到集合中,只需将属性传递给集合add
方法,它就会创建一个模型内部实例
- 您可以使用集合长度而不是手动跟踪计数 属性
建议:
- 不使用内联样式
- 让项目视图自行呈现,并使用
view.el
而不是 view.render().el
(我真的不知道是谁发明了这种方式,也不知道为什么)
您可以概括您的代码,如下所示:
var Item = Backbone.Model.extend({
defaults: {
message: 'hello world',
count: 0
},
validate: function(attr, options) {
if (attr.count % 2 != 0) {
this.trigger('err', this.get('message') + attr.count + ' error');
}
}
});
var List = Backbone.Collection.extend({
model: Item,
validateModels: function() {
this.each(function(m) {
m.isValid(); // invoke models validate method
});
}
});
var ItemView = Backbone.View.extend({
tagName: 'li',
template: _.template($('#item').text()),
events: {
'click span.swap': 'swap',
'click span.delete': 'remove' // triggers view's built in remove method
},
initialize: function() {
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'err', this.errorHandler);
this.render();
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
swap: function() {
var words = this.model.get('message').split(' ');
this.model.set({
message: words.reverse().join(' ')
});
},
errorHandler: function(msg) {
this.$('span.error').text(msg)
}
});
var ListView = Backbone.View.extend({
template: $('#itemView').text(),
events: {
'click button#add': 'addItem',
'click button#save': 'saveCollection'
},
initialize: function() {
this.collection = new List();
this.listenTo(this.collection, 'add', this.appendItem);
this.render();
},
render: function() {
this.$el.html(this.template);
this.collection.each(function(model) {
this.appendItem(model);
}, this);
},
addItem: function() {
this.collection.add({
count: this.collection.length
}, {
validate: true
});
},
appendItem: function(item) {
this.$('ul').append(new ItemView({
model: item
}).el);
},
saveCollection: function() {
if (this.collection.length > 0) {
this.collection.validateModels();
} else
alert('Collection is empty. Please add something.');
}
});
new ListView().$el.appendTo('body');
li span {
font-family: sans-serif;
}
span.control {
cursor: pointer;
}
span.swap {
color: blue;
}
span.delete {
color: orange;
}
span.error {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.2.3/backbone-min.js"></script>
<script type="text/template" id="item">
<span><%=message%> <%=count? count: ''%></span>
<span class="swap control">[swap]</span>
<span class="delete control">[delete]</span>
<span class="error"></span>
</script>
<script type="text/template" id="itemView">
<button id='add'>Add list item</button>
<button id='save'>SAVE</button>
<ul></ul>
</script>
我正在开发一个 Backbone 允许用户添加多个项目的应用程序。
这是我的 型号:
//Model
var Item = Backbone.Model.extend({
defaults: {
part1: 'hello',
part2: 'world'
},
validate: function (attr, options) {
var error = '';
//console.log(attr);
if(attr.part2 == "world1"){
this.trigger('err:world1');
error = 'world 1 error';
}
if(attr.part2 == "world3"){
this.trigger('err:world3');
error = 'world 3 error';
}
}
});
Collection:
//Collection
var List = Backbone.Collection.extend({
model: Item,
validateModels: function() {
var cloneCollection = this.clone();
var errorModels = this.filter(function(m) {
if (!m.isValid()) {
return m;
}
});
// cloneCollection.remove(errorModels);
return cloneCollection;
}
});
我允许用户 Add/Delete 项的视图为:
//Item View
var ItemView = Backbone.View.extend({
tagName: 'li', // name of tag to be created
events: {
'click span.swap': 'swap',
'click span.delete': 'remove'
},
initialize: function(){
_.bindAll(this, 'render', 'unrender', 'swap', 'remove'); // every function that uses 'this' as the current object should be in here
this.model.bind('change', this.render);
this.model.bind('remove', this.unrender);
this.model.on('err:world1', this.world1Err);
this.model.on('err:world3', this.world3Err);
},
render: function(){
$(this.el).html('<span style="color:black;">'+this.model.get('part1')+' '+this.model.get('part2')+'</span> <span class="swap" style="font-family:sans-serif; color:blue; cursor:pointer;">[swap]</span> <span class="delete" style="cursor:pointer; color:red; font-family:sans-serif;">[delete]</span> <span class="error" style="color:red; font-family:sans-serif;"></span>');
return this; // for chainable calls, like .render().el
},
unrender: function(){
$(this.el).remove();
},
swap: function(){
var swapped = {
part1: this.model.get('part2'),
part2: this.model.get('part1')
};
this.model.set(swapped);
},
remove: function(){
this.model.destroy();
},
world1Err: function(){
alert('World 1 Err');
//console.log(this);
},
world3Err: function(){
alert('World 3 Err');
}
});
//Composite View
var ListView = Backbone.View.extend({
el: $('body'), // el attaches to existing element
events: {
'click button#add': 'addItem',
'click button#save': 'saveCollection'
},
initialize: function(){
_.bindAll(this, 'render', 'addItem', 'appendItem'); // every function that uses 'this' as the current object should be in here
this.collection = new List();
this.collection.bind('add', this.appendItem); // collection event binder
this.counter = 0;
this.render();
},
render: function(){
var self = this;
$(this.el).append("<button id='add'>Add list item</button>");
$(this.el).append("<button id='save'>SAVE</button>");
$(this.el).append("<ul></ul>");
_(this.collection.models).each(function(item){ // in case collection is not empty
self.appendItem(item);
}, this);
},
addItem: function(){
this.counter++;
var item = new Item();
item.set({
part2: item.get('part2') + this.counter // modify item defaults
});
this.collection.add(item);
},
appendItem: function(item){
var itemView = new ItemView({
model: item
});
$('ul', this.el).append(itemView.render().el);
},
saveCollection: function(){
var collectionLength = this.collection.length;
if(collectionLength > 0){
this.collection.validateModels();
//console.log(status);
}
else
alert('Collection is empty. Please add something.');
}
});
现在,当用户启动应用程序时,he/she 将显示屏幕:
当用户点击添加时,项目将被添加如下:
我已经进行了硬编码验证,当用户单击 SAVE.
时,第一个和第三个添加的元素会 return 出错卡住的地方 是如何仅在特定项目视图中显示这些错误。 例如,如果第1st和3rd项出现错误,则模型return是那个错误,但我想将该错误映射到 1st 和 3rd list-item,就像这样:
请帮我提出解决方法。提前致谢!
更新: 我找到了解决方法。因此,每当出现验证错误时,我都会执行以下操作:
world1Err: function(){
this.$el.find('span.error').text('Error Occured.')
},
主要注意事项:
- 不要使用
$(this.el)
,而是使用this.$el
- 使用
listenTo
而不是on
(绑定)以避免内存泄漏( 额外的好处是回调将以侦听器作为上下文触发,在您的情况下view
) - 不要重写
Backbone.View
的remove
方法,除非您知道自己在做什么并且自己处理它所做的所有事情
聪明的举动:
- 使用backbone事件哈希绑定的事件处理程序的默认上下文是视图本身,以及
listenTo
的使用,无需使用_.bindAll
- Backbone 集合有 lots of underscore methods built in,你可以用
this.collection.each
代替_(this.collection.models).each
- 您可以随意使用下划线,使用它的
template
方法而不是手动生成模板 - 您可以快速执行
this.$(selector)
而不是this.$el.find(selector)
、$(selector, this.el)
等 - 无需像
new Item()
那样手动创建模型实例,设置它的属性然后将其添加到集合中,只需将属性传递给集合add
方法,它就会创建一个模型内部实例 - 您可以使用集合长度而不是手动跟踪计数 属性
建议:
- 不使用内联样式
- 让项目视图自行呈现,并使用
view.el
而不是view.render().el
(我真的不知道是谁发明了这种方式,也不知道为什么)
您可以概括您的代码,如下所示:
var Item = Backbone.Model.extend({
defaults: {
message: 'hello world',
count: 0
},
validate: function(attr, options) {
if (attr.count % 2 != 0) {
this.trigger('err', this.get('message') + attr.count + ' error');
}
}
});
var List = Backbone.Collection.extend({
model: Item,
validateModels: function() {
this.each(function(m) {
m.isValid(); // invoke models validate method
});
}
});
var ItemView = Backbone.View.extend({
tagName: 'li',
template: _.template($('#item').text()),
events: {
'click span.swap': 'swap',
'click span.delete': 'remove' // triggers view's built in remove method
},
initialize: function() {
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'err', this.errorHandler);
this.render();
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
swap: function() {
var words = this.model.get('message').split(' ');
this.model.set({
message: words.reverse().join(' ')
});
},
errorHandler: function(msg) {
this.$('span.error').text(msg)
}
});
var ListView = Backbone.View.extend({
template: $('#itemView').text(),
events: {
'click button#add': 'addItem',
'click button#save': 'saveCollection'
},
initialize: function() {
this.collection = new List();
this.listenTo(this.collection, 'add', this.appendItem);
this.render();
},
render: function() {
this.$el.html(this.template);
this.collection.each(function(model) {
this.appendItem(model);
}, this);
},
addItem: function() {
this.collection.add({
count: this.collection.length
}, {
validate: true
});
},
appendItem: function(item) {
this.$('ul').append(new ItemView({
model: item
}).el);
},
saveCollection: function() {
if (this.collection.length > 0) {
this.collection.validateModels();
} else
alert('Collection is empty. Please add something.');
}
});
new ListView().$el.appendTo('body');
li span {
font-family: sans-serif;
}
span.control {
cursor: pointer;
}
span.swap {
color: blue;
}
span.delete {
color: orange;
}
span.error {
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.2.3/backbone-min.js"></script>
<script type="text/template" id="item">
<span><%=message%> <%=count? count: ''%></span>
<span class="swap control">[swap]</span>
<span class="delete control">[delete]</span>
<span class="error"></span>
</script>
<script type="text/template" id="itemView">
<button id='add'>Add list item</button>
<button id='save'>SAVE</button>
<ul></ul>
</script>