Re-render only changed section of Marionette.js CollectionView - javascript

Below is the code I currently have that re-renders a collectionView on every addition or removal of a model. However, it seems inefficient as it has to render the whole thing every time, when all I really need is one modelView to be removed or one to be added. So how could I achieve this?
var CollectionView = Marionette.CollectionView.extend({
childView: ModelView,
initialize: function() {
[ "add", "remove" ].forEach(function(eventName) {
this.listenTo(this.collection, eventName, this.render, this);
}.bind(this));
}
});
Thanks in advance for any help you can give!

This is already done automatically in Marionette:
When a model is added to the collection, the collection view will
render that one model in to the collection of item views.
When a model is removed from a collection (or destroyed / deleted),
the collection view will close and remove that model's item view.

Related

Backbone: adding model from one collection to another

Here is the general structure I have right now:
Collection 'A' which has view 'A'
to add a model, you can open a modal and choose from a list. The modal is it's own view, 'B', tied to its own collection, 'B'. the view 'B' is instantiated from view 'A' on a click event.
I was going to initialize view 'B' with a 'myParent' attribute so when a model is selected from collection b, I can say this from within view B:
this.myParent.collection.add(newModel).
I know this will work, but is this coupling things too tight that don't need to be? is there a good pattern for this? I was thinking about having several different events and triggers spread about different places but that seems to just complicate things without much added value.
I have come across a similar problem in an application which lists a collection of selectable models in a table. Based on the models which are selected, another view needs to determine which buttons to show to the user.
In this case, I went for an event driven approach. Every time a model from view A is selected, I fire an event which View B listens to and adds that model to Collection B.
For your situation, that could look something like this:
// Your collection view.
var ViewA = Backbone.View.extend({
initialize: function(){
Backbone.Events.on('modelSelected', this.onModelSelected, this);
this.listenTo(this.collection, 'add', this.renderList);
},
onModelSelected: function(model){
this.collection.add(model);
}
renderList: function(){
// Draw your collection
}
// Do some clean up of events
remove: function(){
Backbone.Events.off('modelSelected', this.onModelSelected, this);
Backbone.View.prototype.remove.call(this);
}
});
// Modal View
var ViewB = Backbone.View.extend({
initialize: function(){
this.collection = new YourCollection();
this.listenTo(this.collection, 'sync', this.renderCollection);
this.collection.fetch();
},
render: function(){
// Render your modal
return this;
},
renderCollection: function(){
// Render your collection
},
//
modalSubmit: function(){
var model = this.getSelectedModel();
Backbone.Events.trigger('modelSelected', modal);
this.close();
}
});
I should note that this comes with a few caveats. Firstly, I would recommend picking a robust and namespace event naming scheme. This is just an example. Secondly, fetching from the initialize function is often not ideal, but I'm leaving out the concept of controllers for brevity.

Backbonejs Events for all collection items or Events per ItemView

What approach would be more efficient?
I have a Backbone.Collection so i Create a Backbone.View to render this collection. The CollectionView render method:
render: ->
container = document.createDocumentFragment()
#collection.each (item) ->
view = new ItemView(item)
container.appendChild view.el
view.render()
$(el).append container
I can use the events in two forms.
1.- Set the events object in CollectionView, so i need to declare the action of select an item in the CollectionView and "rescue" the model that i selected.
CollectionView extends Backbone.View
events:
'click #itemView', 'onSelectItem'
onSelectItem: ->
##Get the model
##Show ItemDetailView
2.- Set the events object per itemView, so the select method don't need to retrieve the model.
ItemView extends Backbone.View
events:
'click #div','onSelect'
onSelect: ->
#Show ItemDetailView
Which of this options are better?
EDITED: I create a JSperf snippet http://jsperf.com/backbone-events-on-collectionview-or-per-itemview
JSperf show us that the ItemView approach is more faster, but is this the only metric of importance?
The second approach is much better. Performance aside, the code is much more straightforward. In 6 months, if you had to return to this code, would you think the event would be handled in the ItemView, or the CollectionView? It is a click event on the ItemView, so that's where I would go to look for how it is handled.
Is there a good reason to handle the event in the collection? If you needed the handle the event from the CollectionView, you could delegate the event to the collection. A little redirection, but, to me, this is far clearer. For example:
# The collection view
initialize: ->
#listenTo #, 'selected', #itemSelected
render: ->
container = document.createDocumentFragment()
#collection.each (item) ->
view = new ItemView(item, parent: #)
container.appendChild view.el
view.render()
$(el).append container
itemSelected: (model)->
# Do whatever you need to here, like
# show the ItemDetailView in the container
# The ItemView
ItemView extends Backbone.View
events:
'click #div','onSelect'
onSelect: ->
#options.parent.trigger('selected', #model, #)
#Show ItemDetailView
If I understand you correctly, option 2 seems the most sane. You will be referring to this.model as opposed to digging through the collection to find the model associated with the clicked view.

Backbone: How do I put a collection inside a view

I see many tutorials which don't follow the supposedly best practice of making a model, a view and collection for that model then a view for the collection. Which would be the parent view?
How do I make a view for a collection? Also, is it possible for it to keep track of when a model is added or deleted for it to update/re-render?
You must do something like this in your collection view:
var view = Backbone.View.extend({});
var myView = new view({'collection' : new collection});
To handle add/remove event, use this in your initialize function:
this.collection.on("add", this.onAdd, this);
this.collection.on("remove", this.onRemove, this);
and in your model view:
this.model.on("change", this.onUpdate,this);
See it here: http://www.neiker.com.ar/backbone/
(Sorry, I don't speak english)
EDIT: Just use marionette:
https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.collectionview.md

Responding to category model change in Backbone relational

I have a model relationship set-up with Backbone Relational, whereby an Item belongs to a Column. Adding items to columns works well, but how do I reference an existing view for an item, in-order to remove it from its old column? (Therefore, items are currently duplicating across columns.)
Please see this JSFiddle - http://jsfiddle.net/geVPp/1 (I presume code will need to be implemented in the ColumnView removeItem event handler, but I may be wrong).
(The example instantiation is at the bottom of the script, as I have no UI controls yet.)
Here is the ColumnView from the fiddle:
var ColumnView = Backbone.View.extend({
className: 'column',
tagName: 'div',
template: Handlebars.compile($('#column-template').html()),
initialize: function() {
_.bindAll(this, 'render', 'renderItem', 'removeItem');
this.model.bind('change', this.render);
this.model.bind('reset', this.render);
this.model.bind('add:items', this.renderItem);
this.model.bind('remove:items', this.removeItem);
},
render: function() {
return $(this.el).html(this.template(this.model.toJSON()));
},
renderItem: function(item) {
var itemView = new ItemView({model: item});
this.$('.items').append($(itemView.render()));
},
removeItem: function(item) {
// #todo -- How do I reference the item model's view, in order to remove the DOM element?
}
});
Unfortunately Backbone has no built-in way; you have to keep track of your child views (and which model they map to) manually.
Backbone.Marionette has a CollectionView class that can automate this for you. Or you could roll your own similar to UpdatingCollectionView.
If you want a quick fix, you could keep a reference to the view as a variable of the model object, but that's bad because it prevents you from showing multiple views of the same model.

remove from collection bind to remove from view

I have a backbone collection and when I remove a model from the collection, I want it to remove the item from a list in the view.
My collection is pretty basic
MyApp.Collections.CurrentEvents = Backbone.Collection.extend({
model: MyApp.Models.Event
});
and in my views I have
MyApp.Views.CurrentEventItem = Backbone.View.extend({
el: 'div.current_event',
initialize: function(){
event = this.model;
_.bindAll(this, "remove");
MyApp.CurrentEvents.bind('remove',this.remove); //the collection holding events
this.render();
},
// yeah, yeah, render stuff here
remove: function(){
console.log(this);
$(this.el).unbind();
$(this.el).remove();
}
});
when I remove the model from the collection, it triggers the remove function, but the view is still on the page.
In the console, I can see the model, but I believe the model should have an 'el', but it doesn't.
My container code is
MyApp.Views.CurrentEventsHolder = Backbone.View.extend({
el: 'div#currentHolder',
initialize: function(){
MyApp.CurrentEvents = new MyApp.Collections.CurrentEvents();
MyApp.CurrentEvents.bind('new', this.add);
},
add: function(){
var add_event = new MyApp.Views.CurrentEventItem(added_event);
$('div#currentHolder').append(add_event.el);
}
});
for some reason in the add method I can't seem to use the $(this.el) before the append, though I'm not sure if that is the problem.
PROBLEM: MyApp.CurrentEvents.bind('remove',this.remove);
This triggers the remove() function every time any model is deleted from the collection.
This means that anytime a model is deleted, all the CurrentEventItem view instances will be deleted.
Now, about the view still being on the page:
It must have something to do with the way you appended/added/html-ed the view in the page. Check your other views, maybe if you have a CurrentEventsContainer view of some sort, check your code from there because with your current code, it does delete the view, albeit, all of them though.
RECOMMENDED FIX:
change your bindings to:
this.model.bind('remove',this.remove);
and make sure that when you instantiate it, pass on the model so that each view will have a corresponding model to it like so:
//...
addAllItem: function(){
_.each(this.collection, this.addOneItem())
},
addOneItem: function(model){
var currentEventItem = new MyApp.Views.CurrentEventItem({model:model});
//...more code..
}
//...
This makes things a lot easier to manage in my opinion.
UPDATE (from your updated question)
The reason you aren't able to use this.el is because you haven't passed the right context into the add() function. You need to add _.bindAll(this,'add') in your initialize() function to pass the right context, therefore making your this correct, allowing you to use this.el within the add function. Also, change your code to this:
MyApp.CurrentEvents.bind('new', this.add, this); this passes the right context. Also, why not use add instead as an event?
Continuing what I said in the comments, the way you've implemented this right now will remove all the CurrentEventItem views from the page when any of them is removed from the collection. The reason for this is the following:
MyApp.CurrentEvents.bind('remove',this.remove);
What this essentially says is, "every time the remove event is triggered on the collection, call this.remove." So, every time you instantiate one of these views, you're also telling the collection to remove that view when the collection triggers a remove event. I've created a fiddle to show you the problem.
You're right that Backbone knows which model has been removed from a collection, but you're not taking advantage of that. You can do that like so:
removeFromView: function(model) {
// Check to make sure the model removed was this.model
if (model.cid === this.model.cid) {
$(this.el).unbind();
$(this.el).remove();
}
}
See how this minor adjustment changes the behavior? Check it out in action here.
If you follow this pattern, you should see the proper views being removed.

Categories

Resources