jQuery: .html [get], innerHTML, etc.. destroys the events!
It was a PAIN learning about this. I was making a Backbone.js-based app and I could not in my life figure out why my Item instances had their events not firing.
Being new to JavaScript and jQuery, I thought it was some kind of missed comma or bad syntax or etc (darn dynamic language!).
But it was not. Apparently, events are handled by the DOM (I think jQuery 1.7+ handles events on its own cache now), and by trying to ‘clone’ the markup with tied events through jQuery’s .html() I am somewhat ‘stringifying’ the elements and their contents and stripping off all other ‘metadata’ that comes along with it.
So, when I ‘paste’ the clone, all events are destroyed…
Here’s a sample code that demonstrates that:
//$('#click-me').click();
document.getElementById("click-me").addEventListener("click",function() { alert("rvrv")});
var content = $('body').html();
$('body').html(content);
You see, the #click-me button will not fire because of the last line. Comment out the last line and it will work.
http://jsfiddle.net/jancarlo000/eEWXk/
Here’s a related stack-overflow question that I posted regarding this:
http://stackoverflow.com/questions/9123182/where-are-attached-event-handlers-meta-data-stored-on-the-dom-object-or
“Cannot call method ‘…’ of undefined” and JavaScript Scope Mechanism with ‘this’ keyword
Why doesn’t this work? When the event triggers, a “cannot call method ‘val’ of undefined” occurs. That is, the val from $('#display-first-name').text(this.$firstName.val());.
side note: manualSubmit works though just to make sure it’s not jQuery or my implementation
$(function () {
MainView = Backbone.View.extend({
el:'body',
initialize:function () {
this.$firstName = $('#input-first-name');
this.$lastName = $('#input-last-name');
this.$firstName.change(this.firstNameChanged);
this.$lastName.change(this.lastNameChanged);
},
events:{
'click #manual-submit':'manualSubmit'
},
firstNameChanged:function (e) {
$('#display-first-name').text(this.$firstName.val());
},
lastNameChanged:function () {
$('#display-last-name').text(this.$lastName.val());
},
manualSubmit:function (event) {
event.preventDefault();
$('#display-first-name').text($('#input-first-name').val());
$('#display-last-name').text($('#input-last-name').val());
}
});
var app = new MainView();
});
Let’s examine this: basically, it’s saying that $firstName is undefined. But why? I defined them on initialize?
Well, there are two concepts in play here, and that is: this is JavaScript, not C-family language. What does this mean? It means that it does not have the same behavior as ‘this’ like in C#. This also mean that JavaScript is not block-level scope like the C-family languages, but actually a function-level scope. This means that the context of ‘this’ changes based on its scope, and in JavaScript, the function is the one that changes it.
What does this mean on this code? It means that ‘this’ in this.$firstName.val(), while a valid code, will not work because ‘this’ refers to the firstNameChanged function, NOT the MainView instance…
So, how to solve it?
Change the value of ‘this’ through either doing var self = this on the initialize function or use underscore’s _.bindAll(this,'firstNameChanged','lastNameChanged') function.
Here’s the solution:
$(function () {
MainView = Backbone.View.extend({
el:'body',
initialize:function () {
_.bindAll(this, 'firstNameChanged', 'lastNameChanged', 'manualSubmit')
this.$firstName = $('#input-first-name');
this.$lastName = $('#input-last-name');
this.$firstName.change(this.firstNameChanged);
this.$lastName.change(this.lastNameChanged);
},
events:{
'click #manual-submit':'manualSubmit'
},
firstNameChanged:function (e) {
/*WORKS ALSO
$('#display-first-name').text($(e.currentTarget).val());
WORKS ALSO
$('#display-first-name').text($('#input-first-name').val())*/
$('#display-first-name').text(this.$firstName.val());
},
lastNameChanged:function () {
$('#display-last-name').text(this.$lastName.val());
},
manualSubmit:function (event) {
//this is very important, otherwise it would refresh the page
event.preventDefault();
$('#display-first-name').text($('#input-first-name').val());
$('#display-last-name').text($('#input-last-name').val());
}
});
var app = new MainView();
});
Backbone.js: Events not working! (apparently, due to disconnected Views)
This problem has got me pulling my hair for about two hours last night. I barely started learning JavaScript going from C# background, and this was one of those moments where I hated JavaScript. Apparently, the problem was that destroying the View through full HTML render disconnects the EVENTS attached within those views. Here’s the problematic code: (solution is afterwards)
Here’s the link: http://jsfiddle.net/jancarlo000/ejn2J/ or check out the code below:
var Item = Backbone.Model.extend({defaults: {Task: 'Study!'}});
var List = Backbone.Collection.extend({});
var MainView = Backbone.View.extend({
tagName: 'ul',
className: '',
initialize: function(){
_.bindAll(this, 'render');
window.list = new List();
window.list.bind('add', this.render, this);
},
render: function(model, collection, options){
var itemView = new ItemView({model:model});
$(this.el).append(itemView.el);
console.log($(this.el));
$('body').html(this.el);
return this;
}
});
var ItemView = Backbone.View.extend({
tagName: 'li',
events: {
"click #deleteMe":"deleteMe"
},
initialize: function(){
_.bindAll(this, 'render');
this.render();
},
render: function(){
console.log('model.get("Task"): ', this.model.get('Task'));
console.log("this.el", this.el);
$(this.el).append(this.model.get('Task') + 'Delete Me');
//WHY WON'T THIS FIRE!!!
return this;
},
deleteMe: function(e){
//e.preventDefault();
console.log('called deleteMe');
$(this.el).remove();
}
});
$(function(){
var app = new MainView();
var first = new Item({Task:'Learn JavaScript'});
var second = new Item({Task:'Learn HTML5'});
var third = new Item({Task:'Learn Backbone.JS'});
var fourth = new Item({Task:'Learn .NET MVC'});
list.add([first, second, third, fourth]);
});
Now, for the solution! I posted this on Stackoverflow, and there was someone who helped me. Here’s the link: http://stackoverflow.com/questions/9077787/backbone-js-my-events-would-not-fire-how-can-i-fix-this-small-code
In addition, Derrick Bailey himself helped me through this through twitter:
@virayjancarlo don’t append things to the body, inside a render method. that was reproducing a copy of the html every time
@virayjancarlo disconnecting it from the view that it came from, meaning the events could not be listened to
@virayjancarlo i moved the $(‘body’).html(…) line, and it works jsfiddle.net/6YH7a/1/
Here’s the solution to the code! :)
http://jsfiddle.net/6YH7a/1/ (by Derrick Bailey)
However, user nikoshr (http://stackoverflow.com/users/1071630/nikoshr) provided me with a bit more comprehensive one with more explanation about things here: http://stackoverflow.com/a/9078456/985895
In your main view render, you use $(‘body’).html(this.el); which replaces the views and their event handlers with unbound elements.
Removing this line and appending the main view el to the body in the initialize method gives the intended result
And if I may, a few opinions
don’t create your Collection in your view, create it in your init code and pass it to your view, the view should not be responsible for creating the models/collections of your app and you may need to reference the collection somewhere else,
your MainView render should be used to re-render the whole list, not a lone model: its purpose is to maintain the UI display of the collection, you probably will need at a later time to add/edit/delete a model and refresh its representation
don’t tie your MainView to the body in the view code, build the view in memory and then append its el to its parent, it’s faster and much easier to move around at a later time
var Item = Backbone.Model.extend({
defaults: {
Task: 'Study!'
}
});
var List = Backbone.Collection.extend({});
var MainView = Backbone.View.extend({
tagName: 'ul',
className: '',
initialize: function() {
_.bindAll(this, 'render', 'renderOne');
this.collection.bind('reset', this.render);
this.collection.bind('add', this.renderOne);
},
render: function() {
this.collection.each(this.renderOne);
return this;
},
renderOne: function(model, collection, options) {
var itemView = new ItemView({
model: model
});
$(this.el).append(itemView.el);
return this;
}
});
var ItemView = Backbone.View.extend({
tagName: 'li',
events: {
"click .deleteMe": "deleteMe"
},
initialize: function() {
_.bindAll(this, 'render');
this.render();
},
render: function() {
$(this.el).html(this.model.get('Task') + 'Delete Me');
//WHY WON'T THIS FIRE!!!
return this;
},
deleteMe: function(e) {
//e.preventDefault();
console.log('called deleteMe ' + this.model.get('Task'));
$(this.el).remove();
}
});
$(function() {
var list = new List();
var first = new Item({
Task: 'Learn JavaScript'
});
var second = new Item({
Task: 'Learn HTML5'
});
var third = new Item({
Task: 'Learn Backbone.JS'
});
var fourth = new Item({
Task: 'Learn .NET MVC'
});
list.add([first, second, third, fourth]);
var app = new MainView({
collection: list
});
$('body').append(app.render().el);
});
Julie on design, business, life and all things web: On necessary skills and secrets about hiring
This morning @smashingmag posted a link to this article titled “Skills for Front-End Developers” and I counted 43 skills on that list. First of all, let’s all make a resolution for 2012 to stop making lists and produce content instead. Now, let’s roll up our sleeves and look deeper into…
Why is it critically important to eat 5 times a day?
I’ve always wondered why…
Now I’m experiencing it personally. I have started my Insanity workouts about two weeks ago and I have been losing weight FAST — that is, 2lbs a week!
The reason I was able to get those results was because I tried increasing my metabolism by keeping myself active and eating small meals 5x a day. To add a little bit of history here, I have been living sedentary for about a whole year due to me transferring my career to web development. This means I am on the computer, sitting down pretty much majority of the day. In addition, I have been eating about 2 or 3 HUGE portions throughout the day.
Changing my lifestyle from eating 2 or 3 huge portions to 5 small meals was pretty drastic change. I had to fight through the cravings and monitored my portions so that I would not overeat on my portions.
This “tug-o-war” between my old and new lifestyle lasted for about two weeks until I was able to be comfortable with it.
As a result, my metabolism increased and I had an increase of fat-loss/day. This helped me lose 2lbs/week.
In contrast to my blissful results… this week have been a huge slump for me. Due to life circumstances, I went back to my old habits and not ONLY did I lose energy physically, but I also gained fat.
It was also harder for me to lose weight. I had to work extra hard to actually bring results. I realized that the reason being was that by eating small huge portions, my body stored what I ate as fat thinking by itself that I would not eat later. Apparently, our body adapts to our lifestyle.
Now, I need to change back my habits and my lifestyle drastically again so that I can reach my goal. But at least I know what to do now and I’ll be learning from my mistakes.
Back to the drawing board again and focus on increasing my metabolism. If any of you have questions how I did this, feel free to comment on this post.
