View:
Member.Views.Popover = Backbone.View.extend({
template: "member/default",
tagName: 'a',
className: 'header-auth member',
events: {
'click a.member': 'toggle'
},
initialize: function() {
//todo
},
toggle: function(){
console.log("toggle");
}
});
Output:
<a class="header-auth member">
//content from template
</a>
First problem: first I defined just a template without tagName and className because this data was already set in the template. But this wrapped the template with a div. To avoid that I set tagName and className and removed this data from template because now it is set by backbone.
Second problem: now that I use tagName the click event does not work anymore.
Any ideas how to fix this?
You can should change click a.member to merely click, since the element is your view.
Related
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')
// ..
});
The extra wrapping element generated by Backbone/Marionette views causes problems (with jQuery Mobile), so I need to unwrap it. After unwrapping, the layout is good, but events are no longer fired (see demo).
How to unwrap the extra element while maintaining events?
Demo
It uses the Marionette.BossView plugin but the idea is the same without.
Marionette.ItemView extends from Backbone.View which has property named tagName. When you mentioning it in view declaration your wrapping element became the element mentioned in tagName.
For you demo example better to use tagName instead of changing the way Marionette renders the view.
Change you view to the following and it will work!
var SubView1 = Backbone.Marionette.ItemView.extend({
className: 'SubView1',
template: function () {
return '(SubView1) Click Me!'; // moved button tag and mentioned it in tagName
},
tagName: 'button',
triggers: {
'click': 'click:button' // no need to 'button' selector, it's already a root element
}
});
var SubView2 = Backbone.Marionette.ItemView.extend({
className: 'SubView2',
template: function () {
return '(SubView2) Click Me!'; // moved button tag and mentioned it in tagName
},
tagName: 'button',
triggers: {
'click': 'click:button' // no need to 'button' selector, it's already a root element
}
});
var TopView = Backbone.Marionette.BossView.extend({
className: 'TopView',
subViews: {
buttonView1: SubView1,
buttonView2: SubView2
},
subViewEvents: {
'buttonView1 click:button': 'onSubViewClickButton',
'buttonView2 click:button': 'onSubViewClickButton' // there was a typo here
},
onSubViewClickButton: function () {
$('body').append('<div>You clicked it, and TopView responded!</div>');
}
});
var topView = new TopView();
topView.render().$el.appendTo($('body'));
Hope this helps!
I'm trying to develop my first backbone application. All seems ok, but when i render the view and append some html to the $el, nothing is rendered in the page.
Rest service calls done ok, the Backbone.Router.extend is declared inside $(document).ready(function () {}); to ensure that the DOM is created.
Debugging my javascript, the el element get to contain the correct value in the innerHTML property, but when the whole page is rendered, this value doesn't appear in the page.
¿What am i doing wrong?
My View code:
window.ProductsListView = Backbone.View.extend({
id: 'tblProducts',
tagName: 'div',
initialize: function (options) {
this.model.on('reset', this.render, this);
},
render: function () {
// save a reference to the view object
var self = this;
// instantiate and render children
this.model.each(function (item) {
var itemView = new ProductListItemView({ model: item });
var elValue = itemView.render().el;
self.$el.append(elValue); // Here: the $el innerHTML is ok, but in the page it disappear. The id of element is div#tblProducts, so the element seems correct
});
return this;
}
});
window.ProductListItemView = Backbone.View.extend({
tagName: 'div',
template: _.template(
'<%= title %>'
),
initialize: function (options) {
this.model.on('change', this.render, this);
this.model.on('reset', this.render, this);
this.model.on('destroy', this.close, this);
},
render: function () {
$(this.el).html(this.template(this.model.toJSON()));
// $(this.el).html('aaaaaa'); // This neither works: it's not a template problem
return this;
},
close: function () {
$(this.el).unbind();
$(this.el).remove();
}
});
Here i load products (inside Backbone.Router.extend). This is executed correctly:
this.productsList = new ProductsCollection();
this.productsListView = new ProductsListView({ model: this.productsList });
this.productsList.fetch();
And this is the html element i want to render:
<div id="tblProducts">
</div>
Thanks in advance,
From the code you have posted, you are not actually inserting your ProductsListView in to the DOM or attaching it to an existing DOM element.
The way I like to look at it is you have two types of Views:
Those that are dynamically generated based on data returned from the server
Those that already exist on the page
Usually in the case of lists, the list already exists on the page and it's items are dynamically added. I have taken your code and restructured it slightly in this jsfiddle. You will see that the ProductListView is binding to an existing ul, and ProductItemView's are dynamically appended when they are added to the Collection.
Updated jsfiddle to demonstrate Collection.reset
The el property exists within the view if it is rendered or not. You can't say it is ok there because Backbone will create an element if no element is passed (empty div).
If you want to render the view you should determine what is the container of the element? Do you have an html you want to attach the view to?
Try passing a container element by calling the view with an el like
this.productsListView = new ProductsListView({ model: this.productsList, el : $("#container") });
Of course you can create the view and attach it to the DOM later:
el: $("#someElementID") //grab an existing element
el.append(view.render().el);
Your view wont exist in the dom until you attach it somewhere.
I was trying the backbone.js examples given here and then tried writing some code on my own.
For some reason the event handler I have attached to event 'click p' is not working. Why is the 'highlight' function not executing when a paragraph tag is clicked?
var ItemView = Backbone.View.extend({
tagName : 'p',
events: {
'click p': 'highlight'
},
initialize: function(){
console.log("An object of ItemView was created");
_.bindAll(this, 'render', 'highlight');
this.render();
},
render: function(){
this.$el.text(this.model.get('content'));
$('body').append(this.$el);
return this;
},
highlight: function(){
console.log('clicked');
}
});
This event it targeting any <p> element inside your root element. It is not targeting your root element, even if your root element is a <p> element.
Try:
events: {
'click': 'highlight'
}
To target the root element.
Hmmm... in your render, you replace all the body content with this.$el. I'm thinking you've effectively "unhooked" the events hash when you replace all the content. So after you do the append, call this.delegateEvents() like so:
render: function(){
this.$el.text(this.model.get('content'));
$('body').append(this.$el);
this.delegateEvents();
return this;
}
This should allow you to click on your paragraph tags.
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>