Trouble with appropriately destroying views in backbone - backbone.js

I have a view that I create twice on two separate tabs. Within each instance of this view I have a bunch of subviews. Each time I create the parent view, it creates new subviews within itself. At the same time, it pushes these subviews to an array that gets called like:
var ParentView = Backbone.View.extend({
itemViews: [],
initialize: function(options) {
var self = this;
// following creates the specific collection of data
// is this what I should be using to destroy the subviews
// rather than the itemViews array?
this.collection = options.collection;
this.collection.on('add', self._createSubView, self);
render: function(){// render stuff},
_createSubView: function() {
view = new SubView();
this.itemViews.push(view);
},
});
The problem is that sometimes within one of the parent views, I need to destroy its subviews. That's done by:
_.each(self.itemViews, function (view) {
Utils.destroyView(view); // Utility function that destroys the views.
});
The issue I'm facing is that each time I instantiate a new parent view, rather than creating a new array for itemViews, backbone simply appends to one overall array called itemViews, as if each of the two parent views shares the same itemViews array. So rather than just destroying the appropriate tab's views, it destroys the views from both tabs. Is there either:
A better way to create the list of subviews so that each list is unique to that parent view?
A better way than using this type of list to destroy the subViews of a parent view?
Thanks!

You need to instantiate your itemViews property so it is belongs to a view instance.
You should do this in your initialize function as follows:
initialize: function(options) {
this.itemViews = [];
// rest of initialize code
}
That should make the itemVews behave the way you would expect.

Related

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.

Collection create function firing add event too quickly

(Using Backbone 0.9.10)
I have a list view of sorts where the user can click a button to show a modal view. The list view has a counter that shows the amount of items contained in the list. Clicking a button in the modal view executes create on a collection that is passed into both views. This fires the add event in the list view, which in turn runs this function:
renderCount: function () {
// Container for model count
var $num = this.$el.find('.num');
if ($num.length > 0) {
// 'count' is a value returned from a service that
// always contains the total amount of models in the
// collection. This is necessary as I use a form of
// pagination. It's essentially the same as
// this.collection.length had it returned all models
// at once.
$num.html(this.collection.count);
}
}
However, add seems to be fired immediately (as it should be, according to the docs), before the collection has a chance to update the model count. I looked into sync but it didn't seem to do much of anything.
How can I make sure the collection is updated before calling renderCount?
Here's the list view initialize function, for reference:
initialize: function (options) {
this.collection = options.collection;
this.listenTo(this.collection, 'add remove reset', this.renderCount);
this.render();
}
EDIT:
Turns out I was forgetting to refetch the collection on success in the modal view.
$num.html(this.collection.count);
shoule be:
$num.html(this.collection.size());
Backbone.Collection uses methods imported from underscore, here is list: http://backbonejs.org/#Collection-Underscore-Methods
Turns out I was forgetting to refetch the collection on success in the modal view.

Backbone.marionnette - Rebinding events vs creating new view

I have a Layout that has several tabs. Clicking one of these tabs will show the appropriate composite view in the page's content region. After navigating back and forth between different tabs I noticed that the composite views have lost their native bindings to render on collection reset and model changes.
Is there a way I should be rebinding the events being used in _initialEvents of a composite view when showing a view for a second time, or should I be creating a new composite view every I show a tab?
Currently I am creating all my views in initialize of my Layout and then using show with the view when a tab is clicked.
initialize: function(){
_.bindAll(this);
// Tabs
this.places_page = new Places_Layout();
},
show_places_page: function(){
this.content.show(this.places_page);
this.places_page.delegateEvents();
},
You don not have to create a Layout/Item/Composite/Collection view each time you switch from tab to tab, on the contrary you can save the content in a variable just the way you are doing, the problem you have is that the variable is being re-declared each time you want to render the content.
The solution is that you have to verify if that variable (this.places_page) is declared if not append it to the view so when you call it more times it will be holding the same layout view without any problem, just note that when you render the main view (the one holding the regions) the nested child views(in regions) will be lost until new navegation through them.
initialize: function(){
_.bindAll(this);
// You can asign a diferent variable for each view so when you call show_places_page it will render with the same view.
if (!this.places_page){
this.places_page = new Places_Layout();
}
// other tab
if (!this.other_page){
this.other_page = new OtherPage_Layout();
}
},
show_places_page: function(){
this.content.show(this.places_page);
this.places_page.delegateEvents();
},
This does not sound like the best approach to me.
You should use the layout's region managers to show views without needing functions like you have defined.
I would go for this approach
var view = new CustomView();
layout.content.show(view);`
then later on:
var newView = new SecondCustomView();
layout.content.show(newView);
If you want to continue down the road that you are on then you would probably be best to use this approach:
initialize: function () {
_.bindAll(this);
},
show_places_page: function () {
var placesLayout = new Places_Layout();
this.content.show(placesLayout);
}
Does that make sense?
Its hard to suggest the best course of action without seeing more structure around this.
Is there a reason that you are creating the views in initialize?
Marionette(v.1) onwords uses Backbone.BabySitter to manage child views .
In your case you do the same.
Just create a containter to store all tab view. Later query the container to return the view you need to display.
this.tabViewsContainer = new Backbone.ChildViewContainer();
this.tabViewContainer.add(new CustomView(),'tab1');
this.tabViewContainer.add(new SecondCustomView(),'tab2');
To Later Show the view just do this
var custv = container.findByCustom("tab1");
this.content.show(custv);
In close method your layout view successfully close all view in container
this.tabViewsContainer.each(function(view){view.close()});
You should not create all the views inside the initialize as this will cause you memory leaks that's why you should do dynamic creation of the views. Also I would suggest create a common function for showing a view in your content region to increase the code re-usability. I would suggest you something like following solution:
//define the regions of your layout view
regions: {
content: '#content'
},
//Maintain a config for the tab content view classes.
contentViews: {
tab1: Tab1View,
tab2: Tab2View,
tab3: Tab3View
},
//keeps all the view instances
viewInstances: {},
/*
* show tab function is called when you click a tab item.
* Consider each tab has a attribute for tab name.
* For example HTML of your one tab is like:
* <div data-tab-name="tab_name">Tab <tab_name></div>
*/
showTab: function (e) {
var tabName = $(e.currentTarget).attr("data-tab-name");
/*
* code for showing selected tab goes here...
*/
//check and create the instance for the content view
if (!this.viewInstances[tabName]) {
this.viewInstances[tabName] = new this.contentViews[tabName]();
}
//Then here you are actually showing the content view
this.content.show(this.viewInstances[tabName]);
this.viewInstances[tabName].delegateEvents(); //this is to rebind events to the view.
}

how to handle multiple views within a single view with backbonejs

I'm new to Backbone and I don't fully understand it yet, and I've come across a situation I can't find any documentation on. What I if I have a view that contains multiple views? For example, I have a view called StackView. The purpose of this view is to neatly lay out a set of cards. It manages the animation of adding, removing, and adjusting cards in the stack. Each card is a CardView. How would I handle this? I've seen people talk about views within views by simple creating a variable in the view and assigning the View instance to that variable. Should I just be adding an array of CardViews in a variable of a StackView?
That's what I do, and it works well. Here's a snippet of a View I use in an application. I've re-written it back into regular javascript from my coffeescript, so I apologize for any typos:
render: function() {
var _this = this;
this.$el.html(this.template());
this.listItemViews = [];
// for each model in the collection, create a new sub-view and add
// it to the parent view
this.collection.each(function(model){
var view = new App.Views.Projects.ListItem({model:model}); // create the new sub-view
_this.listItemViews.push(view); // add it to the array
_this.$('#project-table tbody').append(view.render().$el); // append its rendered element to the parent view's DOM
});
return this;
}
This lets my Table view maintain a reference to all the listItemView views.
Of course, if you do this, you should make sure to properly remove these child views and unbind any events when you remove the parent view.

How to create an instance of a module and pass it to another with requirejs?

I'm building a single page app with Backbone and Requirejs.
For now I've been returning a new instance from my module files, ending the files with:
return new moduleName;
This worked ok until I had to pass arguments to the initialize method of a collection. Since the initialize is called when the instance is created, I took the "new" away from the return statement.
return mymoduleName;
And instantiated the collection with parameters in my router:
myCollection = new library({paramname: "value"});
This indeed creates an instance of "library" - a collection introduced in my router's define block, but how do I pass it to the view responsible of rendering it?
My view class has the same dependency in its define block and in its initialize I bind it to the collection's reset:
this.collection = library;
_.bindAll(this, 'render');
this.collection.bind('reset', this.render);
This worked before taking away the "new" from the collection class (which on retrospective makes no sense at all!) but now the view never gets rendered because it was instantiated in the router - how do I pass that same instance to the view?
Don't set the collection in its initialize method, but wherever you instantiate your view (in your case, the router).
var myCollection = new library({paramname: "value"});
var myView = new myCustomView({
collection: myCollection
});
If myCollection is a variable you need to access from other places as well, you can set it on the router (myRouter.myCollection = ...), you could create a model that holds global objects (MyAppGlobals) or you could simply have a global variable (not recommended).

Resources