Lets say I have a model of (audio/video) tracks and a collection those tracks of it
Model:
Backbone.Model.extend({
defaults:{...},
play: function() {
//plays the track
}
});
The collection then represents something like a playlist with
Collection:
Backbone.Collections.extend({
currentTrackInList: 0,
model: track,
playAll: function() {
self = this;
this.models.each(function(item){
item[self.currentTrackInList].play();
});
}
});
What I now need to do is access properties of the collection within the model to determine the playback logic (i.e. if the current track playing was the last one in the collection).
Of course I could just pass the parameters through the play() function in the collection, but there has to be a much easier way to let models and collections based said model talk to each other.
A model knows its collection. You can access it by this.collection.
Related
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.
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.
What is the best way to remove a model from a collection that has been removed in the DOM. Let me ask a better question, how do I keep views in sync with a collection?
remove the view first, while removing execute
this.model.collection.remove(this.model);
you can check with conditions to whether current view has a model, and that model has a collection etc before you execute the same.
I've followed the backbone Todos example application. This keeps view state up to date with collection.
Pass models to any view created like so:
var someView = new SomeItemView({ model: modelFromCollection });
Then listen to events on that model and react from the view:
initialize: function() {
this.listenTo(this.model, 'destroy', this.remove);
// listen to other events ...
}
So I have an object that looks something like this
{
first_name: 'Matt',
last_name: 'Damon',
movies: [{name: 'mov_name1', director: 'Sven'}, {name:'mov_name2', director: 'Joe'}]
}
There is an endpoint that will return this, and I will use fetch to get this data and put it in a model or ItemView. (I have to use fetch because I really want a collection of these objects, so I'll call fetch on the collection.) However, the representation of the object should not be a model alone, it should be a model (or ItemView), and the movies attribute should be a collection, since when I display the row for this object it should be a CompositeView with a model and a collection. My question is how do I convert this object representation to a model and collection after a fetch called on the model?
Sorry it's a bit confusing to explain...
do a fetch on your collection and then for each element create a model and a collection that you can pass then to a compositeView
something like this
mycollection = new MyCollection();
mycollection.fetch();
mycollection.each(function(item){
var actorModel = new ActorModel({firstName : item.get('first_name'),
lastname: item.get('last_name')});
var moviesCollection = new MoviesCollection(item.get('movies'));
var xCompositeView = new XCompositeView({model:xmodel,
collection:moviesCollection});
});
this way your Compositeview gets a model and a collection and you can render them properly using an itemView for each movie.
hope this helps.
If its not a problem to use another framework, there is one called backbone-relational. It is designed to handle the relations between models. See the zoo example in Backbone-Relational
In the example there is a Zoo model and models has a Animal Collection which consist of animal models. With a single fetch on Zoo model, you will have the animal collection as Backbone collection instead of array of plain objects.
If I initialize a collection with an array of objects for the model attributes when a dialogview is initialized. Then the dialog view that lets the user edit the list updates those model values with calls to model set. When the dialog's ok button is clicked, Does backbone provide a way to get the list of only those models that changed since the collection was created/initialized?
There are various model methods that look tempting:
Model#hasChanged
Model#changedAttributes
Model#previous
Model#previousAttributes
But don't be fooled, those only apply while a "change" event is being triggered:
Note that this method, and the following change-related ones, are only useful during the course of a "change" event.
so they're useless after the event has been triggered and handled.
I think you have to track which models have changed yourself. You can do this on the collection itself without too much effort since
Any event that is triggered on a model in a collection will also be triggered on the collection directly, for convenience.
and the collection can bind to its own events. For example, you could have something like this in your collection:
Backbone.Collection.extend({
initialize: function() {
this.delta = { };
this.on('change', this._a_model_has_changed);
},
changed_models: function() {
return _.chain(this.delta).values();
},
_a_model_has_changed: function(m) {
this.delta[m.id] = m;
}
});
Then you could get the models that have changed by calling collection.changed_models(). You'd want to listen for other events as well so that you could update this.delta when models are deleted or synced with the server; the above is just for illustration. If you didn't want an Underscore object returned you could use this instead:
changed_models: function() {
return _(this.delta).values();
}
but being able to collection.changed_models().each(function() { ... }) is convenient.
Demo: http://jsfiddle.net/ambiguous/8PQh9/
You could also let the models track their own dirtiness through a similar set on the models. Then you could do something like this:
collection.filter(function(m) { return m.is_dirty() });
where, of course, is_dirty would return true if the model had been changed.