BackboneJS delete item from a Model array - backbone.js

I have this collection:
var items = new bb.Collections.QuotesCollection([
{id: 1, name: "item 1", units: []},
{id: 2, name: "item 2", units: []},
{id: 3, name: "item 3", units: []}
]);
And then I output the array "units" like so:
if(this.model.get('units').length){
$(this.el).append('<strong>Units</strong>');
$(this.el).append('<ul>');
for(x in this.model.get('units')){
$(this.el).append('<li class="unit">' + this.model.get('units')[x] + '</li>');
}
$(this.el).append('</ul>');
}
The code above is only POC stuff, so no formal templating as yet.
events: {
"keypress #addUnit" : "addUnit",
"dblclick .unit" : "deleteUnit"
},
deleteUnit: function(){
this.render(); // what do I put here!?
}
What approach do I take to delete an item (the clicked one) from the "units" array?

this is the quick and dirty method:
Assuming the Array's order is not changed through any other medium, you could do
deleteUnit: function() {
// get the index of the li you are clicking
var index = $('.unit').index(this);
this.model.get('units').splice(index, 1);
this.render();
}
This way you have to remember to empty your view element before every render
render: function() {
this.$el.empty();
...
// business as usual
}

First, you probably want to have a view object for each model, so you'd have a collection view which owns the <ul> and looks like this:
var ParentView = Backbone.View.extend({
render: function() {
var html = '<ul></ul>'; // make your html here (usually with templates)
this.$el.append(html);
this.collection.each(_.bind(this.initChild, this));
return this; // so we can chain calls if we want to.
}
initChild: function(model) {
var child = new ChildView({ model: model });
// this.$() is scoped to the view's el property
this.$('ul').append(child.render().el);
}
});
You'd then set up the child views something like this:
var ChildView = Backbone.View.extend({
events: { 'click .delete', 'deleteModel' },
render: function() {
var html = '';// make your html here (usually with templates)
this.$el.append(html);
return this;
},
deleteModel: function(ev){
ev.preventDefault();
// Removes form the collection and sends an xhr DELETE
this.model.destroy();
this.$el.remove();
}
});
The call to Model#destroy will take care of removing it from the collection and sending a DELETE to the server (assuming you have a URL set up in your collection/model).

As far as i understand you need to delete item from model
Person = Backbone.Model.extend({
initialize: function() {
alert("Welcome to this world");
}
});
var person = new Person({ name: "Thomas", age: 67});
delete person.name

Related

how to listen to array submodel's value change

I have a model define like below.
var A = Backbone.Model.extends({
initialize: function(){
this.on('change:users.name',this.onUserNameChanged)
},
onUserNameChanged: function(){ alert('name changed')
});
var a = new A ({
id:1,
name:'test',
users:[
{
id:2,
name:'u1'
},
{
id:3,
name:'u4'
}
]
})
I want add event on the each user name change in Model define.
I have no idea to do this.It's seems hard to me.
It seems that you can't get change events by manipulating Backbone model array members as is described here:
backbone-js-set-model-array-property
Your best option is to set the whole array and listen to a change:users event or even better - come up with a different model that has a Backbone collection for users that will get the event when manipulated:
var A = Backbone.Model.extend({
initialize: function (options) {
_.extend(this, options);
}
});
var Users = Backbone.Collection.extend({
initialize: function () {
this.on('change:name', function () {
alert('name changed');
});
}
});
var a = new A({
id: 1,
name: 'test',
users: new Users([{
id: 2,
name: 'u1'
}, {
id: 3,
name: 'u4'
}])
});
a.users.get('2').set('name', 'u2');

Backbone/Underscore uniqueId() Odd Numbers

I'm relatively new to Backbone and Underscore and have one of those questions that's not really an issue - just bugging me out of curiosity.
I built a very simple app that allows you to add and remove models within a collection and renders them in the browser. It also has the ability to console.log the collection (so I can see my collection).
Here's the weird thing: the ID's being generated are 1,3,5... and so on. Is there a reason specific to my code, or something to do with BB/US?
Here's a working Fiddle: http://jsfiddle.net/ptagp/
And the code:
App = (function(){
var AppModel = Backbone.Model.extend({
defaults: {
id: null,
item: null
}
});
var AppCollection = Backbone.Collection.extend({
model: AppModel
});
var AppView = Backbone.View.extend({
el: $('#app'),
newfield: $('#new-item'),
initialize: function(){
this.el = $(this.el);
},
events: {
'click #add-new': 'addItem',
'click .remove-item': 'removeItem',
'click #print-collection': 'printCollection'
},
template: $('#item-template').html(),
render: function(model){
var templ = _.template(this.template);
this.el.append(templ({
id: model.get('id'),
item: model.get('item')
}));
},
addItem: function(){
var NewModel = new AppModel({
id: _.uniqueId(),
item: this.newfield.val()
});
this.collection.add(NewModel);
this.render(NewModel);
},
removeItem: function(e){
var id = this.$(e.currentTarget).parent('div').data('id');
var model = this.collection.get(id);
this.collection.remove(model);
$(e.target).parent('div').remove();
},
printCollection: function(){
this.collection.each(function(model){
console.log(model.get('id')+': '+model.get('item'));
});
}
});
return {
start: function(){
new AppView({
collection: new AppCollection()
});
}
};
});
$(function(){ new App().start(); });
if you look in the backbone.js source code you'll notice that _.uniqueId is used to set a model's cid:
https://github.com/documentcloud/backbone/blob/master/backbone.js#L194
that means that every time you create a model instance, _.uniqueId() is invoked.
that's what causing it to increment twice.

Backbone.js iterating through JSON results from fetch()

I am very new to Backbone and just trying to figure out the way things work. How do I iterate through the "fetched" results?
(function($) {
console.log('Formcontainer init.');
var Field = Backbone.Model.extend({
defaults: {
id: "0",
memberlist_id: "3",
field_name: "sample-field-name",
},
initialize: function() {
console.log('Field model init.');
}
});
var FieldCollection = Backbone.Collection.extend({
defaults: {
model: Field
},
model: Field,
url: 'http://localhost:8888/getstuff/3.json',
initialize: function() {
console.log('FieldCollection init.');
}
});
var ListView = Backbone.View.extend({
el: '#app-container',
initialize: function() {
_.bindAll(this,"render");
console.log('ListView init.')
this.counter = 0;
this.collection = new FieldCollection();
this.collection.fetch();
//this.collection.bind('reset', function() { console.log('xxx')});
//this.collection.fetch();
this.render();
},
events: {
'click #add': 'addItem'
},
render: function() {
var self = this;
console.log('Render called.');
},
addItem: function() {
this.counter++;
this.$("ul").append("<li>hello world" + this.counter + "</li>");
}
});
var listView = new ListView();
})(jQuery);
I get this in my Firebug console:
Formcontainer init.
ListView init.
FieldCollection init.
GET http://localhost:8888/getstuff/3.json 200 OK
Render called.
Field model init.
Field model init.
Field model init.
Field model init.
Field model init.
It seems that the fetch() was called - since "Field model init." is in the console 5 times. But how to I output that? I would want to append the items in an unordered list.
Thanks!
Bind the reset event to your render function...
this.collection.bind('reset', this.render, this);
This gets triggered after the fetch is complete, so you can create the list...
render: function() {
var self = this;
console.log('Render called.');
this.collection.each(function(i,item) {
this.$el.append("<ul>" + item.get("field_name") + "</ul>");
});
},

backbone.js: add item to collection right after current item

I'm trying to create a invoice rows list with backbone and one of the buttons/events is that when You click "add" on an existing row, it should add a new row right below the one that's "add" button was clicked.
First, I am not sure if I should handle this event in the InvoiceItemView or InvoiceItemListView, and second, I am not sure how to do this. Can anybody help me?
I tried this:
$(function(){
// Invoice row
var InvoiceItem = Backbone.Model.extend({
// Set default values
defaults : {
name: "",
quantity: 1,
price: "",
total: ""
},
// Ensure each item has at least a quantity of one
initialize: function() {
this.set({ "quantity" : this.defaults.quantity });
},
// Delete item (row) from invoice
clear: function() {
this.destroy();
}
});
// Collection of Invoice Items ( rows )
var InvoiceItemList = Backbone.Collection.extend({
// Colelction's model
model: InvoiceItem,
// Use localStorage to save data
localStorage: new Store("invoices-backbone"),
// Calculate invoice totals
calculate_totals: function() {
alert('arvutus');
},
// Generate next item order number
nextOrder: function() {
if (!this.length) return 1;
return this.last().get('order') + 1;
}
});
// Create a global colelction of Invoice Rows
var InvoiceItems = new InvoiceItemList;
// Invoice Item View
var InvoiceItemView = Backbone.View.extend({
// it's a tr tag
tagName: 'tr',
// Cache the template
template: _.template($('#invoiceitem-template').html()),
events: {
"click .remove" : "clear",
"click .add" : "addAfter"
},
initialize: function() {
_.bindAll(this, 'render', 'addAfter', 'remove');
this.model.bind('change', this.render);
this.model.bind('destroy', this.remove);
this.render();
},
// Render the contents
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
$(this.el).attr('id', 'item-' + this.model.get('order'));
return this;
},
// Remove and destroy the item
clear: function() {
this.model.clear();
},
// Add new item
addAfter: function(ItemId) {
var view = new InvoiceItemView({model: InvoiceItem});
this.$("tbody tr#item"+ItemId).after(view.render().el);
}
});
// Invoice Item List View
InvoiceItemListView = Backbone.View.extend({
// Bind to existing DOM element
el: $("#items"),
// Template for displaying totals
totalsTemplate: _.template($('#totals-template').html()),
// Kick-off (load from localStorage as well)
initialize: function() {
_.bindAll(this, 'addOne', 'render');
InvoiceItems.bind('add', this.addOne);
InvoiceItems.fetch();
InvoiceItems.create(this.newAttributes());
InvoiceItems.create(this.newAttributes());
InvoiceItems.create(this.newAttributes());
this.render();
},
// Re-render the totals
render: function() {
this.$('#totals').html(this.totalsTemplate({
total: this.total
}));
},
// Generate the attributes for a new Invoice Item.
newAttributes: function() {
return {
name: '',
price: '',
total: '',
order: InvoiceItems.nextOrder()
};
},
// Add a single invoice item to the list
addOne: function(InvoiceItem) {
var view = new InvoiceItemView({model: InvoiceItem});
this.$("tbody").append(view.render().el);
}
});
var iView = new InvoiceItemListView;
});
But I am getting an error: this.model.toJSON is not a function
The problem is where you create your new InvoiceItemView. You have to create a new instance of InvoiceItem
// Add new item
addAfter: function(ItemId) {
var view = new InvoiceItemView({model: new InvoiceItem()});
this.$("tbody tr#item"+ItemId).after(view.render().el);
}

Backbone.js - Correct way of filtering and displaying collection data in a view

I have got a huge list of tasks loaded on the start.
I want to show them depending on selected list / inbox, so that there won't be additional loadings for each list.
window.Task = Backbone.Model.extend({});
window.TasksCollection = Backbone.Collection.extend({
model: Task,
url: '/api/tasks',
inbox: function() {
return this.filter(function(task) {
return task.get('list') == null;
});
},
list: function(id) {
return this.filter(function(task) {
return task.get('list') == id;
});
}
});
window.tasks = new TasksCollection;
window.TaskView = Backbone.View.extend({
tagName: 'li',
template: _.template($('#item-template').html()),
initialize: function() {
_.bindAll(this, 'render', 'close');
this.model.bind('change', this.render);
this.model.view = this;
},
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
this.setContent();
return this;
},
});
window.TasksView = Backbone.View.extend({
el: '#todo-list',
collection: tasks,
initialize: function() {
_.bindAll(this, 'render');
this.collection.bind('reset', this.render);
this.collection.fetch();
},
render: function() {
var t = this;
$(t.el).html('');
this.collection.each(function(task) {
var view = new TaskView({ model:task });
$(t.el).append( view.render().el );
});
return this;
},
});
window.Nicetask = Backbone.Router.extend({
routes: {
'': 'inbox',
'/inbox': 'inbox',
'/list/:id': 'list',
},
initialize: function() {
_.bindAll(this, 'inbox', 'list');
window.tasksView = new TasksView;
},
inbox: function() {
tasks.reset( tasks.inbox() );
},
list: function(id) {
tasks.reset( tasks.list(id) );
}
});
This code works, but the reset() function removes other tasks in actual list from tasks collection. And on another route, tasks collection is empty.
Is there any reasonable way to achieve this? thanks for any idea.
ps: backbone novice
UPDATE
Thx to #sled and #ibjhb for comments, here is snippet of working solution.
window.TasksView = Backbone.View.extend({
el: '#todo-list',
collection: Backbone.Collection.extend(),
initialize: function() {
_.bindAll(this, 'render', 'addOne', 'addAll');
this.collection.bind('add', this.addOne);
this.collection.bind('reset', this.render);
},
render: function(data) {
$(this.el).html('');
_.each(data, function(task) {
this.addOne(task);
}, this);
return this;
},
addOne: function(task) {
var view = new TaskView({ model:task });
$(this.el).append( view.render().el );
},
});
window.Nicetask = Backbone.Router.extend({
routes: {
'': 'inbox',
'/inbox': 'inbox',
'/today': 'today',
'/list/:id': 'list',
},
initialize: function() {
_.bindAll(this, 'inbox', 'today');
window.tasksView = new TasksView;
window.menuView = new MenuListView;
tasks.fetch();
},
inbox: function() {
tasksView.render( tasks.inbox() );
},
today: function() {
tasksView.render( tasks.today() );
},
list: function(id) {
tasksView.render( tasks.list(id) );
}
});
I think you need to use another collection. For example, in your inbox, do this:
inbox: function(){
currentCollection = new TasksCollection(tasks.inbox());
}
I haven't tested this but when you do a .reset(); you are removing all your models and loading the ones passed in.
#sled there's typos in the code you posted, see comments inline. Did you post this as a project somewhere?
// add models
add: function(models, options) {
// TYPO: next line was missing, so single models not handled.
models = _.isArray(models) ? models.slice() : [models];
var self = this;
models = _.filter(models, this.filter);
// return if no models exist
// TYPO: returned undefined, so was not chainable
if(models.length == 0) { return this; }
// actually add the models to the superset
this.superset.add(models, options);
return this;
},
// remove models
remove: function(models, options) {
// TYPO: next line was missing, so single models not handled.
models = _.isArray(models) ? models.slice() : [models];
// remove model from superset
this.superset.remove(_.filter(_.filter(models, function(cm) {
// TYPO: not 'm != null', causes error to be thrown
return cm != null;
}), this.filter), options);
// TYPO: missing return so not chainable
return this;
},
one quick amendment to you solution, you are using
$(this.el).html('');
My understanding is your the views and related event bindings will still exist in the browser memory, so you ideally need to use view.remove() on the TaskView to correctly clear the event bindings as well as the html.
This is a slightly different take on the answer as I have been looking for a solution to a similar problem, hope this may be of help to others.
My problem: - to filter a complete collection by attributes of the model. eg. a user clicks the models view, gets a list of (some of) the attributes, selecting an attribute filters the collection to only show ones with the same value.
The route I am taking is by calling a method on the collection from the view, in my case the view is specific to a model so:
this.model.collection.myFilter(attr,val);
where attr is an attribute of the model associated with the collection, then in the filter something like
myFilter: function(attr, val){
var groupByAttr = this.groupBy(function(article){
var res = (val === undefined)? true : (article.get(attr) == val);
article.set({selected:res});
return res;
});
return groupByAttr;
}
I have used ._groupBy as this returns 2 arrays (positive / negative) that may be of use. By setting the mode attribute "selected", and binding to this in the model view I can easily toggle a class which shows or hides the view.
if(val === undefined) is added as a simple way of clearing a filter by calling the same method without a value.

Resources