What is the best way to detect the moment after a Backbone View, extended from an other object or not, has been removed?
JsFiddle added :
http://jsfiddle.net/simmoniz/M5J8Q/1917/
How to make the line #32 working without altering the views...
<h2>The container</h2>
<div id="container"></div>
<script>
var SomeExtendedView = Marionette.ItemView.extend({
events: {
'click button.remove':'remove',
},
});
var JohnView = SomeExtendedView.extend({
template: _.template('<div><p>I\'m a <em>John view</em> <button class="remove">Remove me</button></p></div>'),
});
var DoeView = SomeExtendedView.extend({
template: _.template('<div><p>I\'m a <strong>Doe view</strong> <button class="remove">Remove me</button>'),
});
var SimpleView = Backbone.View.extend({
initialize: function(){
Backbone.View.prototype.initialize.apply(this, arguments);
this.$el.bind('click', _.bind(this.remove, this));
},
render: function(){
this.$el.html('<div><p>Simple view. <strong>Click on me to remove</strong></p></div>');
return this;
}
});
var container = {
el: $('#container'),
views: null,
add: function(view){
if(!this.views)this.views = [];
this.el.append(view.render().el);
view.$el.bind('remove', _.bind(this.onRemove, this));
},
onRemove : function(element){
console.log('Element ' + element + ' has been removed!');
}
}
container.add(new JohnView());
container.add(new DoeView());
container.add(new SimpleView());
</script>
View lifecycle management is one of the important things missing from the backbone core.
All non-trivial apps end up needing to solve this. You can either roll your own, or use something like marionette or Chaplin.
Basically, backbone doesn't have the concept of view destruction or dealocation. A point in time in which listeners should be unbound. This is the single greatest source of memory leaks in backbone apps.
I finally came up with a working solution. Since the element added is a Backbone view (simple or extended), it has remove function. This solution replaces the remove function with a new "remove" event that performs the same operation, but triggers a "remove" event juste before. Listeners can catch it now. It works great :
var ev = new $.Event('remove'),
orig = $.fn.remove;
view.remove = function() {
$(this).trigger(ev);
return orig.apply(this, arguments);
}
Then we can listen to the "remove" event like in my question
view.bind('remove', _.bind(this.onRemove, this));
Inside the view
events: {
"remove" : "some function",
},
Related
i want to fire on_change events on dynamically created drop boxes.
but have no idea how to do it in backbone js
here is my html code creating a div tag
<div id="page">
<input type="button"id="btn1"value="ok">
</div>
and its my backbone code where i am dynamically adding drop down in
var btn2id ="";
var app = {};app.v1 = Backbone.View.extend({
el: '#page',
events: {
'click #btn1' : 'f1',
},
f1:function()
{
alert("Boom");
btn2id="btn2";
for(var j=0;j<3;j++) {
$('#page').append('<select id="selecty'+j+'"></select>');
for(var i=0;i<10;i++){
$('#selecty'+j+'').append('<option value="'+i+'">'+i+'</option>');
}
vv = new app.v2();}}
}
});
app.v2 =Backbone.View.extend({
el: '#page',
events:{
at this place i have no idea what to do
// for(int i=0;i<3;i++){
// 'change '#selecty'+i+'' : 'f2',
// }
},
f2:function() {
alert("Boom again");
}
v = new app.v1();
});
v = new app.v1();
In my opinion, reusable components should have their on view.
This practice lets you bind the recurring events easily, and in general matter cleans your code.
Note: in my code example I didn't use any template engine or practice, but I totally recommend you to do that.
So lets assume you have the main view with a button that creates new select elements:
var View = Backbone.View.extend({
el : "#main",
events : {
'click #add' : 'add',
},
add : function(){
var select = new SelectView();
this.$el.append(select.render().el);
}
});
As you can see, anytime #add is clicked, it creates a new SelectView which represents the select element.
And the select element itself:
var SelectView = Backbone.View.extend({
events:{
'change select' : 'doSomething'
},
doSomething: function(e){
$(e.currentTarget).css('color','red');
},
render: function(){
this.$el.html("<select />");
for(var i=0;i<10;i++)
{
this.$el.find('select').append("<option value='"+i+"'>"+i+"</option>")
}
return this;
}
});
In my dummy example I just change the color of the element when it is changed. You can do whatever.
So, it is now super easy to bind events to the select views.
In general, I would recommend you that when you are working with reusable components, you should always think of a practice which makes things make sense.
This is one of many ways to do that, but it is pretty simple to understand and implement.
You are welcome to see the "live" example: http://jsfiddle.net/akovjmpz/2/
I am trying to create simple Backbone example but i don't understand what is the problem with my code. Why is there 2 testAttr attributes(on directly on object and one in attributes object) and why isn't change event triggering on any of the changes? Also i don't understand what is the correct way to set attributes on model?
Heres my code:
<div id="note"></div>
<script>
var NoteModel = Backbone.Model.extend({
defaults: function() {
return {
testAttr: "Default testAttr"
}
}
});
var NoteView = Backbone.View.extend({
initialize : function() {
this.listenTo(this.model, "change", this.changed());
},
el: "#note",
changed: function () {
debugger;
console.log("change triggered");
this.render();
},
render : function() {
this.$el.html("<h1>" + this.model.get("testAttr") + "</h1>");
return this;
}
});
var note = new NoteModel();
var noteView = new NoteView({model: note});
noteView.render();
note.set("testAttr", "blah1");
note.testAttr = "blah2";
</script>
Change this line:
this.listenTo(this.model, "change", this.changed());
to this:
this.listenTo(this.model, "change", this.changed);
For the second part of your question, using .set() goes through a set of dirty-checking to see if you changed, deleted or didn't change anything, then triggers the appropriate event. It isn't setting the object.property value, it's setting the object.attributes.property value (tracked by backbone.js). If you directly change the object property, there's nothing to initiate that event for you.
...unless of course you use the AMAZING AND TALENTED Object.observe()!!!1! - ITS ALIVE (in Chrome >36)
I have problem in implementing touch events with backbone.js and hammer.js
I have tried implementing the touch in the conventional way i.e defining the touch events in "events" section.But it has not worked for me.
Please find my code below
define(['Backbone','underscore','Handlebars','Hammer'],function(Backbone,_,Handlebars,Hammer) {
//getting the type of device in a variable using "userAgent"
window.MOBILE = navigator.userAgent.match(/mobile/i);
var HeadderView = Backbone.View.extend(
{
el: 'body',
touchPrevents : false,
initialize: function()
{
this.el$ = $(this.el);
},
events: function() {//returning different functions based on the device
return MOBILE ?
{
'tap #headcontent': 'handleTap',
} :
{
'click #headcontent':'clickbackbone',
}
},
//declaring the corresponding functions
handleTap: function(){
alert("tap event");
},
clickbackbone:function(){
alert('backbone click');
},
render: function ()
{
//rendering the template and appending it to the page
var that = this;
require(['text!gaming/gameHeadder/headder.html'],function(HeaderTemplate){
var template = Handlebars.compile(HeaderTemplate);
var context = {title: "Tick Tack Toe", imageURL: "images/logo.jpg"}
var htmlTemplate = template(context);
that.el$.html( htmlTemplate);
});
},
});
return new HeadderView();
}
);
Can some one help me out and correct my code
This is not how Hammer works. Backbone knows nothing about HammerJS.
If you really want to use Backbone-style event delegation with Hammer, you might want to check out the backbone.hammer project.
for some reason I need to use a variable as the selector for events in backbone, but I can't figure how to do this :
app.views.Selfcare = Backbone.View.extend({
events: {
click window.parent.document .close : "closeWindow"
},
closeWindow: function() {
//code
}
});
I have to use a different scope and I can't do "click .close" : "closeWindow".
Thx for your help.
I had a look at Backbone.js's source code and found out that if your view's events is a function then the function is called and it's return value is used as the events object.
This means that your code can be changed like this:
app.views.Selfcare = Backbone.View.extend({
events: function() {
var _events = {
// all "standard" events can be here if you like
}
_events["events" + "with variables"] = "closeWindow";
return _events;
},
closeWindow: function() {
//code
}
});
THIS is the interesting part of the source code:
if (_.isFunction(events)) events = events.call(this);
Update:
Example is available on JSFiddle HERE**
I'm not sure that you'll be able to use a variable there. You could use the built-in Events methods (see documentation) to add a custom listener, then add an event listener to window.parent.document to trigger that custom event (use the Events.trigger method).
That said, it would be much easier to decouple this event from Backbone entirely (unless you don't want to do that), and go down the addEventListener route:
app.views.Selfcare = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'render', 'closeWindow');
if(this.options.clickTarget) {
this.options.clickTarget.addEventListener('click', this.closeWindow, false);
}
},
render: function() {
// Render to the DOM here
return this; // as per Backbone conventions
},
closeWindow: function() {
// Stuff here
}
});
// Usage:
var mySelfcare = new app.views.Selfcare({
clickTarget: window.parent.document
});
I think that should work, although I haven't tested it (and there may be one or two syntactical errors!)
I have some Backbone.js code that bind a click event to a button,
and I want to unbind it after clicked, the code sample as below:
var AppView = Backbone.View.extend({
el:$("#app-view"),
initialize:function(){
_.bindAll(this,"cancel");
},
events:{
"click .button":"cancel"
},
cancel:function(){
console.log("do something...");
this.$(".button").unbind("click");
}
});
var view = new AppView();
However the unbind is not working, I tried several different way and end up binding event in initialize function with jQuery but not in Backbone.events model.
Anyone know why the unbind is not working?
The reason it doesn't work is that Backbonejs doesn't bind the event on the DOM Element .button itself. It delegates the event like this:
$(this.el).delegate('.button', 'click', yourCallback);
(docs: http://api.jquery.com/delegate)
You have to undelegate the event like this:
$(this.el).undelegate('.button', 'click');
(docs: http://api.jquery.com/undelegate)
So your code should look like:
var AppView = Backbone.View.extend({
el:$("#app-view"),
initialize:function(){
_.bindAll(this,"cancel");
},
events:{
"click .button":"cancel"
},
cancel:function(){
console.log("do something...");
$(this.el).undelegate('.button', 'click');
}
});
var view = new AppView();
Another (maybe better) way to solve this is to create a state attribute like this.isCancelable now everytime the cancel function is called you check if this.isCancelable is set to true, if yes you proceed your action and set this.isCancelable to false.
Another button could reactivate the cancel button by setting this.isCancelable to true without binding/unbinding the click event.
You could solve this another way
var AppView = Backbone.View.extend({
el:$("#app-view"),
initialize:function(){
_.bindAll(this,"cancel");
},
events:{
"click .button":"do"
},
do:_.once(function(){
console.log("do something...");
})
});
var view = new AppView();
underscore.js once function ensures that the wrapped function
can only be called once.
There is an even easier way, assuming you want to undelegate all events:
this.undelegateEvents();
I like bradgonesurfing answer. However I came across a problem using the _.once approach when multiple instances of the View are created. Namely that _.once would restrict the function to be called only once for all objects of that type i.e. the restriction was at the class level rather than instance level.
I handled the problem this way:
App.Views.MyListItem = Backbone.View.extend({
events: {
'click a.delete' : 'onDelete'
},
initialize: function() {
_.bindAll(this);
this.deleteMe = _.once(this.triggerDelete);
},
// can only be called once
triggerDelete: function() {
console.log("triggerDelete");
// do stuff
},
onDelete:(function (e) {
e.preventDefault();
this.deleteMe();
})
});
Hopefully this will help someone
you can simply use object.off, the code below is work for me
initialize:function () {
_.bindAll(this, 'render', 'mouseover', 'mouseout', 'delete', 'dropout' , 'unbind_mouseover', 'bind_mouseover');
.......
},
events: {
'mouseover': 'mouseover',
'unbind_mouseover': 'unbind_mouseover',
'bind_mouseover': 'bind_mouseover',
.....
},
mouseover: function(){
$(this.el).addClass('hover');
this.$('.popout').show();
},
unbind_mouseover: function(){
console.log('unbind_mouseover');
$(this.el).off('mouseover');
},
bind_mouseover: function(){
........
},