In my web application, I have a user list in a table on the left, and a user detail pane on the right. When the admin clicks a user in the table, its details should be displayed on the right.
I have a UserListView and UserRowView on the left, and a UserDetailView on the right. Things kind of work, but I have a weird behavior. If I click some users on the left, then click delete on one of them, I get successive javascript confirm boxes for all users that have been displayed.
It looks like event bindings of all previously displayed views have not been removed, which seems to be normal. I should not do a new UserDetailView every time on UserRowView? Should I maintain a view and change its reference model? Should I keep track of the current view and remove it before creating a new one? I'm kind of lost and any idea will be welcome. Thank you !
Here is the code of the left view (row display, click event, right view creation)
window.UserRowView = Backbone.View.extend({
tagName : "tr",
events : {
"click" : "click",
},
render : function() {
$(this.el).html(ich.bbViewUserTr(this.model.toJSON()));
return this;
},
click : function() {
var view = new UserDetailView({model:this.model})
view.render()
}
})
And the code for right view (delete button)
window.UserDetailView = Backbone.View.extend({
el : $("#bbBoxUserDetail"),
events : {
"click .delete" : "deleteUser"
},
initialize : function() {
this.model.bind('destroy', function(){this.el.hide()}, this);
},
render : function() {
this.el.html(ich.bbViewUserDetail(this.model.toJSON()));
this.el.show();
},
deleteUser : function() {
if (confirm("Really delete user " + this.model.get("login") + "?"))
this.model.destroy();
return false;
}
})
I always destroy and create views because as my single page app gets bigger and bigger, keeping unused live views in memory just so that I can re-use them would become difficult to maintain.
Here's a simplified version of a technique that I use to clean-up my Views to avoid memory leaks.
I first create a BaseView that all of my views inherit from. The basic idea is that my View will keep a reference to all of the events to which it's subscribed to, so that when it's time to dispose the View, all of those bindings will automatically be unbound. Here's an example implementation of my BaseView:
var BaseView = function (options) {
this.bindings = [];
Backbone.View.apply(this, [options]);
};
_.extend(BaseView.prototype, Backbone.View.prototype, {
bindTo: function (model, ev, callback) {
model.bind(ev, callback, this);
this.bindings.push({ model: model, ev: ev, callback: callback });
},
unbindFromAll: function () {
_.each(this.bindings, function (binding) {
binding.model.unbind(binding.ev, binding.callback);
});
this.bindings = [];
},
dispose: function () {
this.unbindFromAll(); // Will unbind all events this view has bound to
this.unbind(); // This will unbind all listeners to events from
// this view. This is probably not necessary
// because this view will be garbage collected.
this.remove(); // Uses the default Backbone.View.remove() method which
// removes this.el from the DOM and removes DOM events.
}
});
BaseView.extend = Backbone.View.extend;
Whenever a View needs to bind to an event on a model or collection, I would use the bindTo method. For example:
var SampleView = BaseView.extend({
initialize: function(){
this.bindTo(this.model, 'change', this.render);
this.bindTo(this.collection, 'reset', this.doSomething);
}
});
Whenever I remove a view, I just call the dispose method which will clean everything up automatically:
var sampleView = new SampleView({model: some_model, collection: some_collection});
sampleView.dispose();
I shared this technique with the folks who are writing the "Backbone.js on Rails" ebook and I believe this is the technique that they've adopted for the book.
Update: 2014-03-24
As of Backone 0.9.9, listenTo and stopListening were added to Events using the same bindTo and unbindFromAll techniques shown above. Also, View.remove calls stopListening automatically, so binding and unbinding is as easy as this now:
var SampleView = BaseView.extend({
initialize: function(){
this.listenTo(this.model, 'change', this.render);
}
});
var sampleView = new SampleView({model: some_model});
sampleView.remove();
I blogged about this recently, and showed several things that I do in my apps to handle these scenarios:
http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
This is a common condition. If you create a new view every time, all old views will still be bound to all of the events. One thing you can do is create a function on your view called detatch:
detatch: function() {
$(this.el).unbind();
this.model.unbind();
Then, before you create the new view, make sure to call detatch on the old view.
Of course, as you mentioned, you can always create one "detail" view and never change it. You can bind to the "change" event on the model (from the view) to re-render yourself. Add this to your initializer:
this.model.bind('change', this.render)
Doing that will cause the details pane to re-render EVERY time a change is made to the model. You can get finer granularity by watching for a single property: "change:propName".
Of course, doing this requires a common model that the item View has reference to as well as the higher level list view and the details view.
Hope this helps!
To fix events binding multiple times,
$("#my_app_container").unbind()
//Instantiate your views here
Using the above line before instantiating the new Views from route, solved the issue I had with zombie views.
I think most people start with Backbone will create the view as in your code:
var view = new UserDetailView({model:this.model});
This code creates zombie view, because we might constantly create new view without cleanup existing view. However it's not convenient to call view.dispose() for all Backbone Views in your app (especially if we create views in for loop)
I think the best timing to put cleanup code is before creating new view. My solution is to create a helper to do this cleanup:
window.VM = window.VM || {};
VM.views = VM.views || {};
VM.createView = function(name, callback) {
if (typeof VM.views[name] !== 'undefined') {
// Cleanup view
// Remove all of the view's delegated events
VM.views[name].undelegateEvents();
// Remove view from the DOM
VM.views[name].remove();
// Removes all callbacks on view
VM.views[name].off();
if (typeof VM.views[name].close === 'function') {
VM.views[name].close();
}
}
VM.views[name] = callback();
return VM.views[name];
}
VM.reuseView = function(name, callback) {
if (typeof VM.views[name] !== 'undefined') {
return VM.views[name];
}
VM.views[name] = callback();
return VM.views[name];
}
Using VM to create your view will help cleanup any existing view without having to call view.dispose(). You can do a small modification to your code from
var view = new UserDetailView({model:this.model});
to
var view = VM.createView("unique_view_name", function() {
return new UserDetailView({model:this.model});
});
So it is up to you if you want to reuse view instead of constantly creating it, as long as the view is clean, you don't need to worry. Just change createView to reuseView:
var view = VM.reuseView("unique_view_name", function() {
return new UserDetailView({model:this.model});
});
Detailed code and attribution is posted at https://github.com/thomasdao/Backbone-View-Manager
One alternative is to bind, as opposed to creating a series of new views and then unbinding those views. You'd accomplish this doing something like:
window.User = Backbone.Model.extend({
});
window.MyViewModel = Backbone.Model.extend({
});
window.myView = Backbone.View.extend({
initialize: function(){
this.model.on('change', this.alert, this);
},
alert: function(){
alert("changed");
}
});
You'd set the model of myView to myViewModel, which would be set to a User model. This way, if you set myViewModel to another user (i.e., changing its attributes) then it could trigger a render function in the view with the new attributes.
One problem is that this breaks the link to the original model. You could get around this by either using a collection object, or by setting the user model as an attribute of the viewmodel. Then, this would be accessible in the view as myview.model.get("model").
Use this method for clearing the child views and current views from memory.
//FIRST EXTEND THE BACKBONE VIEW....
//Extending the backbone view...
Backbone.View.prototype.destroy_view = function()
{
//for doing something before closing.....
if (this.beforeClose) {
this.beforeClose();
}
//For destroying the related child views...
if (this.destroyChild)
{
this.destroyChild();
}
this.undelegateEvents();
$(this.el).removeData().unbind();
//Remove view from DOM
this.remove();
Backbone.View.prototype.remove.call(this);
}
//Function for destroying the child views...
Backbone.View.prototype.destroyChild = function(){
console.info("Closing the child views...");
//Remember to push the child views of a parent view using this.childViews
if(this.childViews){
var len = this.childViews.length;
for(var i=0; i<len; i++){
this.childViews[i].destroy_view();
}
}//End of if statement
} //End of destroyChild function
//Now extending the Router ..
var Test_Routers = Backbone.Router.extend({
//Always call this function before calling a route call function...
closePreviousViews: function() {
console.log("Closing the pervious in memory views...");
if (this.currentView)
this.currentView.destroy_view();
},
routes:{
"test" : "testRoute"
},
testRoute: function(){
//Always call this method before calling the route..
this.closePreviousViews();
.....
}
//Now calling the views...
$(document).ready(function(e) {
var Router = new Test_Routers();
Backbone.history.start({root: "/"});
});
//Now showing how to push child views in parent views and setting of current views...
var Test_View = Backbone.View.extend({
initialize:function(){
//Now setting the current view..
Router.currentView = this;
//If your views contains child views then first initialize...
this.childViews = [];
//Now push any child views you create in this parent view.
//It will automatically get deleted
//this.childViews.push(childView);
}
});
Related
Im new to backbone and I'm looking to a very simple 2 view configuration page usig backbone.
I have the following code;
define(
["backbone","...","..."],
function(Backbone, ... , ... ) {
var PopupView = Backbone.View.extend({
initialize: function initialize() {
Backbone.View.prototype.initialize.apply(this,arguments);
},
events: {
"click .save_conf_button": "save_conf",
},
render: function() {
this.el.innerHTML = this.get_popup_template();
return this;
},
save:conf: function save_conf() {
//get the field values from popup_template
//var items = jquery(....);
});
var ExampleView = Backbone.View.extend({
//Starting view
initialize: function initialize() {
Backbone.View.prototype.initialize.apply(this, arguments);
},
events: {
"click .setup_button": "trigger_setup", //Triggers final setup
"click .create_conf_button": "trigger_popup_setup", //This is the conf popup
},
render: function() {
this.el.innerHTML = this.get_start_html();
return this;
},
trigger_popup_setup: function trigger_popup_setup() {
console.log("Pop up");
//this.el.innerHTML = this.get_popup_template();
PopupView.render();
...
},
}); //End of exampleView
return ExampleView;
} // end of require asynch
); // end of require
E.g. The ExampleView is the starting view with a couple of fields and 2 buttons; create popup and save. Upon pressing the create_conf_button I want to render the popup view, however this does not seem to work as I expected. (Uncaught TypeError: PopupView.render is not a function)
I'm not sure how to proceed and additionally what the "best practice" is for generating these types of dialogs?
Additionally, keeping the values filled in on the previous page after returning from the popupview would be preferential.
Thanks for any help
try
new PopupView.render()
you have to create an instance to call the methods this way
#ashish is correct, you have to instantiate an instance of the PopupView before calling its render method. Currently, you have defined a blueprint for a view called PopupView, which will act as a constructor for newly created PopupView view instances. In order to use this defined view I would suggest storing it in ExampleView's render or initialize method:
// Example View's initialize method
initialize: function initialize() {
this.popUpView = new PopupView();
Backbone.View.prototype.initialize.apply(this, arguments);
},
then referencing it in your trigger_popup_setup function as follows:
trigger_popup_setup: function trigger_popup_setup() {
console.log("Pop up");
//this.el.innerHTML = this.get_popup_template();
this.popUpView.render();
...
},
As for storing state Backbone models are used for that :)
In general to nest subviews within a master view in Backbone you can do the following:
initialize : function () {
//...
},
render : function () {
this.$el.empty();
this.innerView1 = new Subview({options});
this.innerView2 = new Subview({options});
this.$('.inner-view-container')
.append(this.innerView1.el)
.append(this.innerView2.el);
}
In this example the master view is creating instances of it's subviews within its render method and attaching them to a corresponding DOM element.
I am trying to understand backbone and am currently struggling with zombie views. I have read many stack overflow posts on the matter but I still cannot figure it out.
For the sake of simplicity, I set up two views (without data) that I need to switch.
What I did so far was:
creating an object
//define application object
var app = {
vent: {},
templates: {},
views: {},
routers: {},
};
//instantiate event aggregator and attach it to app
app.vent = _.extend({}, Backbone.Events);
defining two very simple templates (stored into app.templates): the first one has some dummy text and a button (with and id of 'test-begin'), the second one just dummy text
defining two views
app.views.instructions = Backbone.View.extend({
//load underscore template
template: _.template(app.templates.instructions),
//automatically called upon instantiation
initialize: function(options) {
//bind relevant fucntions to the view
_.bindAll(this, 'render', 'testBegin', 'stillAlive', 'beforeClose');
//listen to app.vent event
this.listenTo(app.vent, 'still:alive', this.stillAlive);
},
//bind events to DOM elements
events: {
'click #test-begin' : 'testBegin',
},
//render view
render: function() {
this.$el.html(this.template());
return this;
},
//begin test
testBegin: function() {
Backbone.history.navigate('begin', {trigger: true});
},
//still alive
stillAlive: function() {
console.log('I am still alive');
},
//before closing
beforeClose: function() {
//stop listening to app.vent
this.stopListening(app.vent);
},
});
//test view
app.views.test = Backbone.View.extend({
//load underscore template
template: _.template(app.templates.test),
//automatically called upon instantiation
initialize: function(options) {
//trigger still:alive and see if removed view responds to it
app.vent.trigger('still:alive');
//bind relevant fucntions to the view
_.bindAll(this, 'render');
},
//render view
render: function() {
this.$el.html(this.template());
return this;
},
});
defining a router
//base router
app.routers.baseRouter = Backbone.Router.extend({
//routes
routes: {
'': "instructions",
'begin': "beginTest"
},
//functions (belong to object controller)
instructions: function() {baseController.instructions()},
beginTest : function() {baseController.beginTest()},
});
//baseRouter controller
var baseController = {
instructions: function() {
mainApp.viewsManager.rederView(new app.views.instructions());
},
beginTest: function(options) {
mainApp.viewsManager.rederView(new app.views.test());
},
};
defining mainApp (with a view-switcher)
//define mainApplication object
mainApp = {};
//manages views switching
mainApp.viewsManager = {
//rootEl
rootEl: '#test-container',
//close current view and show next one
rederView : function(view, rootEl) {
//if DOM el isn't passed, set it to the default RootEl
rootEl = rootEl || this.rootEl;
//close current view
if (this.currentView) this.currentView.close();
//store reference to next view
this.currentView = view;
//render next view
$(rootEl).html(this.currentView.render().el);
},
};
//render first view of app
mainApp.viewsManager.rederView(new app.views.instructions());
//initiate router and attach it to app
mainApp.baseRouter = new app.routers.baseRouter();
//start Backbone history
Backbone.history.start({silent: true
});
adding a close function to view via Backbone prototype
//add function to Backbone view prototype (available in all views)
Backbone.View.prototype.close = function () {
//call view beforeClose function if it is defined in the view
if (this.beforeClose) this.beforeClose();
//this.el is removed from the DOM & DOM element's events are cleaned up
this.remove();
//unbind any model and collection events that the view is bound to
this.stopListening();
//check whether view has subviews
if (this.hasOwnProperty('_subViews')) {
//loop thorugh current view's subviews
_(this._subViews).each(function(child){
//invoke subview's close method
child.close();
});
}
};
So, in order to check for zombie views, the second view triggers and event (still:alive) that the first view listen to and respond to it via a message sent to the console.log (although it really shouldn't).
The first view does listen to such a message (in the console log I read 'I am still alive) even when it has been replaced by the second view.
Can you help me? thank you very.
Long post, if you have any questions, please ask
A Zombie View is just a view that is not in the DOM, but listens to and reacts to events -- sometimes this behavior is expected, but not typically.
If the DOM Event handlers for the view are not properly removed, the view and it's in-memory HTML fragments will not be garbage collected. If the Backbone.Event handlers are not unbound properly, you could have all sorts of bad behavior... such as a bunch of "Zombie" view triggering AJAX requests on models. This problem was very common on older versions of Backbone prior to stopListening and listenTo especially if you shared models between views.
In your code, you don't have a Zombie View, because you are properly closing your views.
You can see the console.log because you are initializing the second view (and triggering the event still:alive) before you close the first view.
To switch views, you are calling:
mainApp.viewsManager.rederView(new app.views.test());
Calling new app.views.test() initializes the second view which triggers the event that the first listens to.
If you update your code to the following, you won't see the console.log anymore.
//baseRouter controller
var baseController = {
instructions: function() {
mainApp.viewsManager.rederView(app.views.instructions);
},
beginTest: function(options) {
mainApp.viewsManager.rederView(app.views.test);
},
};
And update rederView
rederView : function(ViewClass, rootEl) {
//if DOM el isn't passed, set it to the default RootEl
rootEl = rootEl || this.rootEl;
//close current view
if (this.currentView) this.currentView.close();
//store reference to next view
this.currentView = new ViewClass();
//render next view
$(rootEl).html(this.currentView.render().el);
},
If you remove this line from your close method, you will have a zombie view and should see the console.log.
//unbind any model and collection events that the view is bound to
this.stopListening();
Zombie View Example
In the following code, I am creating 100 views, but only displaying 1 in the DOM. Every view contains the same model and listens to it's change event. When the view's <button> element is clicked, it updates the model which causes every view's model change handler to be executed, calling fetch 100 times... 100 AJAX requests!
The view's change handlers are called 100 times, because the view close method does not call this.stopListening(), so even when the views are removed from the page, they all still listen to the model's events. Once you click the button, the model is changed, and all of the zombie views respond, even though they're not on the page.
var TestView = Backbone.View.extend({
tagName: 'h1',
initialize: function(options) {
this.i = options.i;
this.listenTo(options.model, 'change', function(model) {
model.fetch();
});
},
events: {
'click button': function() {
this.model.set("show_zombies", Date.now());
}
},
render: function() {
this.$el.append("<button>Click To Test for Zombies!</button>");
return this;
},
close: function() {
this.$el.empty(); // empty view html
// this.$el.off(); // // Whoops! Forgot to unbind Event listeners! (this view won't get garbage collected)
// this.stopListening() // Whoops! Forgot to unbind Backbone.Event listeners.
}
});
var model = new (Backbone.Model.extend({
fetch: function() {
document.body.innerHTML += "MODEL.FETCH CALLED<br />"
}
}));
var v;
for (var i = 1; i < 101; i++) {
if (v) v.close();
v = new TestView({
'i': i,
'model': model
}).render();
$('body').html(v.el);
}
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.1.2/backbone.js"></script>
I'm working with an API and Backbone.js at the moment.
I have two views, both render to the same document element #viewContainer. Both of these views render a table with a couple strings to decribe them and a button that opens a form in a modal.
View 1
App.Views.TaskList = Backbone.View.extend({
el: "#viewContainer",
tagName: 'tr',
events: {
"click button": "showTaskForm"
},
showTaskForm: function (event) {
event.preventDefault();
var id = $(event.currentTarget).data("id");
var item = this.collection.get(id);
var formView = new App.Views.Form({
model: item
});
formView.render();
},
render: function () {
changeActive($('#tasksLink'));
var template = _.template($("#taskList").html(), {});
$('#viewContainer').html(template);
// loop and render individual tasks.
this.collection.each(function (model) {
var variables = {
name: model.get('name'),
button: model.getButton()
};
var template = _.template($("#task").html(), variables);
$("#taskTable tbody").append(template);
});
},
collection: App.Collections.Tasks,
});
View 2
App.Views.ProcessList = Backbone.View.extend({
el: "#viewContainer",
tagName: 'tr',
events: {
"click button": "showStartForm"
},
showStartForm: function (event) {
event.preventDefault();
var id = $(event.currentTarget).data("id");
var item = this.collection.get(id);
var formView = new App.Views.Form({
model: item
});
formView.render();
},
collection: App.Collections.Processes,
render: function () {
changeActive($('#processLink'));
var template = _.template($("#processList").html(), {});
$('#viewContainer').html(template);
this.collection.each(function (model) {
var variables = {
processId: model.get('id'),
processName: model.get('name'),
button: model.getButton()
};
var template = _.template($('#process').html(), variables);
$('#processList tbody').append(template);
});
} });
Neither of these views are rendered by default, both need to be activated by a button on the page and they over-write each other in the DOM. However, which ever view is rendered first, the click event of the buttons in that view are the ones that are always fired.
If there is any more information needed from me let me know and I will edit the question.
Be sure to call undelegateEvents() in the first view when you render the second.
Since you're listening for events on the same elements, essentially you attached two listeners for click events on the same button, and when you change your views you are not cleaning up these listeners.
Here's an article that talks about managing events on view change, which should be really helpful to you.
http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
As other posters have pointed out, you need to watch out for 'zombie' views (i.e. making sure you undelegate events). If you're building even a moderately complex app, you'll want something that can scale. I find this pattern useful:
var BaseView = Backbone.View.extend({
render: function () {
this.$el.html(this.template());
return this;
},
close: function () {
if (this.onClose) this.onClose();
this.undelegateEvents();
this.$el.off();
this.$el.remove();
}
});
Then whenever you build a view you can do:
var view = BaseView.extend({
//your code
//now the .close() method is available whenever you need to close
//a view (no more zombies!).
});
OK, I've done some reading on this and I'm pretty sure I know what the problem relates to I Just don't know the best way to fix it. I've got the standard backbone router that sets me up with an item details view, then when I click on a button called "start" it creates a new view which takes me to a sort of a game that people can play with some buttons on the bottom that have "click" events attached. This second view is not called through the router but directly from the first view.
The problem is the second time someones goes back to the homescreen and does it again, this time there are two events attached to each button. The third time there are three events. Obviously the original views are still listening to these buttons. I've read about this and calling the Remove() method but is this what I need to do? If so where do I call remove? Relevant Code below:
1ST VIEW
window.GameDrillView = Backbone.View.extend({
initialize: function () {
this.render();
},
render: function () {
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
events: {
"click .start" : "startGameDrill",
},
startGameDrill: function () {
var start = $('#start').val();.
var stop = $('#stop').val();.
var StartView = new GameDrillStartView({model: this.model, el: $('#content')[0], start: start, stop:stop});
}
});
START VIEW
window.GameDrillStartView = Backbone.View.extend({
// declare variables
initialize: function () {
this.render();
},
events: {
"click .nextstage" : "nextstage", // 2ND TIME THROUGH GETS CALLED TWICE
},
nextstage: function () {
// Do some stuff //
this.render(); //Re-render
},
render: function () {
// Do some variables stuff
this.$el.html(this.template(jQuery.extend(this.model.toJSON(), extended_options)));..
return this;
}
});
When changing view you need to call undelegateEvents() method from the Backbone.View. It disable listening all the elements events mentioned in events { } block. Also if you need to destroy old view you can call remove() method of the view which will call undelegateEvents() internally.
update (example from official site)
var Workspace = Backbone.Router.extend({
routes: {
"help": "help", // #help
"search/:query": "search", // #search/kiwis
"search/:query/p:page": "search" // #search/kiwis/p7
},
help: function() {
if (this.currentView)
this.currentView.undelegateEvents();
this.currentView = new HelpView();
},
search: function(query, page) {
if (this.currentView)
this.currentView.undelegateEvents();
this.currentView = new SearchView();
}
});
An option is to create only one instance of the view:
if(_.isUndefined(this.StartView))
this.StartView = new GameDrillStartView({model: this.model, el: $('#content')[0], start: start, stop:stop});
else
this.StartView.render();
In the render method of GameDrillStartView add the empty method
this.$el.html(this.template(jQuery.extend(this.model.toJSON(), extended_options)))
In this way you won't add more event listeners but you'll update the page everytime the user presses the button.
You can manage the life cycle of StartView in GameDrillView since it seems like a better place to do so.
Got same trouble. Messy solution:
var current_view = false;
var prev_view = false;
var AppRouter = Backbone.Router.extend({
routes: {
"events/:id": "viewEvent",
}
});
var app_router = new AppRouter;
app_router.on('route:viewEvent', function (event_id) {
var _event = new Event({id:event_id});
current_view = new EventView({
model: _event,
});
});
//Will be called after route:viewEvent
app_router.on('route', function () {
if(prev_view) {
prev_view.undelegateEvents();
}
prev_view = current_view;
});
Not sure, how to make it without having current_view and prev_view out of router scope.
I have a web application developed with Backbone.js. In the application, there are some buttons that remove the content view, but not the content model when pushed. For example, If I push the same button multiple times, the content is replaced, but the model of that content isn't removed.
How can I remove it?
I know how to remove the content with other different button, but I don't know how to remove the content if the same button (or other button not destined to delete but to add) is pushed.
The example code:
HTML:
<button class="ShowCam"></button>
<button class="camClose"></button>
<button class="anotherButton"></button>
JS:
var camContent = Backbone.View.extend({
el: "body",
events: {
"click .ShowCam": "addContentCam",
"click .anotherButton": "otherAddContentFunction"
},
initialize: function() {
_.bindAll(this);
this.model = new ContentCollection();
this.model.on("add", this.contentAdded);
this.model.on("remove", this.removeContentCam);
},
addContentCam: function(event) {
this.model.add({ modelName: "IPcam"});
contentAdded: function(content) {
if (content.view == null) {
var templ_name = 'cam';
content.view = new ContentView({
model: content,
template: $.trim($("[data-template='"+ templ_name +"'] div").html() || "Template not found!")});
$("div.camBox").empty();
this.$el.find(".content").find("div.camBox").append(content.view.render().el);
}
},
removeContentCam: function(content) {
if (content.view != null) {
content.view.remove();
}
content.clear(); //Clear the properties of the model
}
});
var ContentView = Backbone.View.extend({
tagName: "div",
template: null,
events: {
"click .camClose": "removeView"
},
initialize: function() {
_.bindAll(this);
this.template = this.options.template;
},
render: function() {
this.$el.html(Mustache.render(this.template, this.model.toJSON()));
return this;
},
removeView: function() {
this.model.collection.remove(this.model); //Remove the model of the collection
}
});
Javascript uses a garbage collection system to do its memory management. What this means is that you can delete anything simply by removing all references to it (well, technically it doesn't actually get deleted until the garbage collector gets to it, but essentially it's deleted).
So, if you want to make sure that a model gets removed, you don't need to call any special methods, you just need to do delete someView.model on every view (or other place in your code) that references that model.
You can actually see all this in practice if you look at the remove method on Backbone.View. You'll find that all it really does (besides triggering events) is call an internal method _removeReference. And what does _removeReference do? This:
if (this == model.collection) {
delete model.collection;
}
// (there's also an event-related line here)
Now, all that being said, if you're making a new view to replace an old one, and they both have the same model ... well you likely shouldn't be making a new view in the first place. The more standard Backbone way of handling such situations is to just re-call render on the view (instead of making a new one).