Template:
<div class="view">
<input id="todo_complete" type="checkbox" <%= completed ? 'checked="checked"' : '' %> />
<label><%= title %></label>
<button class="destroy"></button>
</div>
<input class="edit" value="<%= title %>" />
View:
var TodoView = Backbone.View.extend({
tagName: 'li',
todoTpl: _.template($('#item-template').html()),
events: {
'dblclick label': 'edit',
'keypress .edit': 'updateOnEnter',
'blur .edit': 'close'
},
initialize: function() {
console.log('Todo View initialized!');
this.$el = $('#todo');
console.log(this.$el);
this.render();
},
render: function() {
console.log('Todo View render started...');
console.log(this.model.attributes);
this.$el.html(this.todoTpl(this.model.attributes));
},
edit: function() {console.log('edit called!');},
close: function() {console.log('close called!');},
updateOnEnter: function(e) {
console.log(e);
}
});
There are no events when the page loads, and I'm able to see the rendered template. However if I double click on the label, keypress on the input or blur the other input, nothing happens. I'm expecting to see a log in the console. What am I doing wrong?
You lose your event bindings when you:
this.$el = $('#todo');
You're not supposed to directly assign to the $el or el properties, you're supposed to call setElement instead:
setElement view.setElement(element)
If you'd like to apply a Backbone view to a different DOM element, use setElement, which will also create the cached $el reference and move the view's delegated events from the old element to the new one.
Also, if you're going to be changing the view's el then there's no need for a tagName property. You could also specify the el when you create the view:
new TodoView({ el: '#todo' });
new TodoView({ el: $('#todo') });
If your #todo is actually the <ul> or <ol> then:
Leave the tagName alone.
Add the view's el to $('#todo') by calling append instead of calling setElement.
If this is the case then your code would look more like this:
var TodoView = Backbone.View.extend({
tagName: 'li',
//...
initialize: function() {
console.log('Todo View initialized!');
this.render();
},
//...
});
// And then where you create the view...
var v = new TodoView(...);
$('#todo').append(v.el);
So I was able to solve this problem by adding el to the properties passed into Backbone.View:
var TodoView = Backbone.View.extend({
tagName: 'li',
// ...
el: $('#todo')
// ..
});
Related
I have the following structure:
var PopupView = Backbone.View.extend({
el:'#shade',
events:{
'click .popup-cancel': 'hide',
'click .popup-commit': 'commit',
},
show: function(){
...
},
hide: function(){
...
},
initialize: function(){
_.bindAll(this, 'show','hide','commit');
}
});
var Popup1 = PopupView.extend({
render: function(){
...
},
commit: function(){
console.log('1');
...
},
});
var Popup2 = PopupView.extend({
render: function(){
...
},
commit: function(){
console.log('2');
...
},
});
The problem is that when I click .popup-commit from one of the popups, it actually triggers the methods of both of them. I've tried moving the declaration of events and initialize() up into the child classes, but that doesn't work.
What's going on, and how can I fix it (so that the commit method of only the view I'm triggering it on gets fired)?
Your problem is right here:
el:'#shade'
in your PopupView definition. That means that every single instance of PopupView or its subclasses (except of course those that provide their own el) will be bound to the same DOM node and they will all be listening to events on on id="shade" element.
You need to give each view its own el. I'd recommend against ever setting el in a view definition like that. I think you'll have a better time if you let each view create (and destroy) its own el. If you do something like:
var PopupView = Backbone.View.extend({
className: 'whatever-css-class-you-need',
tagName: 'div', // or whatever you're using to hold your popups.
attributes: { /* whatever extra attributes you need on your el */ },
//...
});
then your views will each get their own el. See the Backbone.View documentation for more information on these properties.
I have a parent view (and associated model), with several children Views with the same associated model. The parent view is defined statically from the HTML. These events are working fine.
The children views are created dynamically, and are ultimately different, but have some similar initial structure. The #id s will be different from each other (using the view id number) so that we can know which one is interacted with by the user. I have tried the following from reading around:
Adding the el declaration when I create the View (towards the end of the JS)
statically defining it, then trying to update it.
using _.ensureElement()
setting the el in the init()
But I just can't seem to get it for the children views on a fiddle.
Fiddle
JS: Parent
//The view for our measure
parentModule.View = Backbone.View.extend({
//
//This one is static, so I can set it directly, no problem, the events are working
//
el: $('#measure-container'),
events: {
'click .test': 'test'
},
test: function(){
alert('test');
},
initialize: function() {
this.template = _.template($('#instrument-template').html());
},
render: function(){
$(this.el).append(this.template(this.model.toJSON()));
return this;
}
});
return parentModule;
});
JS: Child
// THe child views are dynamic, so how do I set their id's dynamicall and still get the click events to fire?
//
childModule.View = Backbone.View.extend({
//
// Do I set the el here statically then override?
//
events: {
'click .remove-rep' : 'removeRepresentation',
'click .toggle-rep' : 'toggleRepType',
'click .sAlert': 'showAlert'
},
initialize: function(options) {
//
//Do I set the el here using ensure_element?
//
this.model=options.model;
},
render: function(){
//
// Do I Set it here when it renders?
//
this.template = _.template($('#rep-template').html());
$('#measure-rep-container').append(this.template());
return this;
},
showAlert: function() {
alert("This is an alert!");
}
});
JS: Instantiation
define( "app", ["jquery", "backbone", "parentModule", "childModule"], function($, Backbone, ParentModule, ChildModule) {
var app = {};
app.model = new ParentModule.Model({ name: "Snare", numOfBeats: 4 });
app.view = new ParentModule.View({ model: app.model });
app.view.render();
app.firstRepChildModel = new ChildModule.Model({ id: 1, type: 'circle', parentModel: app.model });
//
// Do I add the el as a parameter when creating the view?
//
app.firstRepChildView = new ChildModule.View({ el:'#rep'+app.firstRepChildModel.get('id'), model: app.firstRepChildModel });
app.firstRepChildView.render();
app.secondRepChildModel = new ChildModule.Model({ id: 2, type: 'line', parentModel: app.model });
//
// Do I add the el as a parameter when creating the view?
//
app.secondRepChildView = new ChildModule.View({ el:'#rep'+app.secondRepChildModel.id, model: app.secondRepChildModel });
app.secondRepChildView.render();
return app;
});
HTML:
<h3>Measure View</h3>
<div id="measure-container">
</div>
<!-- Templates -->
<script type="text/template" id="instrument-template">
<div class="instrument">
I am an instrument. My name is <%=name%>. <br/>
Here are my children repViews: <br/>
<div id="measure-rep-container">
<div class="btn btn-primary test">Add a rep</div>
</div>
</div>
</script>
<script type="text/template" id="rep-template">
<div class="rep" id="rep<%=this.model.id%>">
I am a repView <br/>
My ID is 'rep<%=this.model.id%>' <br/>
My el is '<%=this.$el.selector%>'<br/>
My type is '<%=this.model.type%>' <br/>
I have this many beats '<%=this.model.numOfBeats%>' <br/>
<div class="beatContainer"></div>
<div class="btn btn-danger remove-rep" id="">Remove this rep</div>
<div class="btn btn-primary toggle-rep" id="">Toggle rep type</div>
<div class="btn sAlert">Show Alert</div>
</div>
</script>
Every view has a associated el, whether you set it directly or not, if you don't set it then it's el is just a empty div.
In your code you aren't modifying your child view's el or attaching it to the DOM.
Try the following
render: function(){
this.template = _.template($('#rep-template').html());
//this sets the content of the el, however it still isn't attached to the DOM
this.$el.html(this.template());
$('#measure-rep-container').append(this.el);
return this;
},
Updated fiddle
As a separate point if you are going to be reusing the same template multiple times you might want to just compile it once, for example
childModule.View = Backbone.View.extend({
events: {
'click .remove-rep' : 'removeRepresentation',
'click .toggle-rep' : 'toggleRepType',
'click .sAlert': 'showAlert'
},
//get's called once regardless of how many child views you have
template: _.template($('#rep-template').html()),
render: function(){
this.$el.html(this.template());
$('#measure-rep-container').append(this.el);
return this;
},
In this code sample, a Backbone view is bound to a pre-existing DOM element. The scroll event triggers as expected.
In this alternate sample, the Backbone view renders the HTML instead of using a pre-existing DOM element. The scroll event doesn't fire.
Why?
The primary difference is the second sample does this:
this.$el.html(template);
This works:
http://jsfiddle.net/hKWR9/1/
$(function(){
var MyView = Backbone.View.extend({
className: 'scrollbox',
events: {
'click': 'onClick',
'scroll': 'onScroll'
},
initialize: function() {
this.render();
},
render: function() {
var template = '<div class="filler"></div>';
$('body').append(this.$el);
this.$el.html(template);
},
onClick: function() {
console.log('click');
},
onScroll: function() {
console.log("scroll");
}
});
var App = new MyView();
}());
Your fiddle doesn't work, because you defined your el with classname .scrollbox, while it should have been scrollbox. There doesnt seem to be a benefit in creating another 'scrollbox' within this '.scrollbox'.
Trying to create a todo example app to mess around with backbone. I cannot figure out why the click event for the checkbox of a task is not firing. Here is my code for the TaskCollection, TaskView, and TaskListView:
$(document).ready(function() {
Task = Backbone.Model.extend({});
TaskCollection = Backbone.Collection.extend({
model: 'Task'
});
TaskView = Backbone.View.extend({
tagName: "li",
className: "task",
template: $("#task-template").html(),
initialize: function(options) {
if(options.model) {
this.model = options.model
}
this.model.bind('change',this.render,this);
this.render();
},
events: {
"click .task-complete" : "toggleComplete"
},
render: function(){
model_data = this.model.toJSON();
return $(_.template(this.template, model_data));
},
toggleComplete: function() {
//not calling this function
console.log("toggling task completeness");
}
});
TaskListView = Backbone.View.extend({
el: $("#task-list"),
task_views: [],
initialize: function(options) {
task_collection.bind('add',this.addTask,this);
},
addTask: function(task){
task_li = new TaskView({'model' : task});
this.el.append(task_li.render());
this.task_views.push(task_li);
},
});
});
Template for the task:
<script type='text/template' id='task-template'>
<li class="task">
<input type='checkbox' title='mark complete' class='task-check' />
<span class='task-name'><%= name %></span>
</li>
</script>
I can't seem to figure out why the toggleComplete event will not fire for the tasks. how can I fix this?
The problem here is that the backbone events only set to the element of the view (this.el) when you create a new view. But in your case the element isn't used. So you have the tagName:li attribute in your view, which let backbone create a new li element, but you doesn't use it. All you return is a new list element created from your template but not the element backbone is creating, which you can access by this.el
So you have to add your events manually to your element created by your template using jQuery or add your template as innerHtml to your element:
(this.el.html($(_.template(this.template, model_data)))
Try changing the lines where you set your listeners using .bind() to use .live(). The important difference is .live() should be used when you want to bind listeners to elements that will be created after page load.
The newest version of jQuery does away with this bit of ugliness and simplifies the methods used to set event listeners.
Your event is binding to a class of .task-complete but the class on your checkbox is .task-check
Try modifying your render function to call delegateEvents() like so:
render: function(){
model_data = this.model.toJSON();
this.el = $(_.template(this.template, model_data));
this.delegateEvents();
return this.el;
},
You'd really be better off changing your template to not include the li and then return this.el instead of replacing it, but if you want the events to work you need to have this.el be the root element one way or another; delegateEvents() re-attaches the event stuff, so when you change this.el that should fix the issue.
#Andreas Köberle answers it correctly. You need to assign something to this.elto make events work.
I changed your template and your TaskView#render() function.
This JSFiddle has the changes applied.
New render function:
render: function(){
var model_data = this.model.toJSON();
var rendered_data = _.template(this.template, model_data);
$(this.el).html(rendered_data);
return this;
}
It is recommended that the render() returns this.
One line in your TaskListView#addTask function changes from this.el.append(task_li.render()); to this.el.append(task_li.render().el);.
Template change
Since we are using this.el in the render() function, we have to remove the <li> tag from the template.
<script type='text/template' id='task-template'>
<input type='checkbox' title='mark complete' class='task-complete' />
<span class='task-name'><%= name %></span>
</script>
Okay, so I've read several other questions regarding Backbone views and events not being fired, however I'm still not getting it sadly. I been messing with Backbone for about a day, so I'm sure I'm missing something basic. Here's a jsfiddle with what I'm working with:
http://jsfiddle.net/siyegen/e7sNN/3/
(function($) {
var GridView = Backbone.View.extend({
tagName: 'div',
className: 'grid-view',
initialize: function() {
_.bindAll(this, 'render', 'okay');
},
events: {
'click .grid-view': 'okay'
},
okay: function() {
alert('moo');
},
render: function() {
$(this.el).text('Some Cow');
return this;
}
});
var AppView = Backbone.View.extend({
el: $('body'),
initialize: function() {
_.bindAll(this, 'render', 'buildGrid');
this.render();
},
events: {
'click button#buildGrid': 'buildGrid'
},
render: function() {
$(this.el).append($('<div>').addClass('gridApp'));
$(this.el).append('<button id="buildGrid">Build</button>');
},
buildGrid: function() {
var gridView = new GridView();
this.$('.gridApp').html(gridView.render().el);
}
});
var appView = new AppView();
})(jQuery);
The okay event on the GridView does not fire, I'm assuming because div.grid-view does not exist when the event is first bound. How should I handle the binding and firing of an event that's built on a view dynamically? (Also, it's a short example, but feel free to yell at me if I'm doing anything else that I shouldn't)
Your problem is that the events on GridView:
events: {
'click .grid-view': 'okay'
}
say:
when you click on a descendent that matches '.grid-view', call okay
The events are bound with this snippet from backbone.js:
if (selector === '') {
this.$el.on(eventName, method);
} else {
this.$el.on(eventName, selector, method);
}
So the .grid-view element has to be contained within your GridView's this.el and your this.el is <div class="grid-view">. If you change your events to this:
events: {
'click': 'okay'
}
you'll hear your cows (or "hear them in your mind" after reading the alert depending on how crazy this problem has made you).
Fixed fiddle: http://jsfiddle.net/ambiguous/5dhDW/