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>
Related
I created a view and has the ff codes:
var app = app || {};
app.singleFlowerView = Backbone.View.extend({
tagName: 'article',
className: 'flowerListItem',
// tells where to apply the views
template: _.template( $("#flowerElement").html() ),
// render
render: function(){
var flowerTemplate = this.template(this.model.toJSON());
// el contains all the prop above and pass to backbone
this.$el.html(flowerTemplate);
return this;
},
events: {
'mouseover': 'addBgColor',
'mouseout': 'removeBgColor'
},
addBgColor: function(){
this.$el.addBgColor('bgColorImage');
},
removeBgColor: function(){
this.$el.removeBgColor('bgColorImage');
}
});
When I run this to my HTML file I got the error addBgColor and removeBgColor is not a function. I have the CSS for this and all the models and views were set up.
Am I missing something here? Any idea why events doesn't work?
this.$el.addBgColor is the problem.
The events are triggering but you're calling addBgColor on the $el jQuery object, which is not a jQuery function, like the error message is telling you.
Check what's the difference between $el and el.
Tony, your events are cool and they are running they're just not doing anything.
this.addBgColor() will call your function in a view.
this.$el is referring to the html and there's no property called addBgColor assigned to $el.
You need to do something like change the class on your tag with the functions like so...
addBgColor: function(){
this.$el.className = 'bgColorImage'
},
.bgColorImage {
background-image: url('bgColorImage.jpg');
}
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/
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",
},
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'm starting out with backbone.js building my first project with backbone-boilerplate.
I have a module named Navitem with a view called Sidebar:
Navitem.Views.Sidebar = Navitem.Views.Layout.extend({
template: "navitem/sidebar",
tagName: 'ul',
beforeRender: function()
{
var me = this;
this.options.navitems.each(function(navitem)
{
//insertView from Layout datatype
me.$el.append(new Navitem.Views.Item({
model: navitem //select the 'ul' in sidebar view and append an Item with model navitem
}).render().el);
});
return this;
}
});
When the sidebar is constructed, a collection containing many Navitem.Model's are passed into it. After debugging, model:navitem seems to be working correctly and passing in the right navitem model to the new Navitem.Views.Item({...}). That class looks like:
Navitem.Views.Item = Navitem.Views.Layout.extend({
tagName: 'li',
template: 'navitem/default'
events: {
click: "navRoute"
},
navRoute : function()
{
app.router.go(this.model.get('target'));
return this;
}
});
The template looks like <%= model.get('label') %>.
For some reason when I call Item.render() in the first code block, it whines that model is undefined in the view. I can't seem to figure out why this is happening. Any thoughts?
Might be related to what was answered here : Backbone.js: Template variable in element attribute doesn't work
You need to pass the model as a plain JSON to your template (unless maybe you're using another version?)
Hope this helps!
I'm doing something similar in a program that I wrote using Backbone Boilerplate and Backbone LayoutManager.
Try adding a serialize function to your Navitem.Views.Item view:
// provide data to the template
serialize: function() {
return this.model.toJSON();
}
and then in the beforeRender function of Navitem.Views.Sidebar:
beforeRender: function(){
_.each(this.options.navitems.models, function(model){
var view = new Navitem.Views.Item({model: model});
this.insertView(view);
}, this);
}
and the navitem/default template could look like this:
<%= label %>
This is untested code (using your views and collections) but doing this has been working for me.