I'm having issues passing parameters/variables into the ajax() function.
The function below accepts two parameters. One is 'element', the success option of .ajax() uses, that works fine so no need to focus on that. The 'tx' is either a single value like 'menu' or a couple of values separated by a colon like this: "menu:categories:brands"
If there is only one value in 'tx' then only one AJAX request needs to be sent and that works fine. If there is more than one value in 'tx' the function split's it using ":" is the delimiter and then passes offset[0] as the value of 'tx' into the AJAX request and then store's the rest of the values in 'x'.
What I'm having issues with is running this function recursively once the AJAX request is complete for the first value. The var 'more' stores a bool value if there is more values left to process or not. However, when I write in an if argument using 'more == true' into the success or complete option of .ajax() it doesn't reflect the value stored in that variables, it always returns false.
Before some one answers, with "You should structure the function that calls this function to only pass a single value into that parameter," lets suppose that this is an impossible venture that is outside the realm of my control.
I have no idea why this happening. I know it's probably something very simple that I'm overlooking but I've been up for about 16 hours now and this issue has plagued me for at least half that time.
Any help here is appreciated. Here is the function:
function getContent(element, tx) {
e = element
modAmount = tx.split(':')
if (modAmount.length > 1) {
x = ''
tx = modAmount[0]
for (i=1;i<modAmount.length;i++) {
x = x + modAmount[i]
if (i != (modAmount.length)-1){
x = x+":"
}
}
more = true
}
else {
more = false
tx = modAmount[0]
}
$.ajax({
type: "POST",
url: "getModule.php",
data: "modName="+tx+"&DB=<?php echo DB ?>",
success: function(data){
if ($( element ).find('p').text() == "No Content"){
$( element ).find('p').remove();
$( element ).html("<div onclick='destroy(this)' id='destroy' class='ui-corner-all destroy' title='Remove Module'>"+data+"</div>")
}
else {
$( element ).append("<div onclick='destroy(this)' id='destroy' class='ui-corner-all destroy' title='Remove Module'>"+data+"</div>");
}
},
complete: function() {
if (more == true) {alert(x)} // always returns false
}
});
}
There's already one glaring error here: you're not declaring your local variables using the var keyword. This means that there's only one instance of any of those variables, in the global context.
I'm not sure if this is your problem, but it certainly seems like it could be. Consider the case where you call getContent "foo:bar". The first call sets more to true, but then the second recursive call sets more back to false. When the first XmlHttpRequest finishes, the completion callback will see the value of false rather than the true you were expecting, since those two calls are sharing the same variable for more.
You should read up on how JavaScript/ECMAScript scoping works, and gain a solid understanding of how closures work in the language. This will prevent a lot of head-scratching in the future. Google around for writings by Douglas Crockford; that's a good starting point.
tl;dr: define your local variables using the var keyword.
Because "more" isn't scoped within your callbacks.
I recommend using the context property you can send to the ajax call to set the "this" variable in success/complete callbacks:
http://api.jquery.com/jQuery.ajax/
Related
I have a utility function to insert HTML into a div and then call a javascript initializer function that attaches event handlers to various elements in the inserted HTML. The functions works great for 90% of cases but periodically fails in the jquery html() call below. When it fails, the HTML is inserted properly but the code on the next line is never reached.
function loadSection(id, url, initializer = null, param = null) {
const $div = $('#' + id);
return $.ajax({
method: 'GET',
url: url,
})
.done(function (html) {
$div.html(html);
if (initializer) { // This line is never reached.
initializer(param);
}
});
}
The same failure occurs if I use $div.empty().append(html) instead of $div.html(html) so the problem isn't in the html() function itself. In fact, if I step through the html() code, it executes this.empty().append(value) and never returns to the calling function.
The html that is inserted may contain to set variable values but does not call any javascript functions directly.
I've done a pretty exhaustive search of StackOverflow and the web but have come up empty. I've also traced through the jQuery code but couldn't identify the issue. Can anyone tell me why this is failing?
Answers to some of the questions:
It IS deterministic. The failure cases always fail and vice versa.
I know the code succeeded because code execution gets to $div.html(html). And the html returned in the GET is correct.
Example of how this is called:
function loadNewContracts() {
loadSection('prospector-newContracts', '../newContracts', initNewContracts);
}
The initializers are different for every section. The key points is that the initializer is NEVER called.
The correct initializer IS being passed into the function and does exist.
The only tags in the HTML set variable values. No js functions are called. Calls that succeed also set variable values. There are NO loops in the tags.
The $div variable DOES exist in all cases.
Barmar was close and gave me the idea to figure this one out. The js variables being set in the html are generated in a jinja2 template. A non-existent value was being passed to the template so the script ended up looking like ...
var currentValue = ;
... so the js fails while loading the html. Another one failed in an similar manner. These failed because of recently introduced bug in the server code.
Obvious in retrospect but I overlooked it because you can't break on the js in the . Thanks, Barmar!
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();
};
I am using Lawnchair to save data in js and retrieve it back for my mobile app.
I have this in my js file.
$(document).ready(function() {
//Lawchair set up
db = Lawnchair({name: 'db'},function(e){
console.log('storage open');
});
//store the 'username' key in storage
db.save({key:"username", value: "john"});
var name = ""
db.get("username", function(obj){
name = obj.value;
})
alert(name);
});
The problem is I always get "" in the name. I can never set any variable inside the callback function of "get" from Lawnchair object. Am I missing something?
Any help would be appreciated.
Thanks.
The database operation is asynchronous. Put your alert inside the callback to the ".get()" function.
As a general rule, any time you see a JavaScript API like this:
something(param, param, ... , function(result, result, ...) {
// ...
});
it's a good bet that the function may be an asynchronous mechanism, and that the callback function you supply will only be called later when an event actually transpires. In those cases you have to structure your own code such that activities you need to perform after the operation completes are done in code inside the callback.
(It's not always the case; some functional programming APIs for example take functions as arguments.)
I've got a problem. I've been trying to solve the problem of how to avoid using global variables. I decided to use an object with functions on it instead, and wrote a script accordingly. However, the script won't run as expected:
GetXML: function() {
$.ajax({
type: "GET",
url: "questions.xml",
dataType: "xml",
success: function(xml) {
this.xml=xml;
$(window).trigger("canUseXML");
var docLength = $(xml).find('Question').length + 1;
alert("this really is an '" + this.xml + "' and the actual doclength is: " + docLength)//This is the first alert I mention.
} //close success
});//close AJAX
alert("yes! this is still a: " +this.xml) //This is the second alert I mention.
},
If it all ran as expected, both alerts would result in an 'object XMLDocument' (a variable declared elsewhere in this line: this.xml = null;). The first runs as expected. However, the second, outside of that function, returns value "null". Why?
Thanks, Elliot Bonneville
P.S. I've been stuck on the seemingly simple question of passing variables between functions for a week.
In JavaScript "this" always refers to the “owner” of the function executed, or rather, to the object that a function is a method of. So your first and second "this" does not refer to the same thing.
Assuming that the "success" function executes before the second alert (which it probably doesn't). You could do something like this:
GetXML: function() {
var that=this;
.
.
and then use "that" instead of "this".
Scoping.
Once you travel outside of anonymous function(xml) this.xml no longer exists.
You have two problems:
Scoping: this inside the callback is not your object with GetXML as a member. Use something like me = this; in the constructor of your object and then use me instead of this to explicitely set the value.
You callback function waits for a success of your Ajax request, but the rest of the script is going on, which is the purpose of callbacks. So, the Ajax request is made, and then you do alert("yes! this is still a: " +this.xml), while the callback has not yet been executed.
This is probably happening because ajax takes time to process. Javascript doesn't wait for one request before executing the next command, so while AJAX is being called, its trying to figure out what happened to this.xml.