Javascript gurus, I need your help.
I need to compare two different arrays and check for different values. The values are coming from the same form multi select element. I tried getting a list of current values (cacheTermList) and checking for new value on change (newTermList). The idea is I want to pass an id to an ajax call if a new value was input, and return some data to the screen.
Code:
var cachedTermList = $('select#edit-categories').val();
if (cachedTermList == null) {
var cachedTermList = new Array();
}
$('select#edit-categories').chosen().change(function() {
var newTermList = $('select#edit-categories').val();
if (cachedTermList != null) {
for(var i = 0; i < newTermList.length; i++) {
alert(newTermList[i]);
if (!($.inArray(newTermList[i], cachedTermList))) {
$.ajax({
type: "GET",
url: "/classifieds/js/term/" + newTermList[i],
success: function(data){
//$('div#term-help-text').html(data);
cachedTermList.push(newTermList[i]);
alert(cachedTermList);
}
});
}
}
} else {
}
});
Bear with me, I don't tend to work with Javascript too often. I was trying to get a current list of values by setting cachedTermList on load, then when the select changes, set a newTermList to the new value of the field, then loop it, and check for a value in that list that is not in the cached list.
While I could see things happen, and dump both term lists and see different values, for the life of me I could not get it to push the found value to the cached list so that the next time the element changes, it doesn't keep sending the same value to the ajax call again and again. After the .push() executes, it just adds ',,,' without values. Where am I going wrong?
It is the typical closure - loop problem. All success callbacks reference the same i. At the time the callbacks are executed, the loop already finished and i will have the value newTermList.length + 1, so newTermList[i] will return undefined.
You have to capture the index or the value by introducing a new scope, which can be done by calling a function (JavaScript has no block scope).
Update: Another problem is that $.inArray does not return a boolean value, but the index of the element or -1. So you have to compare the return value against -1.
$('select#edit-categories').chosen().change(function() {
// ...
for(var i = 0; i < newTermList.length; i++) {
if ($.inArray(newTermList[i], cachedTermList) === -1) {
addTerm(newTermList[i], cachedTermList);
}
}
//...
});
function addTerm(term, target) {
$.ajax({
type: "GET",
url: "/classifieds/js/term/" + term,
success: function(data){
//$('div#term-help-text').html(data);
target.push(term);
alert(target);
}
});
}
Also keep in mind that all Ajax calls will basically be executed at the same time. The loop does not wait until one call finished. If you want to execute the calls one at a time, you can use jQuery's Deferred objects.
You are using ajax in a for loop and pushing the newTermList item in its success handler. Since ajax is async by default the loop is not going to wait for the ajax request to get completed. Try to put the pushing code outside the ajax call. But this will not work if the ajax call fails, may be you dont want to add the item into cache when the ajax call fails.
Try something like this
for(var i = 0; i < newTermList.length; i++) {
alert(newTermList[i]);
if (!($.inArray(newTermList[i], cachedTermList))) {
$.ajax({
type: "GET",
context: i,//Here I am setting the value of i into context which can be used in the success handler using this keyword
url: "/classifieds/js/term/" + newTermList[i],
success: function(data){
//$('div#term-help-text').html(data);
cachedTermList.push(newTermList[this]);
alert(cachedTermList);
}
});
}
}
Related
I have a problem when saving items on my application, items are listed from a table.
Everytime I save the updated items , the items tend to loop after saving.
This is my event of the button.
$('#saveEventBTN').click(function (event) {
//EventManager.SaveEventBYApprover();
EventManager.MultipleSave();
event.preventDefault();
});
This one is from the .Change event on the dropdown menu where I can enter the ids in the arrays that I will use for the looping saving transaction.
$("#tableApprovalState" + idEvent + "").change(function (event) {
idApproveStatus = $("#tableApprovalState" + idEvent + "").val();
event.preventDefault();
// Adding the event into an array for multiple selection.
if (idEventMulti.indexOf(idEvent) !== -1)
{
var del = idEventMulti.indexOf(idEvent);
idEventMulti.splice(del, 1);
idApproveStatusMulti.splice(del, 1);
idEventMulti[idEventMulti.length] = idEvent;
idApproveStatusMulti[idApproveStatusMulti.length] = idApproveStatus;
} else {
idEventMulti[idEventMulti.length] = idEvent;
idApproveStatusMulti[idApproveStatusMulti.length] = idApproveStatus;
}
});
This are the functions on my module :
This only loops the global array variable for the ids of the array.
EventManager.MultipleSave = function () {
for (let i = 0; i < idEventMulti.length; i++) {
EventManager.SaveEventBYApprover(idEventMulti[i], idApproveStatusMulti[i]);
}
}
This one is the Ajax call to save:
EventManager.SaveEventBYApprover = function (idevent, ideventstatus) {
$.ajax({
url: "/Modules/SaveEvent",
type: "POST",
data: {
IdEvent: idevent,
IdEventStatus: ideventstatus,
LastModifiedBy: user
},
dataType: "json",
success: function (data) {
$("#successMsg").html("").html(data.msg);
$("#successConfirmModal").modal("show");
searchResultsTable.clear().draw();
EventManager.BindDataTable();
}
});
}
This is how it looks like after the transactions successfully save.
I think you're correct in assuming the problem is called by multiple calls to EventManager.BindDataTable in the ajax success callbacks.
It's not a solution to call this method after the for loop in EventManager.MultipleSave, because this code will be called immediately after starting the ajax calls, which are asynchronous.
What you want is to set up some code that runs after all the asynchronous save ajax calls have completed. This is quite messy to achieve with the current callback model you're using.
Instead you can exploit that the $.ajax function returns a Promise. This means that if you return the result of $.ajax from EventManager.SaveEventBYApprover, you can collect these in an array in EventManager.MultipleSave, and then use something like:
$.when(myAjaxPromises).then(function() {
// code that runs after all saves goes here
}, function() {
// error handling goes here
});
I know there are lot of question regarding this but still I am unable to find a proper answer which makes my code run properly.
I have one function defined to call ajax which I cannot change due to security issue. This is how I call that function
var JsonIQDetails = JSON.stringify(input);//Some input
//pram 1:MethodUrl, 2:JsonObject, 3:ReturnType, 4:SuccessCallBackFunction
InvokeAjaxCall(Url, JsonIQDetails, "json", Success);
I have array of objects (more than 500). Since JSON is getting very long so I am unable to make ajax call. Again due to security issue I can't change config file too. So JSON length cannot be increased.
I am dividing the array into small chunks of 100 and calling the method
for (i = 0, j = mainObject.length; i < j; i += chunk) {
var newSubObject = mainObject.slice(i, i + chunk);
InvokeAjaxCall(Url, newSubObject, "json", Success);
function Success(data) {
if (!data) {
alert("Failed");
break;
}
}
}
Its moving without completing the for loop and executing the next code. So I want first it to complete the for loop (Probably asynchronous)
Thanks in Advance..!!!
Ajax is by default Asynchronous, so you pretty much need to invoke the next part of your ajax call in your success function. Here is a recursive loop that takes care of that.
var ajaxRecursive = function(i, j, c){
if(i < j){
var newSubObject = mainObject.slice(i, i + chunk);
InvokeAjaxCall(Url, newSubObject , "json", function(data){
//do stuff with data
ajaxRecursive(i+=chunk, j,chunk);
});
}
}
ajaxRecursive(0, mainObject.length, chunk);
Supposing that the other variables within ajaxRecursive are defined globally.
Update description:
You can get rid of your "success" function and just create it annonymously.
I have an array noti_array which has two elements, noti_array[0]='meghan&3', noti_arra[1]='tylor&5', the username and number concatenated by '&', in each iteration, I want to send the username to a ajax request
for(var i=0;i<noti_array.length;i++)
{
var mesg_array = new Array();
mesg_array=noti_array[i].split('&');
who_sent=mesg_array[0]; //first iteration, meghan
sent_num=mesg_array[1]; //3
//send the user name to a ajax request
$.ajax({
url:'getLastCon.php',
method:'post',
data:{who_sent:who_sent},
success:function(data){
alert(who_sent);
}
});
}
this code above would not alert meghan and tylor, instead, it alerts twice tylor, and if I use a 'break' after the ajax request, it would alert meghan once only, I think it's probably the loop enter the next iteration even the ajax request have not completed
It looks like who_sent could be a global variable. In that case, because of the asynchronous nature of your AJAX calls, the alert would trigger for the last assigned value of who_sent. Make who_sent a local variable or add another variable by adding var: var who_sent = mesg_array[0];.
Edit:
The above is wrong on how variables persist in a loop. I'm sorry for the inaccurate info. The following code will do what you want:
for( var i=0 ; i < noti_array.length ; i++ ) {
var mesg_array = noti_array[i].split('&');
who_sent = mesg_array[0]; //first iteration, meghan
sent_num = mesg_array[1]; //3
(function(who) {
//send the user name to a ajax request
$.ajax({
url: 'getLastCon.php',
method: 'post',
data: { who_sent:who },
success: function(data) {
alert(who);
}
});
})(who_sent);
}
I have some 'ajax' calls (really sjax I guess you could call it) and I'm trying to make them render on the page one at a time, but they aren't. They all render at the end. How can I space them out?
function getNames() {
var names = $('#thenames').val();
alert(names);
var splitnames = names.split(',');
for(var i = 0; i < splitnames.length; i++) {
var name = splitnames[i];
$.ajax({
type: 'GET',
url: '/acert/secure/people/namesservice/getnamesajax.jsp',
data: { usernames: name},
success: function(data) { $('#results').html($('#results').html() + data);},
async: false });
}
}
}
I can't risk them coming back in the wrong order so I need them to be synchronous. I put them into a for-loop, so the for-loop should give the browser a chance to render between calls, but I can't seem to make it.
Any ideas on what I'm doing wrong?
If I add an alertbox in the success function it works, but I don't want to have to babysit the operation, I just want to monitor its progress now and again.
async: false blocks the browser. It completely locks up everything, including repaints to the DOM.
I strongly strongly recommend you don't use async: false. It is extremely bad.
You might be able to use setTimeout in-between the calls, but they don't guarantee the browser will trigger a repaint.
If you set async: true you will not have this problem, but you will likely have to change your code to properly deal with asynchronous behaviour.
async false is so bad jQuery decided to remove it from the API.
Do not use async: false.
The code below will run all ajax requests as fast as possible, then append the content to #results in the correct order. DO NOT include async: false if you are using the code below.
var defArr = [];
for(var i = 0; i < splitnames.length; i++) {
defArr.push( $.ajax({...}) );
}
$.when.apply($,defArr).done(function(){
var $results = $("#results");
$results.empty();
for (var i = 0; i < arguments.length; i++) {
$results.append(arguments[i][0]);
}
});
assuming you know how many calls you make (or can include that as a parameter to the return result) you can simply fire the calls asynchronously, and make them elements in an array on your success callback. When the array gets to the expected size, just render them in sequence.
For one, you seem to have an extra curly brace there.
But more to the issue at hand, if you just want to monitor the progress would it work for you to use setTimeout?
-- update --
I think I get what you're trying to do. And if I'm not mistaken, you could refactor a little, and then use a closure and an object with the names as the keys. Something like this:
function getNames()
{
var names = $('#thenames').val();
var splitnames = names.split(',');
var myData = {};
for(var i = 0; i < splitnames.length; i++)
{
(function(name)
{ return function(){
$.ajax({
type: 'GET',
url: '/acert/secure/people/namesservice/getnamesajax.jsp',
data: { usernames: name},
success: function(data) { myData[name] = data; updateNames(); }
});
})( splitnames[i] )
}
}
What this basically does is that it sets up a bunch of ajax calls right away, that weird bit in the middle with the (function(){})() makes sure you don't end up fetching the last value that name gets set to when the loop finishes. Everything gets saved to myData, I figured once everyone is loaded you could check to see if all the names you have in splitnames are in myData with the updateNames function. Something like
var count = 0;
for ( var i = 0; i < splitnames.length; i++ )
{
count += myData[splitnames[i]] != null ? 1 : 0;
}
if (count == splitnames.length)
{
// write the names to the screen
}
Does that make sense?
But, to be honest, the best approach would probably be to change the getnamesajax.jsp so that it accepts all the names, and then gives you back the info you need in the order you need. If that was an option, that would be best since you would only need to make one ajax call.
I have a tightly coupled javascript, where in there are series of if-else checks and multiple ajax calls are made. The ajax calls are nested type. My problem is I am in a deep nested ajax callable function and I want to get out from there gracefully.
The snippet of the code is .
function showSubscriptionLightBox() {
$.get("/ajax/get_subscription_lightbox_content.php?feed_id=" + feedid, function(data) {
//Work on the data we receive... and check whether user is logged in.
if(userLoggedIn) {
//Make one more ajax call
$.get("/ajax/is_user_subscribed.php?feed_id=" + feedid, function(data) {
//Work on data again.... and check if user is subscribed.
if(userSubscribed) {
//Then there is popup which comes up, a part of same page and it has a button name "task".
document.getElementById('task').onclick = function() {
if(document.getElementById('email_mode').checked) {
$.ajax({
url : "ajax/is_user_email_verified.php?user_id="+userID,
success : function(data) {
if(!data)
return;
var response;
response = eval("response = " + data);
if(!response)
return;
if(response['email_status'] == 0) {
//Exit from here
}}}
......
other part of code..
I want to exit gracefully from javascript, when the response['email_status'] == 0
Please tell me, how to do this??
I tried the return statement, but it took me to the enclosing function and not outside the script.
Thanks,
Amit
For what it is worth, here is some code from one of my applications. It syncs records using JSONP and AJAX. It first gets an array of object ids from a remote server. It then fetches the record for the object id at the zero index from the host server. Then it sends the record it receives to the remote server. At that point, it continues the process by starting the process with an incremented index into the array of ids. It terminates when the index reaches the end of the array.
(function( $ ) {
$.getJSON( 'http://remote.com/admin/record_ids.js?callback=?', function( data ) {
var set_record = function( index ) {
if ( index < data.length ) {
$.get( 'record_get.json', { contact_id: data[ index ] }, function( data ) {
$.getJSON( 'http://remote.com/admin/record_save.js?callback=?', data, function() {
set_record( index + 1 );
});
}, 'json');
}
};
set_record( 0 );
});
})( jQuery );
As you can see, when you want to get out gracefully, you just don't call. I can't imagine why you can't just return to stop your code.
There's a funny trick you can always use in JavaScript to escape the call stack: setTimeout(). It's useful in many situations, not just this, it is often used to work around DOM event related bugs in browsers as well.
$.ajax(
{
url: 'lol.php',
success: function(data)
{
setTimeOut(function()
{
// Your code comes here
}, 0); // 0 will ensure that it gets executed immediately
}
});
I know that with Prototype you could do this with try/catch blocks. You could throw an object from within one of the inner functions and it will travel up the call stack for other functions to intercept.