I have one question about Backbone.View and its delegateEvents. You can read in docs here about extend method. Using this method, you can "override the render function, specify your declarative events" etc.
I have a question about declarative events or delegateEvents (not sure how should I call it). They are described here. And here is an example:
var DocumentView = Backbone.View.extend({
events: {
"dblclick" : "open",
"click .icon.doc" : "select",
"contextmenu .icon.doc" : "showMenu",
"click .show_notes" : "toggleNotes",
"click .title .lock" : "editAccessLevel",
"mouseover .title .date" : "showTooltip"
},
open: function() {
window.open(this.model.get("viewer_url"));
},
select: function() {
this.model.set({selected: true});
},
...
});
As you can see, you can add different events on specific objects in template DOM. Like click or mouseover. So, having this template:
<foo></foo>
{#myplayers}
<player class="normal" value="{player}" style="{style}"></player>
{/myplayers}
You can add different click event on every single player, like this:
events: {
'click player': 'playerClick'
},
playerClick: function( e ) {
var playerValue = e.currentTarget.getAttribute( 'value' );
// HERE: e.currentTarget I've particular player
}
My question: Can I declare render event in similar way as click event? I want to catch event when single player (not the whole list) is rendered. I need to get e.currentTarget there and change its css a little bit.
I'm looking for something like this:
events: {
'render player': 'playerRendered'
},
playerRendered: function( e ) {
var playerValue = e.currentTarget.getAttribute( 'value' );
// HERE: e.currentTarget I've particular player
}
How can I do this? Because render in delegateEvents, doesn't work:/
Maybe in the initialize function within your view you can have a listenTo with the render. Something like that:
var view = Backbone.View.extend({
className: 'list-container',
template: _.template($('#my-template').html()),
initialize: function () {
this.listenTo(this, 'render', function () {
console.info('actions');
});
},
render: function() {
this.$el.html(this.template(this));
return this;
},
});
And then:
var myView = new view();
myView.render();
myView.trigger('render');
$('#container').html(myView.el);
var View = Backbone.View.extend({
events: {
'render .myselector': 'playerRendered'
},
playerRendered: function( e ) {
console.log("arguments");
var playerValue = e.currentTarget.getAttribute( 'value' );
// HERE: e.currentTarget I've particular player
},
render:function(){
console.log("render");
this.$el.html("<div class='myselector'></div>");
}
});
var view = new View();
view.render();
and you can trigger with Jquery trigger
this.$(".myselector").trigger("render");
or outside your view
view.$(".myselector").trigger("render");
Related
I have a simple backbone view as follows:
/**
* Renders a form view for an event object.
*/
APP.EventFormView = Backbone.View.extend({
tagName: 'form',
events: {
'keydown': 'keyPressed',
'focus input': 'inputChanged',
'change select': 'selectChanged',
'change textarea': 'textareaChanged'
},
initialize: function() {
this.template = _.template($('#newevent-form').html());
this.listenTo(this.model, 'change', this.render);
this.listenTo(APP.eventTypes, 'update', this.render);
this.listenTo(APP.selectedEvent, 'update', this.render);
},
render: function() {
var modelJSON = this.model.toJSON();
if ('id' in modelJSON && modelJSON.id !== "") {
this.loadForm();
} else if (!('id' in modelJSON) || modelJSON.id === "") {
this.loadForm();
} else {
this.$el.html('');
}
return this;
},
loadForm: function() {
var templateData = $.extend(this.model.toJSON(),
{"event_types":APP.eventTypes.toJSON()});
this.$el.html('');
this.$el.html(this.template($.extend(this.model.toJSON(),
{event_types: APP.eventTypes.toJSON()})));
$('.ev-main-container').html('').html(this.el);
},
inputChanged: function(e) {
console.log('inputChanged');
},
selectChanged: function(e) {
console.log('selectChanged');
},
textareaChanged: function(e) {
console.log('textareaChanged');
},
keyPressed: function(e) {
console.log('key pressed');
}
});
I initialize this view as follows under document.ready:
// Initialize the form view
APP.selectedEvent = APP.selectedEvent || new APP.Event();
APP.eventFormView = new APP.EventFormView({model: APP.selectedEvent});
APP.eventFormView.render();
But none of the events I have defined are firing for some reason, What is it that I am doing wrong here ?
Update:
Ok, I fugred out if i remove $('.ev-main-container').html('').html(this.el); from the loadForm method and instead intialize the view as follows, it works:
APP.eventFormView = new APP.EventFormView({
model: APP.selectedEvent,
el: $('.ev-main-container'),
});
I was able to resolve it but I still don't understand why this happens, could anyone throw a little light on what's going on and how this works.
jQuery's html function has a side effect that many people seem to forget about, from the fine manual:
jQuery removes other constructs such as data and event handlers from child elements before replacing those elements with the new content.
Consider what that means when you do something like this:
container.html(view.el);
container.html(view.el);
Everything will be fine after the first container.html() call. But the second will "remove ... event handlers from child elements" (such as view.el) before adding the new content. So after the second container.html() call, all the events on view.el are gone. Sound familiar?
You have lots of things that will call render on your view and render will eventually do this:
$('.ev-main-container').html('').html(this.el);
Your events will silently disappear the second time that gets called but the HTML will look just fine.
Consider this simplified example (http://jsfiddle.net/ambiguous/otnyv93e/):
var V = Backbone.View.extend({
tagName: 'form',
events: {
'click button': 'clicked'
},
initialize: function() {
this.template = _.template($('#t').html());
},
render: function() {
this.$el.html('');
this.$el.html(this.template());
$('.ev-main-container').html('').html(this.el);
return this;
},
clicked: function() {
console.log('clicked');
}
});
var v = new V;
v.render();
$('#re-render').click(function() {
v.render();
console.log('Re-rendered');
});
and you'll see exactly your problem.
If you make the view's el the .ev-main-container then you'll be using html() to alter the contents of el rather than altering the contents of the element that contains el. Once you're working entirely inside the el you're no longer accidentally re-using an element and no longer accidentally removing the event bindings from that element.
My rules of thumb for preventing event problems with Backbone:
Never attach views to existing DOM nodes, always let views create and own their own el and let the caller put that el in a container.
Call remove on views to dispose of them when they're no longer needed.
Don't try to re-use views, create them when you need them and remove them when you don't need them.
No view references anything outside its el.
There are exceptions (of course) and this approach won't solve everything but it is a good starting point and avoids most of the common problems.
I'm writing Todo app with Backbone.js
You can see part of my code below.
Model:
var Todo = Backbone.Model.extend({
defaults : {
title: 'Task Title',
complete: false
},
initialize: function(){
this.on("change:complete", function () {
alert("foo");
});
}
});
View:
var AppView = Backbone.View.extend({
collection: todoCollection,
el: 'body',
events: {
'click #tasks li .complete-task' : 'toggleComplete'
}
toggleComplete: function (e) {
var modelCid = $(e.target).parent('li').attr('id');
if ( this.collection.get(modelCid)['complete'] ){
this.collection.get(modelCid)['complete'] = false;
} else {
this.collection.get(modelCid)['complete'] = true;
};
}
});
But something working wrong and change event in the model doesn't working. I can't understand where I have mistakes.
Help me, please.
10q.
As per the Backbone Documentation:
Set model.set(attributes, [options])
Set a hash of attributes (one or
many) on the model. If any of the attributes change the model's state,
a "change" event will be triggered on the model. Change events for
specific attributes are also triggered, and you can bind to those as
well, for example: change:title, and change:content. You may also pass
individual keys and values.
So you need to be using the set method on the model for these events to be fired. So you would need to use something like this:
this.collection.get(modelCid).set('complete',false);
In the following code:
var AppView = Backbone.View.extend({
events:{
"click .button":"cancel"
},
cancel:function() {
console.log("do something...");
},
onSomeEvent: function() {
this.$el.undelegate('.button', 'click', this.cancel);
}
});
var view = new AppView();
I need to undelegate this.cancel handler from elements with 'button' classes. Unfortunately this.$el.undelegate in onSomeEvent method doesn't work.
How could I remove that event handler?
try something like:
....
onSomeEvent: function() {
this.delegateEvents(
_(this.events).omit('click .button')
);
}
update:
do you mean like:
this.events[event] = "someEvent";
//call delegateEvents() on the view to re-bind events
this.delegateEvents();
I am new to javascript and Backbone.js. I would like to bind a custom listener to a Backbone view on initialization. For example, I would like to achieve something like this:
var CampaignListView = Backbone.View.extend({
initialize: function(){
this.on("customFunc")
},
customFunc: function() {
if (this.$el.scrollTop() == 500 ) {
console.log("this has occurred, time to do stuff")
}
}
)}
That whenever a user scrolls to a specified position, I can execute some code.
Thanks.
I'm assuming this is something you want to happen when the window scrolls, no? In that case you have a few options:
The first is something a bit more familiar, and close to what you have, using the remove method to cleanup that binding:
var CampaignListView = Backbone.View.extend({
initialize: function(){
// You probably want to add an identifier to the event name, like 'scroll.campainlist' or something
$(window).on('scroll', _.bind(this.customFunc, this));
},
customFunc: function() {
if (this.$el.scrollTop() == 500 ) {
console.log("this has occurred, time to do stuff")
}
},
remove: function() {
$(window).off('scroll');
Backbone.View.prototype.remove.call(this);
}
)}
The other option is to use an Event Aggregator like so:
var vent = _.extend({}, Backbone.Events);
$(window).on('scroll', function(ev) {
vent.trigger('window:scroll', ev);
});
var CampaignListView = Backbone.View.extend({
initialize: function(){
this.listenTo(vent, 'window:scroll', this.customFunc);
},
customFunc: function() {
if (this.$el.scrollTop() == 500 ) {
console.log("this has occurred, time to do stuff")
}
}
)}
You can make use of delegateEvents in Backbone Views. For example you could attach to the scroll event of your view, but of course it will fire with every scroll. I put together a quick and very simple jsfiddle here based on your example. Below is some of the JavaScript. Notice the use of the events{} in the code.
var CampainListView = Backbone.View.extend({
el: '#list',
events: {
'scroll': 'customFunc'
},
initialize: function() { },
render: function() {
this.$el.html(sampleHtml);
return this;
},
customFunc: function() {
// console.log('scrolling... top is ' + this.$el.scrollTop());
if (this.$el.scrollTop() >= 100 ) {
console.log("this has occurred, time to do stuff")
}
}
});
var view = new CampainListView();
view.render();
I'd like to implement a reversible animation in Backbone, in the same way we do it in jquery :
$('a.contact').toggle(
function(){
// odd clicks
},
function(){
// even clicks
});
my question is how to do this in backbone's event syntax?
How to do I mimic the function, function setup?
events : {
'click .toggleDiv' : this.doToggle
},
doToggle : function() { ??? }
Backbone's view events delegate directly to jQuery, and give you access to all of the standard DOM event arguments through the callback method. So, you can easily call jQuery's toggle method on the element:
Backbone.View.extend({
events: {
"click a.contact": "linkClicked"
},
linkClicked: function(e){
$(e.currentTarget).toggle(
function() {
// odd clicks
},
function() {
// even clicks
}
);
}
});
I was looking for a solution to this problem and I just went about it the old fashioned way. I also wanted to be able to locate my hideText() method from other views in my app.
So now I can check the status of the 'showmeState' from any other view and run either hideText() or showText() depending on what I want to do with it. I have tried to simplify the code below by removing things like render and initialize to make the example more clear.
var View = Backbone.View.extend({
events: {
'click': 'toggleContent'
},
showmeState: true,
toggleContent: function(){
if (this.showmeState === false) {
this.showText();
} else {
this.hideText();
}
},
hideText: function() {
this.$el.find('p').hide();
this.showmeState = false;
},
showText: function() {
this.$el.find('p').show();
this.showmeState = true;
}
});
var view = new View();
Is the element you want to toggle within the view receiving the event? If so:
doToggle: function() {
this.$("a.contact").toggle()
}
I actually believe the only to do this using events is to add a trigger in order to keep the actual flow together. It seems a bit clumsy to be honest to have to use toggle in this way.
Backbone.View.extend({
events: {
"click .button": "doToggle"
},
doToggle: function(e){
var myEle = $(e.currentTarget);
$(e.currentTarget).toggle(
function() {
// odd clicks
},
function() {
// even clicks
}
);
myEle.trigger('click');
}
});
It's probably cleaner to just use
Backbone.View.extend({
el: '#el',
initalize: function() {
this.render();
},
doToggle: {
var myEle = this.$el.find('.myEle');
myEle.toggle(
function() {
// odd clicks
},
function() {
// even clicks
}
);
},
render: function(e){
//other stuff
this.doToggle();
return this;
}
});