How to clone a backbone collection - backbone.js

Is there a way to easily clone Backbone Collection? I wonder why there is no build in method like for models. My problem is that I have a model holding a collection of childs. When I clone the model. I've still the collection of childs but only with their default values.

Simplest way:
var cloned = new Backbone.Collection(original.toJSON());
where original is the collection to clone.
Could always then extend Backbone's Collection to add clone support w or w/o inheritance support.

What's your use case that you want to clone the collection?
There isn't a built in clone function for a collection because you do not want to clone the models within the collection. Cloning a model would cause there to be two separate instances of the same model, and if you update one model, the other one won't be updated.
If you want to create a new collection based upon a certain criteria, then you can use the collection's filter method.
var freshmenModels = studentsCollection.filter(function(student) {
return student.get('Year') === 'Freshman';
}
var freshmenCollection = new Backbone.Collection(freshmenModels);
To go ahead and clone the models in the collection, you can write the following code
var clonedCollection = new Backbone.Collection();
studentsCollection.each(function(studentModel) {
clonedCollection.add(new Backbone.Model(studentModel.toJSON()));
});

Use the Backbone Collection clone() method:
var clonedCollection = myCollection.clone();

Another option, if you need the following (which is what I was looking for when I found this question ;) ):
The copy of the collection should be of the same type as the original collection (e.g. you've created your own collection type that extends Backbone.Collection)
The copy of the collection should be created with the same options as the original
The models in the copy of the collection should be created using the model.clone() method
Code:
var models = original.map(function (model) { return model.clone(); });
var options = _.clone(original.options);
var copy = new original.constructor(models, options);
A generic clone method on Backbone.Collection would be awkward because there are always going to be subtleties around whether models and their nested objects get copied by reference or are themselves cloned. Requirements will vary wildly according to your scenario, so it's been left for you to write what you need.

Related

Backbone JS - Combination of collection, views and models

I had the following setup now in three different projects, so I thought maybe it's worth asking here. There's a chance I'm not the only person wondering about this.
So there it comes:
I have a list of objects
The list items have a special function like draggable or onClick-Events
My question: How do I build this scenario with Backbone?
I guess I create a model and a view for my list objects. I use a collection for keeping together my objects.
Now I wonder: Do I instantiate the object view in the model constructor and instantiate the models in the collection constructor? Or is there a better way to achieve what I want?
Neither; use a mediator to instantiate the views and inject them with their model/collection.
Marionette.js has a nice implementation for this sort of mediating object, called Controller - I suggest you check it out.
Further more, you don't have to explicitly instantiate the collection models - just declare their type in the collection's prototype, e.g.:
var MyModel = Backbone.Model.extend({
// do some shit
});
var MyCollection = Backbone.Collection.extend({
model: MyModel // pass the type for the collection to instantiate
});
Taking our mediator/controller approach further, this is how it could be done (with Marionette.js):
var MyController = Marionette.Controller.extend({
initialize: function () {
this.myCollection = new MyCollection();
this.myCollectionView = new MyCollectionView({
collection: this.myCollection
});
}
});
This, of course, is just a skeleton code, meant to roughly demonstrate the MVC approach, but it's a good starting point.

Trying to add a collection to a model and render it in multiple places in a view

I'm trying to build an app that allows you to create items in a view in multiple places.
For instance, I can add a model from a collection when I create a new model in the parent collection.
The problem I'm having is, when I add the collection to the model and it iterates, it's adding the collection for each loop. So it ends up with LOTS of collections in the model that are repeated.
The parent model render has the following loop:
this.noteTasksViewAdding = new NoteTaskListViewAdding({ collection: tasks });
$('.note-tasks-list-all').append(this.noteTasksViewAdding.render().el);
this.collection.each(function(note){
var view = new NoteView({ model: note });
$('#items-list').prepend(view.render().el);
});
Then the model has the following render in it:
render: function() {
this.noteTasksViewAdding = new NoteTaskListViewAdding({ collection: tasks });
$('.note-tasks-list-all').append(this.noteTasksViewAdding.render().el);
this.$el.html(this.template(this.model.toJSON()));
return this;
}
What I want to have happen is that the collection is added to the model when it's rendered. I'm open to any suggestions on how to do this better.
Thanks!
I faced the problem as well. The solution is to clear the view before you set the collection. In your case, when you set parent view, you might do this:
$('.note-tasks-list-all').html(this.noteTasksViewAdding.render().el);
Then you can append your collection members.

Backbone.js: Natively passing attributes to models when fetched with a collection

Let say you are defining a Backbone.js Model. From the documentation we have ...
new Model([attributes], [options])
This seems great for passing some default attributes to a model. When passed the model automatically inherits those attributes and their respective values. Nothing to do. Awesome!
On they other hand lets say we have a Collection of that model.
new Backbone.Collection([models], [options])
Okay, cool we can pass some initial models and some options. But, I have no initial models and no options I need to pass so let's continue. I am going to fetch the models from the server.
collection.fetch([options])
Well I don't have any options, but I want to pass some attributes to add to each models as it is fetched. I could do this by passing them as options and then adding them to the attributes hash in the initialize for the model, but this seems messy.
Is their a Backbone.js native way to do this?
You can pass the attributes as options to fetch and over-ride the collection's parse method to extend the passed options (attributes) on the response.
The solution would look like the following:
var Collection = Backbone.Collection.extend({
url:'someUrl',
parse:function(resp,options) {
if (options.attributesToAdd) {
for (var i=0;i<resp.length;i++)
_.extend(resp[i],options.attributesToAdd);
}
return resp;
}
});
Then to add attributes when you call fetch on the collection, you can:
var collection = new Collection();
collection.fetch({
attributesToAdd:{foo:'bar',more:'foobar'}
});
You may have to tweak the code a bit to work with your JSON structure, but hopefully this will get you started in the correct direction.

How to make Backbones toJSON function include sub-models and collections?

I have a few models that don't just contain basic data attributes, but they might have one or two attributes that hold another models object.
This has been okay, but now I want to call
myRootModel.toJSON()
and I've noticed that it doesn't call .toJSON on the other models in my model that I'm trying to call toJSON() on.
Is there a way to override backbone model .toJSON to go through all fields, recursively, whether they are basic attributes, sub-models or collections? If not, can I override toJSON in each model / collection?
I'm aware of backbone-relational, but I don't want to go that route - I'm not using fetch/save, instead our API returns responses that I adjust in the models parse function and simply invoke new MyRootModel(data,{parse:true}).
Here's a way you can achieve such a thing (there's maybe another way):
Backbone.Model.prototype.toJSON = function() {
var json = _.clone(this.attributes);
for(var attr in json) {
if((json[attr] instanceof Backbone.Model) || (json[attr] instanceof Backbone.Collection)) {
json[attr] = json[attr].toJSON();
}
}
return json;
};
http://jsfiddle.net/2Asjc/.
Calling JSON.parse(JSON.stringify(model)) parses the model with all the sub-models and sub-collections recursively. Tried on Backbone version 1.2.3.

How to update model in collection?

I have the following Backbone.js collection:
var Tags = Backbone.Collection.extend({
url: "/api/v1/tags/"
}),
How do I update one of the models in the collection so that it posts to /api/v1/tags/id and saves the data for that model.
So if I change name of model with id 2 in the collection
It should PUT to
/api/v1/tags/2 with the following data:
name: new name id: 2
I've also recently wanted to update particular model in the collection. The problem was that if I did use just model.save it didn't update the collection. The goal was to change the model in collection, change it on the server, update collection accordingly and not use the sync method. So for example I have my variable collection and I want to change the model with id = 2. So the first thing, I will create an instance model, like this: var model = collection.get(2)Then I will update the attributes on this particular model:model.set({name: 'new name'})Then I will save it to the server:model.save({}, {url:'/api/v1/tags/'+model.get('id')})Then we have to update collection accordingly to the changes:collection.set({model},{remove: false})set method - it's a 'smart' update of the collection with the list of the models you passed in parameters. remove: false parameter - it's a restriction for a collection to remove existing models in collection. More here.
The first thing you can miss is that in your corresponding Tag model you'll need to set "urlRoot" to match the Collection's "url". Otherwise it doesn't know about the collection at all:
var Tag = Backbone.Model.extend({
urlRoot: "/api/v1/tags"
});
var Tags = Backbone.Collection.Extend({
model: Tag,
url: "/api/v1/tags"
});
This is useful if you want to save the tag separately:
var tag = collection.get(2);
tag.set({key: "something"});
tag.save(); // model.save works because you set "urlRoot"
On the collection, "create()" is also "update()" if id is not null. That's not confusing. :) Therefore, this is pretty much equivalent to the previous sample:
collection.create({id: 2; key: "something"});
This will update the existing tag with id=2 and then trigger a PUT.
This is an ancient question; answering because I was searching for the same answer--you've probably long since solved this problem and moved on. :)
You can pass variables to the save method. It accepts all the options which jQuery's ajax method uses (unless you overrided Backbone.Sync)
You could do something like:
model.save( { name:'new name' } );
The id and PUT method will automatically be added by Backbone for you.

Resources