I have the following code:
for (_field in _fields) {
$.post(
'/api/fields',
{ id: _field.id },
function(data, _field) {
alert(data);
} (data, _fields[_field)
);
}
I have to pass the _fields[_field] element to the function that returns the data from the jQuery because loses the reference to the right object during the loop. The problem is that in defining that the post function should have a _field parameter, you also have to specify a parameter for data, or data will be overwritten with _field.
Currently data returns as undefined because I have no data object defined inside the loop. I also tried passing in null, but that also just returns null. I'm looking for a way to pass the element without overwriting the data returned from the post function.
Is there any way to fix this, or is there perhaps an alternative jQuery method that can do what's needed?
You want a function factory function — a function that creates a function:
for (_fieldName in _fields) {
$.post('/api/fields',
{
// Unrelated, but I think this bit is wrong; shouldn't it be
// `id: _fields[_fieldName].id` ? You're trying to use `.id` on
// a string -- see below for a full update
id: _fieldName.id
},
makeHandler(_fields[_fieldName])
);
}
function makeHandler(field) {
return function(data) {
// Use `data` and `field` here
};
}
Note that in the object initializer we're passing into $.post, we're calling makeHandler to it runs and returns the function we'll then pass into $.post. That function is then called when the $.post completes, and has access to the data argument that $.post gives it as well as the field argument to makeHandler, because it's a closure over the context of the call to makeHandler, which includes the field argument. More: Closures are not complicated
Note that in the code above, I changed your variable _field to _fieldName to be more clear: The variable in for..in loops is a string, the name of a property. See also the comment, I think you were trying to use .id in the wrong place. Here's what I think you really wanted:
for (_fieldName in _fields) {
_field = _fields[_fieldName];
$.post('/api/fields',
{
id: _field.id
},
makeHandler(_field)
);
}
function makeHandler(field) {
return function(data) {
// Use `data` and `field` here
};
}
Also note that if _fields is an array, you shouldn't use for..in on it without safeguards. More: Myths and realities of for..in
Related
I'm trying to write a js function that triggers another, variable, function when complete. I figured I could do this by passing the second function's name as a string, but I can't get it to work. Here's my code:
logMeIn('likeThisPost');
function logMeIn(callBack) {
//First function content
$.post("/ajaxurl/",
{
login_id: login_id,
intent: 'login'
},
function(){
console.log('Logged in, refreshing header');
$.post("/secondajaxurl/",{},
function(data){
//success
if(typeof callBack!=='undefined') {
window[callBack]();
}
}
);
}
);
});
}
This should, according to my thinking, run likeThisPost after successfully completing both ajax calls in logMeIn, but instead I get this error:
window[callBack] is not a function
The function I'm calling on success definitley exists, and besides which, it doesn't look like it's even trying to call that function, but it's treating callBack as literal rather than a string.
I'm using jQuery and have everything wrapped in a $. Where am I going wrong?
With thanks to Jonas W and Rory McCrossan, the answer was to change the passed function to the function name, rather than a string, and then call the function directly rather than using window.
So logMeIn(likeThisPost) instead of logMeIn("likeThisPost") and callBack(); rather than window[callBack]();.
Thanks!
I've read the answers here and here that describe how to read the value of jQuery ajax settings. My question, however, is this:
if I have previously invoked the ajaxSetup function with settings, like $.ajaxSetup({cache: true}), and then I invoke $.ajaxSetup() (with no arguments), are there any side effects to this invocation?
Or more bluntly, does calling $.ajaxSetup() with no arguments do anything other than return a plain object that contains the settings for ajax?
The answers I've referenced above both call ajaxSetup in this way in order to read the value of a setting, and I have been unable to find any official answer on the jQuery docs that indicate what calling ajaxSetup with no parameters does, probably because its use is so heavily discouraged.
In my browser, I ran a simple test by loading jQuery, running $.ajaxSetup({async: false}); followed by $.ajaxSetup(); and found that the value of async was still unchanged (the default value is true, and so if calling $.ajaxSetup() resets the settings to default values, then the value of async should have been changed), but this probably isn't a strong enough evidence to conclude that side effects do not occur when the function is invoked in this way.
Does calling $.ajaxSetup() with no arguments do anything other than return a plain object that contains the settings for ajax?
No.
Example
// set a new custom url
$.ajaxSetup({
url: 'CHANGED'
});
// call it again with no params
$.ajaxSetup();
// the url is still the custom url
console.log('url: ' + $.ajaxSetup().url);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Why?
If we look at the code for ajaxSetup, we see that, when called with a single parameter, it returns the call:
ajaxExtend( jQuery.ajaxSettings, target );
where target is the single argument you passed into ajaxSetup().
Now, inside ajaxExtend, we can see that it tries to iterate over that same argument with a for in loop.
Relevant code:
function ajaxExtend( target, src ) {
for (var key in src) {
// here is where target, which in this case is jQuery.ajaxSettings, gets overriden
}
return target;
}
Since you don't pass anything, basically you are doing
function ajaxExtend( target, src ) {
for (var key in undefined) {
// this is never reached
}
return target;
}
which never enters the for in loop and returns the current value of jQuery.ajaxSettings.
http://jsfiddle.net/FZ6K6/24/
I have a button (Remove inputs) with enable and css bindings that are returned when an observable array contains more than 2 items.
<button data-bind="click: removeInput, enable: Integers().length >2, css { red: Integers().length >2 }">Remove Input</button>
I also have a function (loadIntegerSorter) that sets the observable array to contain 2 items.
self.loadIntegerSorter = function () {
self.Integers([new integer(0, 0, 0), new integer(0, 0, 0)]);
};
I also have a save function that submits via ajax. Within the success callback, loadIntegerSorter is called.
success: function (result) {
if (result.Status == "success") {
isvm.loadSortedIntegers();
}
}
However, this seems to break the enable binding. The CSS binding behaves as expected with the array items = 2. But the Enable binding does not. I can run loadIntegerSorter outside of the Ajax function successfully so I suppose this is a synchronization problem but I don't know what the solution is.
The fiddle I've linked to doesn't fully demonstrate the problem because it depends on making a genuine Ajax request. But I hope it shows enough to understand.
Elaboration:
This results in the expected behaviour from the enable binding:
self.save = function () {
self.isloading();
};
But this doesn't:
self.save = function () {
$.ajax("/Home/Index", {
data: ko.toJSON(self.integerSorter),
cache: false,
type: "post",
contentType: "application/json",
context: self,
success: function (result) {
this.isloading();
}
});
};
And nor does this:
self.save = function () {
self.isloading();
$.ajax("/Home/Index", {
data: ko.toJSON(self.integerSorter),
cache: false,
type: "post",
contentType: "application/json",
context: self,
success: function (result) {
}
});
};
Whatever the cause of the problem, it seems to be related to the ajax call.
1)
Inside of your self.save function you're calling
self.isLoading(true);
Which yields
TypeError: 'undefined' is not a function (evaluating
'self.isLoading(true)')
telling you that self.isLoading is not declared anywhere in your code. This will break code execution even before the ajax request is sent.
2)
Same as 1) but this time for self.msgbox.status(). Undeclared: will break your code.
3)
The function self.loadIntegerSorter appears as self.loadSortedIntegers in the success function. Also, the self.save function appears declared two times. The second one will ovverride the first, but I guess the first one is there just in the fiddle.
4)
Inside of the success function, result.Status doesn't have any sense. You must understand that result is just a string of plain text, accessing the Status property of a string will result in an error. Perhaps you expect the response to be a JSON object with a Status property? If that is the case, you have to deserialize the string either by yourself (JSON.parse(response)) or by telling jQuery to do that for you (replace $.ajax with $.getJSON).
However, it may also be that you're not receiving any JSON back and you just wanted to access the response status, assuming you could do it that way. You can't. Being inside of a success function, you already know that your request has been successfully sent and a response received. No need to check it again.
5)
You're calling the loadSortedIntegers() method on the variable isvm. That's a totally wrong approach, even if it should work now it may cause huge troubles in the future. isvm is a global variable you use to contain an instance of your viewModel. The success function is contained in the viewModel itself, you should access it's own methods with this or self. A class should not access an instance of itself with a global variable. Question: how can I make this and/or self available in the success function? this can be reached by setting the context property to your $.ajax object. Exactly as you write success: function(){} you should write, just before that, context: this or, in your case, context: self.
Do that, and then just change the success function contents with this.loadSortedIntegers().
I've took the liberty to make some edits to your fiddle. Take your time to examine the difference here and to run it here.
Try to use valueHasMutated to push update for observable directly:
self.loadIntegerSorter = function () {
self.Integers([new integer(0, 0, 0), new integer(0, 0, 0)]);
self.Integers.valueHasMutated();
};
the following Ajax works
function Retrieve(el) {
var table = this;
this.el = el;
this.fetchInfo = function () {
$.ajax('data.html', {
context: table,
<!-- QUESTION HERE -->
data: {
location: table.data('location')
},
success: function (response) {
this.el.find('.info').html(response).fadeIn();
},
}
}
but I am wondering why I cannot replace table.data with this.data on the denoted line. Since I set the context to table variable, this now should be set to whatever table is referring to right? This holds true in the context of other members of the Ajax object (including success), but not for the members of data. Why is this the case?
data('name') extracts value from a tag with attribute data-name
The context variable you've given only applies within the success callback, and doesn't change anything for any of the other parameters passed to $.ajax.
The answer therefore depends on how you actually call fetchInfo. The data: variables will be resolved in whatever context fetchInfo has. Given you're experiencing problems, this suggests that you are not calling the function with your Retrieve object as its context.
EDIT this line is your problem:
this.el.on('click', 'button', this.fetchInfo);
just because you've referred to this.fetchInfo, doesn't make this the context when it's subsequently invoked. Try this instead:
this.el.on('click', 'button', $.proxy(this.fetchInfo, this));
Looking at the documentation of $.ajax(), I found that the context gets set to callbackContext variable. This gets with use success, error, beforeSend, complete, but not with data option.
AJAX options gets assigned to a variable named s:
s = jQuery.ajaxSetup({}, options),
The data option gets converted to string, but callBackContext is not used.
// Convert data if not already a string
if (s.data && s.processData && typeof s.data !== "string") {
s.data = jQuery.param(s.data, s.traditional);
}
So it is necessary to set specify the element for data using a variable other than this.
var s = store.get('billablexxxx');
if (!s) {
if (false) {
s = {
"something" : 'Value', ....
}
}
}else {
s = new Object;
$.getJSON('Translation/GetIndexLabels', function(data) {
$.each(data, function(key, val) {
s[key] = val;
// temp[key] = val;
})}) ;
}
}
//here i use the variable
But where i use the variable, the dynamic properties dissapaered, it's not undefined but it's an empty object.
However, when it has been set in the $.getJSON() function, all values are added.
What did i do wrong?
Edit:
Fixed the problem by putting it into a function. When i posted the question, i was already close to the answer that i have looked for the last "hour" (weird javascript console results brought confusion).
Thanks for the advice!
The call to $.getJSON() is asynchronous, so the execution will reach the part where you "use the variable" before the asynchronous call completes.
The usual way to deal with this is to have the use of the variable inside a callback function that is referenced in the getJSON() call.
Here's the reference: http://api.jquery.com/jQuery.getJSON/
The getJSON method is asynchronous, it calls your function(data) once it has completed.
At the point which you are using s, the getJSON method has not yet returned so the properties of s have not been initialised.