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.
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);
While migrating to requirejs I have run into a problem that I cannot seem to find the answer to.
The problem is that my nested functions do not have access to the parameters passed to the requirejs callback.
for example:
define(['knockout', 'knockoutmapping', 'other'], function(ko, mapping, other) {
var Something = function() {
var self = this;
self.items = ko.observableArray([]);
self.doSomeStuff = function(data) {
// I would think I would still be able to access
// parameters ko, mapping and other
// trouble is, I cannot. I only have
// a valid 1st parameter, "ko".
var d = ko.toJSON(self.items()); // this works fine
self.items(mapping.fromJS(data));// this does not work (undefined)
// do not have access to "other" either.
};
};
return {
Something: Something,
};
});
Any help with this would be appreciated. I know I could put these into variables in the callback, but that doesn't seem like the correct approach to me.
Thank you in advance.
in define, callback is first argument and it have one parameteter require function, try:
define(function(require) {
var knockout = require('knockout');
var knockoutmapping = require('knockoutmapping');
var other = require('./other');
var Something = function() {
...
};
return {
Something: Something,
};
});
So the goal of what I'm doing it to store an array of objects inside an object literal for later reference. I am losing context(if that is the right terminology to use here) in a place that is confusing for me. Here is the code:
HuntObject = {
// Data.hunts gives collection
Data: {},
fetchCollec: function(){
var self = this;
var huntObj = new Parse.Query(huntObject);
huntObj.find({
success: function(results){
var hunts = [];
for(i in results){
hunts.push(i);
}
console.log(self);
//Here self references HuntObject
self.Data = hunts;
},
error: function(e){
console.log(e.message);
}
});
console.log(self);// Here self references HuntObject
console.log(self.Data); // empty
So in both my console.log statements the correct context that I want is referenced and in the last log call to self I can even see in the console that the Data object now has an array of objects in it. But than as soon as I try to reference that array I get an empty Object. I tried assigning the array in different ways like self.Data.array = hunts. I also tried to set up Data as a method like this.
Data: (function(){
return {
array: []
}
}());
I think maybe my understanding of how context changes in different situations is pretty weak so It would be nice to solve my original goal but more important I would like to understand context better and why my implementation is failing in this instance?
This isn't a scope or context issue, you're handling that with your self variable.
My guess is that Parse.Query is asynchronous. And so you'd have to do your console.log within the success function; doing it in the code immediately after the call to Parse.Query calls it too soon (before the query completes).
So:
huntObj.find({
success: function (results) {
var hunts = [];
for (i in results) {
hunts.push(i);
}
console.log(self);
//Here self references HuntObject
self.Data = hunts;
// *********** Move these here
console.log(self); // Here self references HuntObje
console.log(self.Data); // Not empty anymore
},
error: function (e) {
console.log(e.message);
}
});
// Any code here runs after you've *started* the query, but
// before it *completes*
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.
I'm trying to organize my ExtJS javascript a little better. I've an ExtJS object like this:
Ext.define('QBase.controller.ControlModelConfigurationController', {
extend: 'Ext.app.Controller',
views: [
'ControlModelConfiguration'
],
init: function() {
console.log('Initialized ControlModelConfigurationController');
this.control({
'#testBtn': {
click: this.loadModel
}
});
},
loadModel: function() {
console.log('Load Model....');
var conn = new Ext.data.Connection;
conn.request({
url: 'partsV10.xml',
callback: function(options, success, response)
{
if (success)
{
alert("AHHH");
var dq = Ext.DomQuery;
var xml = response.responseXML;
var nodes = dq.select('part', xml,parent);
Ext.Array.forEach(nodes,handleNode);
}
}
});
},
handleNode: function(items) {
console.log(item.name);
}
});
The posted code above is not working. Ext.Array.forEach(nodes,handleNode) causes trouble. Instead of using an anonymous function like :
...
Ext.Array.forEach(nodes,function(item) {
console.log(item)});
}
...
I'd like to extract the anonymous function as a named external one. Unfortunately I'm unable to figure out the right syntax to establish a code structure as shown above.
Meanwhile, I figured out, that putting
function handleNode(item) {
{console.log(item)}
}
at the very end of the file works. Is it possible to make the handleNode method an object - "member" of the controller?
Thanks in advance
Chris
handleNode is a member of the containing object. When loadModel is called, this contains the right object, but at the time the callback is invoked, it will not point to the one we are interested in. You can save this to the local variable self, and use it instead.
loadModel: function() {
var self = this
console.log('Load Model....');
var conn = new Ext.data.Connection;
conn.request({
url: 'partsV10.xml',
callback: function(options, success, response)
{
if (success)
{
alert("AHHH");
var dq = Ext.DomQuery;
var xml = response.responseXML;
var nodes = dq.select('part', xml,parent);
Ext.Array.forEach(nodes, self.handleNode);
}
}
});
},
The solution posted by vhallac is not entirely correct. It assumes that handleNode doesn't reference the current object through this variable. Maybe just a typo, but additionally it's not really the ExtJS way...
Whenever ExtJS provides a callback parameter, there is nearly always a scope parameter to set the value of this within the callback function.
loadModel: function() {
console.log('Load Model....');
var conn = new Ext.data.Connection;
conn.request({
url: 'partsV10.xml',
callback: function(options, success, response) {
if (success) {
alert("AHHH");
var dq = Ext.DomQuery;
var xml = response.responseXML;
var nodes = dq.select('part', xml, parent);
Ext.Array.forEach(nodes, this.handleNode, this);
}
},
scope: this
});
},
handleNode: function(node) {
// From within here you might want to call some other method.
// Won't work if you leave out the scope parameter of forEach.
this.subroutine();
}
Just like forEach uses a scope parameter, the request method uses a scope config option. This is ExtJS convention for passing around the scope. You can alternatively create an extra local variable and reference the scope from there, but in the context of ExtJS this style will feel awkward, plus (I'm pretty sure) it's a lot more bug-prone.