Anonymous function causing ids to not be set after AJAX success - javascript

I'm making an AJAX call from within an Anonymous function. When the success callback takes place I'm then able to set my custom JS objects ids and other important info that has arrived from the data server.
After I set the a.target.id to the returned data.id everything looks fine.
On the step where I'm calling a function to do some work with the newly updated custom JS object, that I just updated with the response data from the server. I am passing the parent of that object to the method to do some work on all the children of that object.
However, as you can see on the last example in the snap shot photos the a.target.parent.children[0].id is not in the collection and/or it's ids where never set.
I must be losing a reference to that object during the AJAX call when using an Anonymous function.
Here is all of the code. How am I losing the reference? Or how can I maintain a reference to that parent's children when the AJAX call returns?
I've never had this happen before, makes me believe that it has something to do with the Anonymous function.
var horizontalPositioner = function (horizontals) {
var hpa = ['?horPositions='];
for (var i = 0; i < horizontals.children.length; i += 1) {
hpa.push(horizontals.children[i].id + ':' + horizontals.children[i].position + ',');
};
hpa[i] = hpa[i].replace(',', '');
dataBase.update(dbPart('horizontal' + hpa.join('')));
};
this.subscribe.call(this, e.horizontaladded, function (a, fn) {
//
if (!a.extra.owner.id) {
return;
};
(function (a) {
dataBase.insert(
dbPart(
['horizontal?a=', a.extra.owner.instanceName, '&id=', a.extra.owner.id].join(''),
a.target
),
dbCB(
function (data, status) {
if (status === 'error') { return; };
a.target.id = data.id,
a.target.HTML().addClass('alum_' + data.id),
a.target.finish.id = data.finishID,
a.target.size.id = data.sizeID,
a.target.siteLine.id = data.sitelineID;
//
//reposition horizontals
// setTimeout(function () { horizontalPositioner(a.target.parent); }, 1000);
debugger
horizontalPositioner(a.target.parent);
if (fn) { processCallbacks(data, status, fn); };
//very last
events.publishDatabaseCallbacks(e.horizontaladded,
eArgs(a.bay, { data: data, instanceName: 'horizontal', ownerid: a.extra.owner.id, id: data.id }));
},
function (xhr, status, errorThrown) { console.log('ERROR adding horizontal'); })
);
}(a));
}, true);

I've added an anonymous function with a nested setTimeout to give everything time to build. I've got many events taking place at once, so for now this works.
var horizontalPositioner = function (horizontals) {
(function (hors) {
setTimeout(function () {
var hpa = ['?horPositions='];
for (var i = 0; i < hors.children.length; i += 1) {
hpa.push(hors.children[i].id + ':' + (hors.children[i].position ? hors.children[i].position : 1) + ',');
};
hpa[i] = hpa[i].replace(',', '');
dataBase.update(dbPart('horizontal' + hpa.join('')));
}, 1000);
}(horizontals));
};

Related

Wait until multiple asynchronous functions complete before executing

I have a Javascript for loop which runs through an array of database records (that have been extracted already).
I want to know when all the subsequent asynchronous actions have completed but I can't seem to do it.
For each record, the code runs a number of functions which return promises and then resolve (which then triggers another function to get more information, etc). This all works ok, but I can't figure out how to gather up each "FOR" iteration and detect when all records have been processed. Basically, I want to use a "throbber" and have the throbber remain until all processing has been completed.
Code is below (I've removed some extraneous info)...
for (var i = 0; i < systemArray.length; i++) {
// ***** FOR EACH SYSTEM ***** //
var currRecord = systemArray[i];
// SECTION REMOVED //
// GET SYSTEM LINES
var thisSystem = AVMI_filterArray("8.9", currRecord);
var thisSystemName = thisSystem[1].value;
var thisSystemRID = thisSystem[0].value;
// GET CHILDREN RIDS
AVMI_getChildren(systemLinesTable, thisSystemRID, systemLinesFID).done(function(ridList, sysRID)
{
var thisDiv = "div#" + sysRID;
// GET RECORD INFO FOR EACH RID
AVMI_getMultipleRecordInfoFromArray(ridList, systemLinesTable).done(function(systemLinesArray)
{
if (systemLinesArray != "" && systemLinesArray != null) {
systemLinesArray = systemLinesArray.sort(propComparator("10"));
x = AVMI_tableCombiner("System Lines", systemLinesArray, systemLinesCLIST, "skip3Right hbars xsmallText");
$(thisDiv).append(x);
} else {
$(thisDiv).append("<p>No System Lines...</p>");
}
}
);
}
);
} // ***** FOR EACH SYSTEM ***** //
AVMI_throbberClose(); // THIS, OF COURSE, EXECUTES ALMOST IMMEDIATELY
Here is function 1
///////////////////////////////////////////////////////////////
// Get related records using master
///////////////////////////////////////////////////////////////
function AVMI_getChildren(AVMI_db, AVMI_rid, AVMI_fid, AVMI_recText) {
var AVMI_query = "{" + AVMI_fid + ". EX. " + AVMI_rid + "}";
var AVMI_ridList = [];
var dfd2 = $.Deferred();
$.get(AVMI_db, {
act: "API_DoQuery",
query: AVMI_query,
clist: "3",
includeRids: "1"
}).then(function(xml1) {
$(xml1).find('record').each(function(){
var AVMI_record = $(this);
var AVMI_childRID = AVMI_record.attr("rid");
AVMI_ridList.push(AVMI_childRID);
});
AVMI_throbberUpdate("Found " + AVMI_ridList.length + " " + AVMI_recText + "...");
dfd2.resolve(AVMI_ridList, AVMI_rid);
});
return dfd2.promise();
};
And function 2
///////////////////////////////////////////////////////////////
// Get record info for each array member
///////////////////////////////////////////////////////////////
function AVMI_getMultipleRecordInfoFromArray(ridList, AVMI_db, AVMI_recType) {
var promises = [];
var bigArray = [];
$.each(ridList, function (index,value) {
var def = new $.Deferred();
var thisArray = [];
$.get(AVMI_db, { //******* ITERATIVE AJAX CALL *******
act: 'API_GetRecordInfo',
rid: value
}).then(function(xml2) {
AVMI_throbberUpdate("Got " + AVMI_recType + " " + value + "...");
$(xml2).find('field').each(function() {
var $field = {};
$field.fid = $(this).find('fid').text();
$field.name = $(this).find('name').text();
$field.value = $(this).find('value').text();
thisArray.push($field);
});
thisArray = thisArray.sort(AVMI_ArrayComparator);
bigArray.push(thisArray);
def.resolve(bigArray);
});
promises.push(def);
});
return $.when.apply(undefined, promises).promise();
};
Any ideas of how to structure this? I've tried all sorts of things with $.Deferred but I can't quite figure it out...
You do exactly the same thing you did in AVMI_getMultipleRecordInfoFromArray: Collect the promises in an array and use $.when (or Promise.all) to wait until they are resolved.
You can simply use .map in here which also takes care of the "function in a loop" problem:
var promises = systemArray.map(function(currRecord) {
// ...
return AVMI_getChildren(...).done(...);
});
$.when.apply(undefined, promises).done(function() {
AVMI_throbberClose();
});
You should have to disable the async property of ajax. By default it is set to true. It means that your doesn't wait for your ajax response. That why it is returning you undefined value and you have to set it to false. So your code will wait your request to complete.
So all you have to do is.
$.ajax({
url: '',
type: '',
async: false,
success: function(data){
}
});

Waiting for Recursive Function To Complete

I have a recursive Javascript function which gets the links from one Wikipedia page, follows them, and then gets all of those links (repeating a specified number of times).
It calls itself an unknown number of times to construct an object of a known depth. When it completes, I want to output the object. Currently the object immediately outputs, and is empty, meaning the function obviously isn't waiting for all the recursive calls to complete.
As you can see, I have attempted to use callbacks, but I assume incorrectly. What am I doing wrong, how should I be doing it? I'm going to presume there's a few other things wrong I haven't spotted too; I'm relatively new to Javascript.
$(document).ready(function ()
{
pageLinks[START_PAGE] = {};
//Get initial pages
links = getLinks(START_PAGE, 0, printLinks));
});
function printLinks()
{
console.log(links);
}
function getLinks(currentPage, level, callback)
{
visitedPages.push(currentPage)
var pageLinks = {}
var data = $.getJSON(URL_BEGIN + currentPage + URL_END, function(data)
{
var pages = data.query.pages;
for(var page in pages)
{
pageContentObj = pages[page].revisions[0];
for(var key in pageContentObj) if(pageContentObj[key].length > 100)
{
var pageContent = pageContentObj[key];
//Get links
hyperlinks = getFromBetween.get(pageContent,"[[","]]");
for(var link in hyperlinks)
{
link = hyperlinks[link].split("|")[0]; //Remove friendly name
link = link.replaceAll(" ", "%20");
//Add to pagelist object
prefix = link.split(":")[0];
if(prefix != "Category" && prefix != "File" && prefix != "wikipedia")
if(level < ITERATIONS && !visitedPages.includes(arguments, link))
{
console.log(level + ": " + link)
pageLinks[link] = getLinks(link, level+1, callback); //===Recursive call===
}
}
}
}
});
if(level == 0 && callback) callback();
return pageLinks;
}
Any help is appreciated, thanks in advance.
**EDIT: ** Link: https://github.com/JakeStanger/Wikipedia-Mapper/blob/master/init.js#L53
The recursive call needs to be like this:
var counter = 0;
//the big for loop
counter++;
getLinks(link, level + 1, function(res) {
for (var key in res) { //with an array it would be concat...
pageLinks[key] = res[key];
}
counter--;
if (counter == 0 && callback) callback(pageLinks); //callback if all callbacks called
});
Also remove this weird code:
if(level == 0 && callback) callback();
No you can do:
getLinks(START_PAGE, 0, console.log);

Javascript array shows in console, but i cant access any properties in loops

I really try my damndest not to ask, but i have to at this point before I tear my hair out.
By the time the js interpreter gets to this particular method, I can print it to the console no problem, it is an array of "event" objects. From FireBug I can see it, but when I try to set a loop to do anything with this array its as if it doesn't exist. I am absolutely baffled......
A few things:
I am a newbie, I have tried a for(var index in list) loop, to no avail, I have also tried a regular old for(var i = 0; i < listIn.length; i++), and I also tried to get the size of the local variable by setting var size = listIn.length.
As soon as I try to loop through it I get nothing, but I can access all the objects inside it from the FireBug console no problem. Please help, even just giving me a little hint on where I should be looking would be great.
As for the array itself, I have no problems with getting an array back from PHP in the form of: [{"Event_Id":"9", "Title":"none"}, etc etc ]
Here is my code from my main launcher JavaScript file. I will also post a sample of the JSON data that is returned. I fear that I may be overextending myself by creating a massive object in the first place called content, which is meant to hold properties such as DOM strings, settings, and common methods, but so far everything else is working.
The init() function is called when the body onload is called on the corresponding html page, and during the call to setAllEvents and setEventNavigation I am lost.
And just to add, I am trying to learn JavaScript fundamentals before I ever touch jQuery.
Thanks
var dom, S, M, currentArray, buttonArray, typesArray, topicsArray;
content = {
domElements: {},
settings: {
allContent: {},
urlList: {
allURL: "../PHP/getEventsListView.php",
typesURL: "../PHP/getTypes.php",
topicsURL: "../PHP/getTopics.php"
},
eventObjArray: [],
buttonObjArray: [],
eventTypesArray: [],
eventTopicsArray: []
},
methods: {
allCallBack: function (j) {
S.allContent = JSON.parse(j);
var list = S.allContent;
for (var index in list) {
var event = new Event(list[index]);
S.eventObjArray.push(event);
}
},
topicsCallBack: function(j) {
S.eventTopicsArray = j;
var list = JSON.parse(S.eventTopicsArray);
topicsArray = list;
M.populateTopicsDropDown(list);
},
typesCallBack: function(j) {
S.eventTypesArray = j;
var list = JSON.parse(S.eventTypesArray);
typesArray = list;
M.populateTypesDropDown(list);
},
ajax: function (url, callback) {
getAjax(url, callback);
},
testList: function (listIn) {
// test method
},
setAllEvents: function (listIn) {
// HERE IS THE PROBLEM WITH THIS ARRAY
console.log("shall we?");
for(var index in listIn) {
console.log(listIn[index]);
}
},
getAllEvents: function () {
return currentArray;
},
setAllButtons: function (listIn) {
buttonArray = listIn;
},
getAllButtons: function () {
return buttonArray;
},
setEventNavigation: function(current) {
// SAME ISSUE AS ABOVE
var l = current.length;
//console.log("length " + l);
var counter = 0;
var endIndex = l - 1;
if (current.length < 4) {
switch (l) {
case 2:
var first = current[0];
var second = current[1];
first.setNextEvent(second);
second.setPreviousEvent(first);
break;
case 3:
var first = current[0];
var second = current[1];
var third = current[2];
first.setNextEvent(second);
second.setPreviousEvent(first);
second.setNextEvent(third);
third.setPreviousEvent(second);
break;
default:
break;
}
} else {
// do something
}
},
populateTopicsDropDown: function(listTopics) {
//console.log("inside topics drop");
//console.log(listTopics);
var topicsDropDown = document.getElementById("eventTopicListBox");
for(var index in listTopics) {
var op = document.createElement("option");
op.setAttribute("id", "dd" + index);
op.innerHTML = listTopics[index].Main_Topic;
topicsDropDown.appendChild(op);
}
},
populateTypesDropDown: function(listTypes) {
//console.log("inside types drodown");
//console.log(listTypes);
var typesDropDown = document.getElementById("eventTypeListBox");
for(var index2 in listTypes) {
var op2 = document.createElement("option");
op2.setAttribute("id", "dd2" + index2);
op2.innerHTML = listTypes[index2].Main_Type;
typesDropDown.appendChild(op2);
}
}
},
init: function() {
dom = this.domElements;
S = this.settings;
M = this.methods;
currentArray = S.eventObjArray;
buttonArray = S.buttonObjArray;
topicsArray = S.eventTopicsArray;
typesArray = S.eventTypesArray;
M.ajax(S.urlList.allURL, M.allCallBack);
//var tempList = currentArray;
//console.log("temp array length: " + tempList.length);
M.setAllEvents(currentArray);
M.testList(currentArray);
M.setEventNavigation(currentArray);
//M.setEventNavigation();
M.ajax(S.urlList.topicsURL, M.topicsCallBack);
M.ajax(S.urlList.typesURL, M.typesCallBack);
}
};
The problem you have is that currentArray gets its value asynchronously, which means you are calling setAllEvents too soon. At that moment the allCallBack function has not yet been executed. That happens only after the current running code has completed (until call stack becomes emtpy), and the ajax request triggers the callback.
So you should call setAllEvents and any other code that depends on currentArray only when the Ajax call has completed.
NB: The reason that it works in the console is that by the time you request the value from the console, the ajax call has already returned the response.
Without having looked at the rest of your code, and any other problems that it might have, this solves the issue you have:
init: function() {
dom = this.domElements;
S = this.settings;
M = this.methods;
currentArray = S.eventObjArray;
buttonArray = S.buttonObjArray;
topicsArray = S.eventTopicsArray;
typesArray = S.eventTypesArray;
M.ajax(S.urlList.allURL, function (j) {
// Note that all the rest of the code is moved in this call back
// function, so that it only executes when the Ajax response is
// available:
M.allCallBack(j);
//var tempList = currentArray;
//console.log("temp array length: " + tempList.length);
M.setAllEvents(currentArray);
M.testList(currentArray);
M.setEventNavigation(currentArray);
//M.setEventNavigation();
// Note that you will need to take care with the following asynchronous
// calls as well: their effect is only available when the Ajax
// callback is triggered:
M.ajax(S.urlList.topicsURL, M.topicsCallBack); //
M.ajax(S.urlList.typesURL, M.typesCallBack);
});
}

javascript keeping a var within inner functions

I am making a website based dashboard. one of the functionalities is showing the locations of all customers. when i'm placing these on the map i can't seem to get the pop-up right.
function getCoordinates(locationList) {
for (var i = 0; i < locationList.length; i++) {
if (locationList[i].city != null) {
$http.get('https://api.tiles.mapbox.com/geocoding/v5/mapbox.places/' + locationList[i].city + '.json?access_token=' + access_token)
.success(
function (data) {
var marker = L.marker([data.features[0].center[1], data.features[0].center[0]]).addTo(mymap);
marker.bindPopup(locationList[i].customerName);
}
);
}
}
}
When I use this code the pop-up will only contain the last customer's name in every pop-up.does someone know how to make sure that the attributes of the correct user are used?
That's a closure problem, to fix it you have to move your $http call to a new function like this.
function httpCall(locationList,i){
$http.get('https://api.tiles.mapbox.com/geocoding/v5/mapbox.places/' + locationList[i].city + '.json?access_token=' + access_token)
.success(
function (data) {
var marker = L.marker([data.features[0].center[1], data.features[0].center[0]]).addTo(mymap);
marker.bindPopup(locationList[i].customerName);
}
);
}
After for loop i is always locationList.length - 1. Try to add IIFE with local i. For example you can solve the problem with replacing for loop with locationList.forEach
This is Infamous Loop Problem. Since you are just defining the function and not actually executing it when the for loop ends all the functions will have the same values for index i.
Solution: Is to assign the value to a variable and use this variable inside you success callback.
for (var i = 0; i < locationList.length; i++) {
if (locationList[i].city != null) {
var currLocation = locationList[i]; // assign the data to a variable
$http.get('https://api.tiles.mapbox.com/geocoding/v5/mapbox.places/' + locationList[i].city + '.json?access_token=' + access_token)
.success(
function (data) {
var marker = L.marker([data.features[0].center[1], data.features[0].center[0]]).addTo(mymap);
marker.bindPopup(currLocation.customerName); // use the variable instead of the indexed lookup
}
);
}
}
Let me know if this helps.
It's a scope problem. Your i is updated and later, when you will click on the popup, it will read the last value of i.
You should put your conditional in the for a function which take in parameter the i :
function getCoordinates(locationList) {
for (var i = 0; i < locationList.length; i++) {
conditionalGet(i);
}
function conditionalGet(i) {
if (locationList[i].city != null) {
$http.get('https://api.tiles.mapbox.com/geocoding/v5/mapbox.places/' + locationList[i].city + '.json?access_token=' + access_token)
.success(function (data) {
var marker = L.marker([data.features[0].center[1], data.features[0].center[0]]).addTo(mymap);
marker.bindPopup(locationList[i].customerName);
});
}
}
}

Access function in module with similar name of function in another module

Code:
for (var i in RESTCalls_GET) {
describe('Retrieve all Product Component names and their IDs', function() {
var restCalls;
beforeEach(function() {
RESTCalls_GET.setClient(mockClient);
restCalls = new Rest_calls(mockClient);
});
describe(i + '()', function() {
it('should return data if response code is 200', function(done) {
mockClient.mockURLForSucceed(eval('restCalls.' + i + "_RESTCall"), eval('RESTCalls_GET_ExampleData.' + i + "_ExampleData"), 200);
eval('RESTCalls_GET.' + i)(function(result) {
result.should.equal(eval('RESTCalls_GET_ExampleData.' + i + "_ExampleData"));
done();
});
}),
it('should return error if response code is NOT 200', function(done) {
mockClient.mockURLForError(eval('restCalls.' + i + "_RESTCall"), null, TestData.RESTCallResponseError_Test);
eval('RESTCalls_GET.' + i)(function(errorObj) {
errorObj.should.have.property('errorCode');
done();
});
});
});
});
}
I am looping though functions in RESTCalls_GET. Say, for example, i = getComponent, a function called getComponent_RESTCall will be in module restCalls
I have been told that one way to accomplish this is by using eval() (even though it is not recommended). This way is not working and when I debug, the parameters which use eval() in mockURLForSucceed are undefined.
This obviously causes all my tests to fail.
Any suggestions appreciated.
EDIT: (additional information)
var mockClient = function() {
var urlMap = {};
return {
get: function(url, callback) {
var entry = urlMap[url];
if (entry) {
callback(entry[0], entry[1]);
} else {
console.error("Unable to match URL " + url);
}
return {
on: function() {
//Ignore
}
};
},
mockURLForSucceed: function(URLofRESTCall, succeedData, succeedRes) {
urlMap[URLofRESTCall] = [succeedData, {statusCode: succeedRes}];
},
mockURLForError: function(URLofRESTCall, errorData, errorRes) {
urlMap[URLofRESTCall] = [errorData, errorRes];
}
}
}();
EDIT: (half way there)
I've resorted back to eval() an got the function/variable name required in format file.functionName by this:
var RESTCallURL = eval('"restCalls." + i + "_RESTCall"');
var RESTCallData = eval('"RESTCalls_GET_ExampleData." + i + "_ExampleData"');
The problem I'm having now if that these are strings. So when I pass them into a function, it gets that string value and not the one it equals in it's own function, does that make sense?
What I mean is that if I passed in RESTCallURL into a function now, then the value of that parameter would be restCalls.whatever_RESTCall whereas before it got the URL of the REST Call I am calling (http://whatever). Since I now have the name of the function, am I able to search for functions in my project by that name?
This task seems so simple to do and I think I am over thinking it.
I don't think you need eval there, what about using
RESTCalls_GET[i](function(result) {
result.should.equal(RESTCalls_GET_ExampleData[i + '_ExampleData']));
done();
});
You could easily test this behaviour by defining the following in your browser console
var test = {
'some-function': function() { console.log('works'); }
};
test['some-function']();

Categories

Resources