I have a decent understanding of the "this" keyword, but for some reason it's still tripping me up in this specific case. Inside the bindEvents method, when I'm binding the submit event to the form, it then executes fetchTweets. I understand that now that it's inside a callback function from the "on" method, so "this" now refers to the form that the event was bound to, rather than the parent object "Tweets".
My understanding was that it is common practice to declare self = this at the top of a method to cache the parent object to prevent later issues with a callback, but in this case it won't work because the sole purpose of that method is to be a callback function for the form submission event.
I know about .call and .apply and even $.proxy, I was just wondering if there was a need to use them in this case, or if I'm missing something obvious. I have this code working using $.proxy, I just thought there might be a smarter way of going about it.
var Tweets = {
init: function ( config ) {
var self = this;
self.config = config;
self.url = 'http://search.twitter.com/search.json?callback=?';
self.bindEvents();
},
bindEvents: function() {
var self = this;
// bind events as needed
self.config.form.on('submit', self.fetchTweets );
},
fetchTweets: function(e) {
e.preventDefault();
var self = this;
var term = self.config.form.find('#term').val();
// grab tweets from the server
$.getJSON(self.url, { q: term }, function(data) {
self.displayTweets(data);
});
},
displayTweets: function(data) {
var self = this;
var tweetList = self.config.list;
tweetList.empty();
// append each tweet to the list
$.each(data.results, function(index, tweet){
$('<li></li>').text(tweet.text).appendTo(tweetList);
});
}
};
Tweets.init({
form: $('#getTweets'),
list: $('#tweets')
});
Instead of using self.<member>, try using Tweets.<member>. You can't do var self = this inside the method, because this is already something other than Tweets. But since you have a variable to refer to the object you're creating, you can just use that. :)
You could also wrap your event handler in an anonymous function as follows:
self.config.form.on('submit', function(e) { self.fetchTweets(e); });
Then don't do var self = this; in any method other than the method that binds the handlers. It's safe to use this.
Related
I feel my whole understanding of this has been thrown up in the air.
I have a Quiz object which holds the necessary variables and methods required to play the quiz.
I am trying to reference a method of Quiz from another method in Quiz (getQuestion in skipQuestion()) however, I am seeing a message in the console saying that this.getQuestion is not defined. I was under the impression that this in this case refers to the object it is in, hence the function in question should be referred to as this.getQuestion().
The error message I am getting is script.js:18 Uncaught TypeError: this.getQuestion is not a function
Can anyone explain what is going wrong here?
In my init function it seems that this refers to the Quiz object, but in skip question it seems to change. Is this down to query having a different definition of this? where do you draw the line, and when is the context of this changed?
(function(window){
$(document).ready(function(){
var Quiz = {
score : 0,
question: '',
answer: '',
init: function() {
this.getQuestion();
this.checkAnswer();
this.skipQuestion();
},
skipQuestion: function() {
$('#skip').click(function(){
this.getQuestion();
})
},
getQuestion: function() {
$.get('http://jservice.io/api/random', function(data){
$('#question').html(data[0].question);
this.answer = data[0].answer.toLowerCase();
});
},
checkAnswer: function() {
if($('#answer').val() === this.answer) {
this.score += 1;
}
}
}
Quiz.init();
});
})(window);
Because you are nesting inside another function, the this context changes to that function, so the methods you look for are no longer available. You can try to solve it by either storing the this inside a variable that will be within the scope of the function you are defining, or by using Double Arrow Functions, which have no associated this context themselves (and therefor also don't support bind or call). Here are your options:
Declare a variable:
skipQuestion: function() {
var that = this;
$('#skip').click(function(){
that.getQuestion();
})
}
or a Double Arrow Function:
skipQuestion: function() {
var that = this;
$('#skip').click(() => that.getQuestion())
}
Your init function is considered a method of your Quiz object, while the anonymous function passed to the click event is not a method of your Quiz, it is a method of an anonymous object created in the background, and shares no methods or variables with your Quiz. This is important to consider!
The thing is you are using this inside the click event and it refers to the event rather than you context. To work around you need to assign this to another variable and then use that;
skipQuestion: function() {
var self = this;
$('#skip').click(function(){
self.getQuestion();
})
},
$.get and .click event create their own context and thus this refers to their context instead of the context of quiz.
JS
(function(window){
$(document).ready(function(){
var Quiz = {
score : 0,
question: '',
answer: '',
init: function() {
this.getQuestion();
this.checkAnswer();
this.skipQuestion();
},
skipQuestion: function() {
var self = this;
$('#skip').click(function(){
that.getQuestion();
})
},
getQuestion: function() {
var self = this;
$.get('http://jservice.io/api/random', function(data){
$('#question').html(data[0].question);
self.answer = data[0].answer.toLowerCase();
});
},
checkAnswer: function() {
if($('#answer').val() === this.answer) {
this.score += 1;
}
}
}
Quiz.init();
});
})(window);
I'm new to javascript modular programming and I have been running into this issue a lot and I'm not sure what the best way to deal with it.
Let's say I have a method that I want to use as both an event handler in some circumstances and as part of the regular flow of the module initialization in others depending on the url parameters.
var people = {
render: function(data){
// This takes data from a JSON object and turns it into HTML
var html = '';
for(var i; i < data.length; i++){
html += '<li><a data-firstname="'+data.firstName+'" data-lastname="'+data.lastName+'">Some text</a>';
}
$('#element').html(html).find('li a').click(this.getFullName);
},
getFirstName: function(event){
// This method is called both as an event handler or as part of the initialization process.
if(event !== undefined){
event.preventDefault();
}
var fullName = $(this).attr('data-firstname') || someOtherSource.firstName;
},
someOtherMethod: function(){
this.getFirstName();
}
};
The thing about it though is that I lose the 'this' keyword, because this needs to refer to the DOM element that is being acted upon. I know I can do something like:
var self = this;
$(#element).click(self.getFirstName.bind(this, self));
And pass self as a parameter, but what about the event object? Will it interpret 'self' as the event object?
One standard strategy is to utilize closures:
var self = this;
$(#element).click( function(evt) { self.getFirstName(evt); } );
The other is to create a bound function:
$(#element).click(this.getFirstName.bind(this));
Both techniques invoke the function with the intended context (value of this).
I guess that's the simple question. I'm new in js, especially in Backbone.js.
All I want to know is how I can refer to my function inside jquery function.
getLanguages: function() {
...
return languages;
},
render: function() {
...
$("input[type='checkbox']").bind("change", function() {
// todo: getLanguages
});
}
I tried to get languages via this but, of course, I got checkbox in this case.
Edit:
It's so simple. Many thanks to all!!!
This is a classic problem in Javascript. You need to reference this inside a callback, but this changes to the element being bound to. A cheap way to do it:
render: function() {
var that = this;
$("input[type='checkbox']").bind("change", function() {
that.getLanguages();
});
}
that will stay defined as the this that render is defined on.
However, you’re using Backbone, and it has more suitable ways to handle this situation. I don’t know the name of your Backbone.View class, but here’s an example adapted from the documentation:
var DocumentView = Backbone.View.extend({
events: {
"change input[type='checkbox']": "doSomething"
},
doSomething: function() {
this.getLanguages(); # uses the correct this
}
});
Calling bind inside render is not The Backbone Way. Backbone views are made to handle event delegation without the unfortunate need to pass this around.
Save this object before bind change event in the scope of render function.
render: function() {
var CurrentObj = this;
$("input[type='checkbox']").bind("change", function() {
CurrentObj.getLanguages();
});
}
You can save the appropriate object into a local variable so from the event handler, you can still get to it:
getLanguages: function() {
...
return languages;
},
render: function() {
...
var self = this;
$("input[type='checkbox']").bind("change", function() {
var lang = self.getLanguages();
...
});
}
I've learned that for scope reasons the this keyword inside an event listener, which is embedded in an object, doesn't refer to the global object but rather to the element which triggered the event.
Now, I understand that if I want to fetch a property I can save it to a variable before the event handler is called. But what can I do if I want to manipulate the property's value?
In the following piece of code I am trying to manipulate the drugCount property within the removeDrug event listener.
var Drugs = {
drugs: $("#drugs_table"),
drugRow: $("#drug").html(),
drugCount: 0,
init: function() {
this.addDrugRow();
this.removeDrugRowHandler();
},
addDrugRow: function() {
this.drugCount++;
this.drugs.append(this.drugRow.replace(/{{id}}/,this.drugCount));
$(".drugsSelect").select2();
},
removeDrugRowHandler: function() {
drugCount = this.drugCount;
// also a problematic solution, because it only retains the inital drugCount.
// i.e I need a way to access the "live" count from within the event
$(document).on("click",".removeDrug",function(){
if (drugCount>0) {
$(this).parents("tr").remove();
this.drugCount--; // how should I approach this?
}
});
}
}
Try This
var Drugs = function() {
var me = this;
me.drugs = $("#drugs_table");
me.drugRow = $("#drug").html();
me.drugCount = 0;
me.init = function() {
this.addDrugRow();
this.removeDrugRowHandler();
};
me.addDrugRow = function() {
this.drugCount++;
this.drugs.append(this.drugRow.replace(/{{id}}/,this.drugCount));
$(".drugsSelect").select2();
};
me.removeDrugRowHandler= function() {
var drugCount = me.drugCount;
$(document).on("click",".removeDrug",function(){
if (drugCount>0) {
$(this).parents("tr").remove();
me.drugCount--;
}
});
}
}
As it turns out the easy solution is to use the object name instead of the contextual this.
So instead of this.drugCount I used Drugs.drugCount.
However, this solution only works if I am in the context of a single object. If I were to write a "class" (i.e var Drugs = function(){ ... }) this won't work.
I am having an "this" issue and would appreciate any help
this is my basic code
function xxx(val)
{
this.x = val;
this.change = function() {
var self = this;
$.ajax({
blah: '',
blah: '',
success: function(data) { self.x = 5; },
});
};
}
var newX = new x(1);
newX.change();
console.log(newX.x);
Hopefully that makes sense,
What I am trying to do is update the original object on the jquery ajax response. I do not have access to 'this' so i tried to pass it off to the 'self' variable but the object values does not seem to be updating even though the code is running as expected.
I am sure there is a simple answer, i just dont know it.
Any help is greatly appreciated.
So the way to resolve this is take a look at your function declarations. Each function declaration will give you a new this object. Store the one you are interested in the correct spot. If I'm correct, it looks like you actually want to access the scope of the original xxx function scope. So instead of storing this in the change function, store it above that in the original scope. Something like this:
function xxx(val)
{
var self = this;
this.x = val;
this.change = function() {
var that = this;
$.ajax({
blah: '',
blah: '',
success: function(data) { self.x = 5; },
});
};
}
var newX = new x(1);
newX.change();
console.log(newX.x);
The other issue here is that you are using AJAX to make that call so you either need a Deferred object or you could add a callback to that function that gets triggered at the right time like so:
function xxx(val)
{
var self = this;
this.x = val;
this.change = function(callback) {
var that = this;
$.ajax({
blah: '',
blah: '',
success: function(data) {
self.x = 5;
if (typeof callback === "function"){
callback.call(this);
}
}
});
};
}
var newX = new xxx(1);
newX.change(function(){
console.log(newX.x);
});
In knockout... you'll have to do something like that:
function Xxx(val)
{
var self = this;
this.x = ko.observable(val);
this.change = function() {
// "that" may be different to "self" in some
// cases...
var that = this;
$.ajax({
url: '...',
type: 'get',
success: function(data) {
self.x(5);
},
error: function(a) {
console.log('got an error');
}
});
};
}
var newX = new Xxx(1);
newX.change();
ko.computed(function () {
// This will get called everytime
// newX.x is changed
console.log(newX.x());
});
When you create a variable, that may change, you have to create it as an observable. The observable is in fact a function that you call. When called, it will update its inner value and it will also trigger any changes wherever the observable is "observed"
In no way you should try to do this.x = 5. It will override the actual observable object and thus it will never trigger every observer of a change.
edit
In case you're interested to understand how does computed works. A computed variable is a function that will listen to observables. When the computed is created, it will be called once to check which observables where called from within it. It's a way to "track" dependencies. In this example, you should see at least two console log. one with 1, and then with 5.
In my case, the computed variable is kind of anonymous since it isn't affected anywhere. Also in some case, you may need to observe one variable but use multiple observables. To prevent update on any other used observables. There are some ways to do that. You can either return after you "watched" the observables you needed.
Or you can create a sub function that will be triggered a little after the computed with setTimeout(..., 0);. There are a couple of ways to achieve some really nice tricks.