I need to fire a function when a Backbone.js view is removed. I guess something like a de-constructor. The code below is a snippet I wrote; I know it won't work. However, I do remember seeing in the past a video tutorial on how to write a function that does this. The reason I need to run a de-constructing function is to clear the interval set inside a view when the view is removed.
ViewWeather = Backbone.View.extend({
interval: setInterval(function() {console.log('interval fire');}, 1000),
// made up function
deconstructor: function () {
// if the view is removed
clearInterval(this.interval);
}
});
var viewweather = new ViewWeather();
This blog post should give you some better info
http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
most notable parts
Backbone.View.prototype.close = function(){
this.remove();
this.unbind();
if (this.onClose){
this.onClose();
}
}
and then
MyView = Backbone.View.extend({
initialize: function(){
this.model.bind("change", this.render, this);
},
render: function(){ ... },
onClose: function(){
this.model.unbind("change", this.render);
}
});
i am not sure if I understand you correctly, but the approach you are showing seems correct:
dispose: function(id) {
clearInterval(this.interval);
this.remove();
}
you are going to have to call dispose yourself, by e.g. an event:
initialize: function(opts) {
router.bind('route:leave', this.dispose, this);
},
edit after comments: this should work to overload remove
remove: function() {
clearInterval(this.interval);
Backbone.View.prototype.remove.call(this);
}
Related
I have some model and I want to bind render method to it on change. I'm trying to pass the model.toJSON to the render, but it doesn't work. However it works if I pass model and apply toJSON inside render.
(the whole code is here: http://plnkr.co/edit/xoeY4hexnqgHnkxap5uj?p=preview)
window.onload=function(){
var defaultModel = Backbone.Model.extend({
defaults: {
greeting: 'Hello, Dude',
content: 'Coming soon...'
}
}),
defaultView = Backbone.View.extend({
tagName: 'section',
className: 'default',
initialize: function(option) {
this.template = $('#tmpl-default').html();
this.render();
var _this = this;
this.model.bind('change', _.bind(this.render, this, this.model.toJSON()));
$('[name="default-input"]').on('blur', function() {
console.log('got blurred....');
_this.model.set('content', this.value);
});
},
render: function(content) {
if (!content) {
console.log('%cno content', 'color: green');
content = this.model.toJSON();
}
this.$el.html(_.template(this.template)(content));
$('#content').html(this.$el);
return this;
}
}),
viewDefault = new defaultView({
model: new defaultModel()
});
};
the code above doesn't work. If I change
this.model.bind('change', _.bind(this.render, this, this.model.toJSON()));
to
this.model.bind('change', _.bind(this.render, this, this.model));
and
if (!content) {
content = this.model.toJSON();
}
to
if (!content) {
content = this.model.toJSON();
}else{
content = content.toJSON();
}
But why?!
A more appropriate way is to use the listenTo function on the view, such as:
this.listenTo(this.model, "change", this.render);
I think the reason it doesn't work as you expect is because when you do
this.model.bind('change', _.bind(this.render, this, this.model.toJSON()));
The argument this.model.toJSON() passed to render method will always be the initial state of the model at the point when _.bind was called.
When you do content = this.model.toJSON(); inside render method, you get the current state, including the expected changes that triggered render.
You can better structure your view like this:
defaultView = Backbone.View.extend({
tagName: 'section',
className: 'default',
initialize: function(option) {
this.render();
this.model.on('change', _.bind(this.render, this));
},
template: _.template($('#tmpl-default').html()),
events: {
'blur [name="default-input"]': 'eventHandler'
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
$('#content').html(this.$el);
return this;
},
eventHandler: function(event) {
var val = $(event.currentTarget).val();
this.model.set('content', val);
}
});
Also, look into listenTo than on like #Jayem suggested to aviod unexpected memory leak issues
I use the backbone to test something, but i don't know why the drawSomething just no show ##"
initialize: function() {
setInterval(function() {
//alert("Hello");
this.drawSomething();
}, 1000);
},
drawSomething: function() {
alert('hi');
},
The problem is that inside setInterval callback context this is not what you expect (it's global object window). Simplest fix is to save proper object reference in variable:
var self = this;
setInterval(function() {
//alert("Hello");
self.drawSomething();
}, 1000);
as you are using backbone, so possible you are using underscore too. Bind should help:
initialize: function () {
var foo = function () { this.drawSomething(); };
foo = _.bind(foo, this);
setInterval(foo, 1000);
}
or jQuery analog Proxy:
foo = $.proxy(foo, this);
as quick solution
initialize: function() {
setInterval(function() {
//alert("Hello");
this.drawSomething();
}.bind(this), 1000);
},
drawSomething: function() {
alert('hi');
},
but I would prefer to use additional variable as dfsq methioned, because some old browsers doesn't support bind
I have the following backbone view. I had a doubt. In case if a model is deleted, i call the render after cancel(First Approach), the other way of doing it would be to have an initialize function, which renders the model listening to the event changes, inside the views.(Second Approach)
Could someone please let me know, the difference between one and two. As to which of the two is better.
First Approach
var AppointmentView = Backbone.View.extend({
template: _.template('">' +
'<%= title %>' +
'x'),
events: { "click a": "cancel" },
cancel: function(){
this.model.cancel();
this.render(); // rendering after cancel
},
render: function(){
this.$el.html(this.template(this.model.toJSON()));
}
});
Second Approach
var AppointmentView = Backbone.View.extend({
template: _.template('<span class="<% if(cancelled) print("cancelled") %>">' +
'<%= title %></span>' +
'x'),
initialize: function(){
this.model.on("change", this.render, this);
},
events: { "click a": "cancel" },
cancel: function(){
this.model.cancel();
},
render: function(){
this.$el.html(this.template(this.model.toJSON()));
}
});
I would define a custom cancelled event, trigger that from your cancel method, and bind to that event in the view.
var Appointment = Backbone.Model.extend({
cancel: function() {
//cancellation code...
this.trigger('cancelled', this);
}
});
var AppointmentView = Backbone.Model.extend({
initialize: function() {
this.listenTo(this.model, 'cancelled', this.render);
}
});
This way your view will re-render, even if the model is cancelled from elsewhere than the view itself, but you still get the specific behavior or only re-rendering upon cancel, and not on every change.
I'm trying to use Backbone.js to in a JQuery Dialog. I've managed to get the dialog to render and open, but it doesn't seem to be firing my events. I've added a test event to check this, and clicking it doesn't have the expected result.
I've tried following the instructions on this blogpost, regarding delegateEvents, but nothing it made no difference. No errors are thrown, the events just don't fire. Why is this?
Slx.Dialogs.NewBroadcastDialog.View = Backbone.View.extend({
events: {
"click .dialog-content": "clickTest"
},
clickTest : function () {
alert("click");
},
render: function () {
var compiledTemplate = Handlebars.compile(this.template);
var renderedContent = compiledTemplate();
var options = {
title: Slx.User.Language.dialog_title_new_message,
width: 500
};
$(renderedContent).dialog(options);
this.el = $("#newBroadCastContainer");
this.delegateEvents(this.events);
return this;
},
initialize: function () {
_.bindAll(this, 'render');
this.template = $("#newBroadcastDialogTemplate").html();
this.render();
}
});
You might want to try this. I had to refactor your code a bit hope you will get the idea
Slx.Dialogs.NewBroadcastDialog.View = Backbone.View.extend({
el:"#newBroadCastContainer",
template:$("#newBroadcastDialogTemplate").html(),
events: {
"click .dialog-content": "clickTest"
},
clickTest : function () {
alert("click");
},
render: function () {
var compiledTemplate = Handlebars.compile(this.template);
var renderedContent = compiledTemplate();
$(this.el).html(renderedContent).hide().dialog(this.options.dialogConfig);
return this;
},
initialize: function () {
}
});
Instantiate and render outside the View definition
var myDialog = new Slx.Dialogs.NewBroadcastDialog.View({dialogConfig:{title: Slx.User.Language.dialog_title_new_message,width: 500}});
myDialog.render();
The problem turned out to be due to me assigning this.el when I should have been assigning this.$el
This worked perfectly:
Slx.Dialogs.NewBroadcastDialog.View = Backbone.View.extend({
el: "#newBroadcastContainer",
events: {
"click .clicktest": "clickTest"
},
clickTest : function () {
console.log("click");
},
render: function () {
var compiledTemplate = Handlebars.compile(this.template);
var renderedContent = compiledTemplate();
var options = {
title: Slx.User.Language.dialog_title_new_message,
width: 500
};
this.$el = $(renderedContent).dialog(options);
return this;
},
initialize: function () {
_.bindAll(this, 'render');
this.template = $("#newBroadcastDialogTemplate").html();
this.render();
}
});
I had two codebases on one of the code base I was able to bind events by assigning the dialog to this.$el however in the other codebase this somehow did not work. I add the following line this.el = this.$el;
to the code and it is working now. however I am still not able to figure out why it was working in one codebase and not the other and why assigning $el to el got it to work.
I have a Backbone collection and when I add a new model to it the "add" event doesn't seem to work as I'd expect. I've bound 2 views to listen for add events on the collection, but only one seems to get notified of the event, and when this happens, no PUT request is sent to my server. When I remove the second bind, the other one works and the PUT request is sent. Here's the code snippets:
var FlagList = Backbone.Collection.extend({
model: Flag // model not shown here... let me know if it would help to see
});
var FlagCollectionView = Backbone.View.extend({
el: $('ul.#flags'),
initialize: function() {
flags.bind('add', this.addFlag, this); // this one doesn't fire!!
},
addFlag: function(flag) {
alert("got it 1"); // I never see this popup
}
});
var AddFlagView = Backbone.View.extend({
el: $("#addFlagPopup"),
events: {
"click #addFlag": "addFlag"
},
initialize: function() {
flags.bind('add', this.closePopup, this); // this one fires!!
}
addFlag: function() {
flags.create(new Flag);
},
closePopup: function() {
alert("got it 2"); // I see this popup
}
});
var flags = new FlagList;
var addFlagView = new AddFlagView;
var flagCollectionView = new FlagCollectionView;
A few suggestions:
ID's vs Classes
you've over qualified your selector by combining a class and an id. jQuery allows this, but the ID selector should be unique on the page anyway so change el: $('ul.#flags') to el: $('ul#flags').
Leveraging Backbone
I like to explicitly pass my collections and/or models to my views and use the magic collection and model attributes on views.
var flags = new FlagList;
var addFlagView = new AddFlagView({collection: flags});
var flagCollectionView = new FlagCollectionView({collection: flags});
which now means that in your view, you will automagically have access to this.collection
unbinding events to avoid ghost views
var FlagCollectionView = Backbone.View.extend(
{
initialize: function (options)
{
this.collection.bind('add', this.addFlag, this);
},
addFlag: function (flag)
{
alert("got it 1");
},
destroyMethod: function()
{
// you need some logic to call this function, this is not a default Backbone implementation
this.collection.unbind('add', this.addFlag);
}
});
var AddFlagView = Backbone.View.extend(
{
initialize: function ()
{
this.collection.bind('add', this.closePopup, this);
},
closePopup: function ()
{
alert("got it 2");
},
destroyMethod: function()
{
// you need some logic to call this function, this is not a default Backbone implementation
this.collection.unbind('add', this.closePopup);
}
});
It looks like I have to agree with #fguillen, that your problem must be somewhere in how you initialize the view, as in my comment I mention that it's most likely related to timing, ie: binding your event to the collection after the 'add' event has already fired.
This code works for me:
var FlagList = Backbone.Collection.extend({});
var FlagCollectionView = Backbone.View.extend({
initialize: function() {
flags.bind('add', this.addFlag, this);
},
addFlag: function(flag) {
alert("got it 1");
}
});
var AddFlagView = Backbone.View.extend({
initialize: function() {
flags.bind('add', this.closePopup, this);
},
closePopup: function() {
alert("got it 2");
}
});
var flags = new FlagList;
var addFlagView = new AddFlagView;
var flagCollectionView = new FlagCollectionView;
flags.add({key:"value"});
check the jsFiddle
Your problem is somewhere else.
If you ended up here after making the same stupid mistake I did, make sure you've got:
this.collection.bind( 'add', this.render )
and NOT:
this.collection.bind( 'add', this.render() )