JQuery.ajax() makes a copy of the settings object? - javascript

JQuery.ajax() accepts a settings argument. The functions for success or error (for example) run in the context of this object.
var arr = [];
var a = {
url : '/',
arbiteraryProperty: 'yeah!',
complete:function () {
console.dir(arr[0]);
}
};
arr.push( a );
$.ajax( a );
runs the command and then prints the attributes of the first element of arr (which is a) as follows:
arbiteraryProperty : "yeah!"
url : "/"
complete : function()
Now the problem is that the this keyword inside the above complete function is not actually referring to the settings object. It is interesting because it seems JQuery is making a copy of the settings object.
var arr = [];
var a = {
url : '/',
arbiteraryProperty: 'yeah!',
complete:function () {
console.log(this.arbiteraryProperty );
//this prints 'yeah!'
this.arbiteraryProperty = 'nope!';
console.log( this.arbiteraryProperty );
//this prints 'nope!' so the value of the attribute in this is changed
console.log( a.arbiteraryProperty );
//this prints 'yeah!' ie. the value originally existed in the settings object
}
};
arr.push( a );
$.ajax( a );
The question is: does JQuery really create a duplicate copy of the setting object? And if yes, how can I force it to use my object?
I have an application where I need to save these objects in a queue and expect them to be updated when they run. I guess one alternative is to use the context settings for the $.ajax(). However this behavior of this function (making a copy of the settings object) wasn't documented. Or I missed it?

Yes, jQuery creates a new options object when you call jQuery.ajax(). The result is a combination of the settings object you passed and the global jQuery.ajaxSettings object, so that you have the correct default values and any settings you've set globally, even when you don't explicitly set them in the object passed.
This can be seen in the source code for jQuery 1.9 on line 7745:
// Create the final options object
s = jQuery.ajaxSetup( {}, options ),
Generally you use the context property to specify a different value for this inside the callback functions, so:
options = {
url: '/',
...,
context: a
}
However, the circular reference in your case (a referring to itself in one of its properties) may cause issues if the merge does a deep copy.

Related

Javascript parameter dynamically change using Play

I have a javascript request like this to pass data to play framework and based on the different propertyKey, the event should have different parameter.
I do something like this.
The js is used by multiple windows. So this._propertyKey will change based on different windows.
_postEvent: function(customData) {
var propertyKey = this._propertyKey;
var Event = {
propertyKey : customData
},
url = play.url({
alias: 'controllers.eventController.trackEvent',
args: Event,
withCsrf: true
});
return $.ajax(url, {
type: 'POST'
});
},
The problem when I trigger this code. According to the request it sends, it is always Event.propertyKey instead of the customized propertyKey I pass in. For example, if for this window, propertyKey = 'region'. I want 'region' to pass in as the parameter. But no matter what propertyKey is, the post request always sends Event.propertyKey = XXX instead of Event.region = XXX.
Is there a way to pass in the propertyKey here Is use to make it change dynamically based on different pages?
When a function is called as a method of an object, its this is set to the object the method is called on. MDN Reference
To fix that part of the code, use window instead of this.
In order to set a property name to be the value of another variable, you have to create the object first, and then set the key/value pair using []. SO Reference
var Event = {};
Event[propertyKey] = customData;
var url = play.url({
alias: 'controllers.eventController.trackEvent',
args: Event,
withCsrf: true
});

Why in Javascript the this context changes based on way of calling?

While this issue occurred to me specifically with KnockoutJS, my question is more like a general javascript question.
It is good to understand however that ko.observable() and ko.observableArray() return a method so when assigning a value to them, you need to call the target as method instead of simply assigning a value to them. The code that I'm working with should also support plain objects and arrays, which I why I need to resolve to a method to call to assign a value to the target.
Think of these 2 examples:
Non-working one (this context changed in called method):
// Assigning value to the target object
var target;
// target can be of any of thr following types
target = ko.observableArray(); // knockout observable array (function)
// target = ko.observable(); // knockout observable (function)
// target = {}; // generic object
// target = []; // generic array
//#region resolve method to call
var method;
if (isObservable(target)) {
// if it is a knockout observable array, we need to call the target's push method
// if it is a konckout observable, we need to call the target as a method
method = target.push || target;
} else {
// if target is a generic array, we need to use the array's push prototype
// if target is a generic object, we need to wrap a function to assign the value
method = target.push || function(item){ target = item; };
}
//#endregion
// call resolved method
method(entity);
Working one (this context is fine):
if (isObservable(target)) {
if (target.push) {
target.push(entity);
} else {
target(entity);
};
} else {
if (target.push) {
target.push(entity);
} else {
target = entity;
};
}
Now, to the actual question:
In the first approach, later in the execution chain when using a knockout observable knockout refers to this context within itself, trying to access the observable itself (namely this.t() in case someone is wondering). In this particular case due to the way of callin, this has changed to window object instead of pointing to the original observable.
In the latter case, knockout's this context is just normal.
Can any of you javascript gurus tell me how on earth my way of calling can change the 'this' context of the function being called?
Ok, I know someone wants a fiddle so here goes :)
Method 1 (Uncaught TypeError: Object [object global] has no method 'peek')
Method 2 (Works fine)
P.S. I'm not trying to fix the code, I'm trying to understand why my code changes the this context.
UPDATE:
Thanks for the quick answers! I must say I hate it when I don't know why (and especially how) something is happening. From your answers I fiddled up this quick fiddle to repro the situation and I think I got it now :)
// So having an object like Foo
function Foo() {
this.dirThis = function () {
console.dir(this);
};
};
// Instantiating a new Foo
var foo = new Foo();
// Foo.dirThis() has it's original context
foo.dirThis(); // First log in console (Foo)
// The calling code is in Window context
console.dir(this); // Second log in console (Window)
// Passing a reference to the target function from another context
// changes the target function's context
var anotherFoo = foo.dirThis;
// So, when being called through anotherFoo,
// Window object gets logged
// instead of Foo's original context
anotherFoo(); // 3rd log
// So, to elaborate, if I create a type AnotherFoo
function AnotherFoo(dirThis){
this.dirThis = dirThis;
}
// And and instantiate it
var newFoo = new AnotherFoo(foo.dirThis);
newFoo.dirThis(); // Should dir AnotherFoo (4th in log)
If you're after a way to choose the 'this' that will get used at the time of call,
you should use bind, that's exactly done for that.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
So if SomeObject has a push method, then storing it like this won't work :
var thePushMethod = someObject.push;
since you loose the context of the function when writing this.
Now if you do :
var thePushMethod = someObject.push.bind(someObject);
the context is now stored inside thePushMethod, that you just call with
thePushMethod();
Notice that you can bind also the arguments, so for instance you might write :
var pushOneLater = someObject.push.bind(someObject, 1 );
// then, later :
pushOneLater(); // will push one into someObject
Consider this example,
function Person () {
this.fname = "Welcome";
this.myFunc = function() {
return this.fname;
}
};
var a = new Person();
console.log(a.myFunc());
var b = a.myFunc;
console.log(b());
Output
Welcome
undefined
When you make a call to a.myFunc(), the current object (this) is set as a. So, the first example works fine.
But in the second case, var b = a.myFunc; you are getting only the reference to the function and when you are calling it, you are not invoking on any specific object, so the window object is assigned. Thats why it prints undefined.
To fix this problem, you can explicitly pass the this argument with call function, like this
console.log(b.call(a));
So, for your case, you might have to do this
method.call(target, entity);
Check the fixed fiddle

How to declare a JavaScript list variable to populate later?

This is my first stab at trying to create a JavaScript object. I am attempting to extract properties from a very complex JSON response from a USGS REST service and save them as an object for later use.
What I am attempting to do is create an empty array as the first property of the object to later populate with instances of a custom object later, the last line of actual code. After digging around both the W3C, MDN and this site, I have yet to really come up with a solution.
Please feel free to not only offer solutions to the array issue, but also offer constructive criticism on the rest of the code. After all, I am trying to learn with this project.
// create site object
function Site(siteCode) {
this.timeSeriesList = [];
this.siteCode = siteCode
this.downloadData = downloadData;
// create timeSeries object
function TimeSeries(siteCode, variableCode) {
this.siteCode = siteCode;
this.variableCode = variableCode;
this.observations = [];
}
// create observation object
function TimeSeriesObservation(stage, timeDate) {
this.stage = stage;
this.timeDate = timeDate;
}
// include the capability to download data automatically
function downloadData() {
// construct the url to get data
// TODO: include the capability to change the date range, currently one week (P1W)
var url = "http://waterservices.usgs.gov/nwis/iv/?format=json&sites=" + this.siteCode + "&period=P1W&parameterCd=00060,00065"
// use jquery getJSON to download the data
$.getJSON(url, function (data) {
// timeSeries is a two item list, one for cfs and the other for feet
// iterate these and create an object for each
$(data.value.timeSeries).each(function () {
// create a timeSeries object
var thisTimeSeries = new TimeSeries(
this.siteCode,
// get the variable code, 65 for ft and 60 for cfs
this.variable.variableCode[0].value
);
// for every observation of the type at this site
$(this.values[0].value).each(function () {
// add the observation to the list
thisTimeSeries.observations.push(new TimeSeriesObservation(
// observation stage or level
this.value,
// observation time
this.dateTime
));
});
// add the timeSeries instance to the object list
this.timeSeriesList.push(thisTimeSeries);
});
});
}
}
If you would like to view the JSON I am using for testing, you can find it here: http://waterservices.usgs.gov/nwis/iv/?format=json&sites=03479000&period=P1W&parameterCd=00060,00065
Thank you in advance for your time and mentoring!
Your primary problem is that this inside the AJAX callback isn't your object, it's variably either the jqXHR object created by jQuery or the current element that you're iterating over with .each.
The simplest solution is to create another reference to this in the higher level lexical scope that may be accessed from inside the inner functions:
var self = this;
$.getJSON(..., function() {
// use self inside to refer to your object
});

Strange JavaScript behaviour, variable changing value before converter function is called

I'm passing down some JSON data from Smarty. I'm applying this to a JavaScript variable, options.
If you've seen my previous question about date formats you'll know I need to do a bit of work on the data coming in, so I've got a function called chart_convert_dates() that's called, passing in the options (well, options.data), and upon it's return setting it back again.
If you read through my code you'll notice I'm debugging the options variable, and it changes from the original before the function is called!?
If I comment out the function call, the variable is untouched, as it should be at that point.
This happens with Chrome, FF... what's going on?
{literal}
<script type="text/javascript">
$(document).ready(function() {
// set JS var, this data is coming in from smarty
var options = {/literal}{$options}{literal};
// these should both be exactly the same
debug.debug({/literal}{$options}{literal});
debug.debug(options);
// but the above outputs aren't the same! options has been modified
// by the function below... that hasn't even fired yet!? We can prove
// this by commenting out the following function call
options.data = chart_convert_dates(options.data);
// ... do something else
});
</script>
{/literal}
This is, of course, impossible.
You'll probably find that the debug.debug() function is saving a reference to the object it is provided with, rather than converting it to a string immediately. When you then view the contents of it's argument at a later time, the output will reflect the current state of the object, rather than the state it was in.
This is best explained with the following example:
var debug = {
report: function () {
// console.log(this._value);
},
debug: function (arg) {
this._value = arg; // save a reference
}
}
var options = {
foo: 1
};
debug.debug(options);
options.foo = 2;
debug.report(); // will show 2 (http://jsfiddle.net/zQFPm/)
http://jsfiddle.net/zQFPm/

Unable to use "class" methods for callbacks in JavaScript

I'm having a really rough time wrapping my head around prototypes in JavaScript.
Previously I had trouble calling something like this:
o = new MyClass();
setTimeout(o.method, 500);
and I was told I could fix it by using:
setTimeout(function() { o.method(); }, 500);
And this works. I'm now having a different problem, and I thought I could solve it the same way, by just dropping in an anonymous function. My new problem is this:
MyClass.prototype.open = function() {
$.ajax({
/*...*/
success: this.some_callback,
});
}
MyClass.prototype.some_callback(data) {
console.log("received data! " + data);
this.open();
}
I'm finding that within the body of MyClass.prototype.some_callback the this keyword doesn't refer to the instance of MyClass which the method was called on, but rather what appears to be the jQuery ajax request (it's an object that contains an xhr object and all the parameters of my ajax call, among other things).
I have tried doing this:
$.ajax({
/* ... */
success: function() { this.some_callback(); },
});
but I get the error:
Uncaught TypeError: Object #<an Object> has no method 'handle_response'
I'm not sure how to properly do this. I'm new to JavaScript and the concept of prototypes-that-sometimes-sort-of-behave-like-classes-but-usually-don't is really confusing me.
So what is the right way to do this? Am I trying to force JavaScript into a paradigm which it doesn't belong?
Am I trying to force JavaScript into a paradigm which it doesn't belong?
When you're talking about Classes yes.
So what is the right way to do this?
First off, you should learn how what kind of values the this keyword can contain.
Simple function call
myFunc(); - this will refer to the global object (aka window) [1]
Function call as a property of an object (aka method)
obj.method(); - this will refer to obj
Function call along wit the new operator
new MyFunc(); - this will refer to the new instance being created
Now let's see how it applies to your case:
MyClass.prototype.open = function() {
$.ajax({ // <-- an object literal starts here
//...
success: this.some_callback, // <- this will refer to that object
}); // <- object ends here
}
If you want to call some_callback method of the current instance you should save the reference to that instance (to a simple variable).
MyClass.prototype.open = function() {
var self = this; // <- save reference to the current instance of MyClass
$.ajax({
//...
success: function () {
self.some_callback(); // <- use the saved reference
} // to access instance.some_callback
});
}
[1] please note that in the new version (ES 5 Str.) Case 1 will cause this to be the value undefined
[2] There is yet another case where you use call or apply to invoke a function with a given this
Building on #gblazex's response, I use the following variation for methods that serve as both the origin and target of callbacks:
className.prototype.methodName = function(_callback, ...) {
var self = (this.hasOwnProperty('instance_name'))?this.instance_name:this;
if (_callback === true) {
// code to be executed on callback
} else {
// code to set up callback
}
};
on the initial call, "this" refers to the object instance. On the callback, "this" refers to your root document, requiring you to refer to the instance property (instance_name) of the root document.

Categories

Resources