I'm working on a Backbone application where I allow the user to Add multiple items.
Here's my Model:
//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;
}
});
I allow a user to Add/Delete items from the view as:
//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.');
}
});
Now when a user starts the application, he/she will be presented with the screen:
When user clicks on Add, item would be added like:
I've put in hardcoded validation where 1st and 3rd added element would return error when user clicks on SAVE.
Where I'm stuck is how do I show those error only at that particular item view. For instance, if there's an error at 1st and 3rd item, then the model returns that error but I want to map that error to the 1st and 3rd list-item only, much like this:
Please help me suggest ways to approach it. Thanks in advance!
UPDATE: I've found a fix to that. So whenever there's a validation error, I do something like this:
world1Err: function(){
this.$el.find('span.error').text('Error Occured.')
},