I am struggling with this for a day now, and going in circles and help would be greatly appreciated :-)
Abstract
Asynchronous ajax calling cgi with resolver and fqdn variables in order to return a dns resolution agains this pair. (returns the output of dig #resolver $fqdn)
Problem
In firebug I can see that get requests are being fired asynchronously and responses to the browser are as expected. However I cannot place the responses in correct divs in doc as onreadystatechange doesn't recognize the objects.
Side note
Apart from the fact that I am iterating through the array of objects It appears that they all are fired instantaneously even when delay between iterations is placed.
Below is a code with my comments
As resolver is an array I created an array of xmlhttprequest objects.
function resolve() {
var numofres = 6;
var arr = new Array;
arr[0] = "192.168.1.11";
arr[1] = "8.8.8.8";
arr[2] = "8.8.4.4";
arr[3] = "159.134.0.1";
arr[4] = "159.134.0.2";
var len = arr.length;
var ax = new Array(); //creating ax as an array
for (var i=0; i<=len; i++) { //iterating through the length of resolvers array
ax[i] = new XMLHttpRequest(); //assigning to each item in array new object
//alert(ax[i]); // shows that object exists
ax[i].onreadystatechange = function(){
/*===
problem is above - firebug will show:
**Uncaught TypeError: Cannot read property 'readyState' of undefined**
**ax.(anonymous function).onreadystatechangehello.cgi:30**
oddly it will still populate divs inner html with 'loading +1 '
albeit regardless of readystate code (can be 4 or anything else )
It perplexes me why i is thought as a function?
=====*/
// alert(i); //if this is enabled I will see readyState==4 populated correctly
if (ax[i].readyState != 4) {
document.getElementById('return_table_'+i).innerHTML="loading "+i;
}
if(ax[i].readyState == 4){
// get data from the server response
var response_ready=ax[i].responseText;
document.getElementById('return_table_'+i).innerHTML = response_ready;
}
}
ax[i].open("GET","av.pl?resolver=" + arr[i] +"&fqdn=google.com",true); //works
ax[i].send(null); //works
}
}
Your problem is an extremely common one. In JavaScript, variables are scoped at the function level, not at the block statement level. Thus, as you iterate through that loop with the variable "i", each function you create in the loop shares the same "i". Thus when the functions are actually called, the value of "i" will be what it is at the end of the loop — and that's a point beyond the end of the array!
To avoid the problem, you need to create those functions in another function. A clean way to do that is to have a separate local function:
function makeReadyStateHandler(i) {
return function() {
if (ax[i].readyState != 4) {
document.getElementById('return_table_'+i).innerHTML="loading "+i;
}
if(ax[i].readyState == 4){
// get data from the server response
var response_ready=ax[i].responseText;
document.getElementById('return_table_'+i).innerHTML = response_ready;
}
};
}
Then just call that function from the loop:
ax[i].onreadystatechange = makeReadyStateHandler(i);
By using a separate function like that, you assure that each handler function will have its own copy of "i" that's frozen at the correct point in the loop. The function will return a newly-created function as its result, which you'll use as the event handler.
Related
I have the below for loops, I have it manytime in my code, with different variables name, I would put it in a function with parameters and call it but it didn't work
for(let i = 0; i < inputs.length - 1; i++){
if(!nputs[i].value){
error += inputs[i].getAttribute('test') + " is blank";
isTrue = false;
}
}
Here what I did
let y = "";
let z = true;
function test(x,y,z){
for(let i = 0; i < x.length - 1; i++){
if(!x[i].value){
y += x[i].getAttribute('name') + " is blank !";
z = false;
}
}
}
let error = "";
let isTrue = true;
test(inputs,error,isTrue);
shall I do return in the function? if yes which return should I do?
Scope: when you define y and z outside the function (in the global scope presumably) they are different than the y and z defined in the parameter list of the function. The ones in the parameter list only exist within the body of the function. Changing the value of the parameter named y within the function does not change the value of the global variable y. So the simple answer to your question is, yes, you need to return something, since the value of the parameter y is lost when the function is done executing.
Give your variables descriptive names. Let the obfuscator do it's thing later.
function test(x,y,z) -> function valueTest(arr, err, success)
The boolean status and error string are redundant bits of information. If the error string is not empty, then the status is failure. So you don't need to return both a boolean and the string.
The status of the previous test is of no relevance to the next test. Therefore, z or success doesn't have to be passed in to the function each time, as it (or something like it) is really the desired output of the function, and each call of the function can be treated separately. If you want to combine the results from different tests then that should be the concern of whatever is calling this function - see separation of concerns and coupling
The only parameter the function actually needs is the value that is under test (the array).
When you write the function you define the return value, and thus you define how other code can decipher those results. The function itself doesn't have to do all the work of interpreting the results and building the error string. If your return value was just an array of name attribute values (of the elements of the test array that failed), the calling code could still process "success" or "failure". If the return value has one or more elements, or a length > 0 that would indicate failure.
Removing the redundant/unnecessary parameters and information, you'll have a function that looks something like this:
function valueTest(arr) {
let results = [];
for (let i = 0; i < arr.length - 1; i++){
if (!arr[i].value) {
results.push(arr[i].getAttribute('name'));
}
}
return results;
}
The caller can decipher and build an error message from that. It might make sense for the function to handle some of the additional work by returning <name> is blank! instead of just <name>, and then you just need to join the elements of the array.
...so within the function...
results.push(arr[i].getAttribute('name') + ' is blank!');
...and back in the global scope...
const error = valueTest(inputs).join(" ");
let success = error.length > 0;
5.If you want a running status indicator from different tests, evaluate an individual test's result, then logically AND that with the previous result.
const result1 = valueTest(inputs1).join(' ');
let success = results1.length > 0;
const result2 = valueTest(inputs2).join(' ');
success &= (results2.length > 0);
Seeing the issues with your code are handled in the comments, I present you a simpler method.
If you count the elements that have the attribute and are not empty and compare it to the length of all the inputs passed you will have a better test
const test = (inputs,attr) => [...inputs]
.filter(inp => inp.getAttribute(attr) && inp.value.trim() !== "").length === inputs.length;
const istrue = test(document.querySelectorAll("input"),"name")
isTrue will be true if all passed inputs has an attribute called name
You can also do
const elems = document.querySelectorAll("input[name]")
const isTrue = elems.filter(inp => inp.value.trim() !== "").length === elems.length
Been struggling all day with looking at code examples and can't get this to work.
Basically I want to load an array using the HTML from four local files.
Then I'll use that HTML to fill a div dynamically with some kind of animated slider.
My problem is that I can't seem to get the timer to wait for the html to be retrieved and assigned to the variables. The GET is working great, but the variables don't seem to be loaded outside the GET loop.
I can see the asynch GET filling the array properly, but the elements remain undefined in the loop. I think they're global variables so it shouldn't be that.
I'm new at jQuery and have been using SO for examples, so open to any suggestions - hopefully this is just an obvious fix for an experienced javascript coder.
Here's my code:
window.standings = new Array(4);
window.iURL = 0;
standingsURL = ["MondayNight.html", "TuesdayNight.html", "WednesdayNight.html", "ThursdayNight.html"]
for (window.iURL = 0; window.iURL < window.standings.length; window.iURL ++) {
$.get(standingsURL[window.iURL], function( data ) {
window.standings[window.iURL] = data;
alert (window.standings[window.iURL]);
}, "text");
}
setTimeout(function repeatTimeout() {
if(window.standings[window.standings.length] !== undefined){
for (window.iURL = 0; window.iURL < window.standings.length; window.iURL ++) {
alert (window.standings[window.iURL]);
}
} else {
alert ("Wait again");
setTimeout(repeatTimeout, 2500);
}
}, 1000);
The first issue I can see is that in your repeatTimeout function, you're referencing an item in your array that doesn't exist.
window.standings[window.standings.length]
which is equivalent to window.standings[4]
You have an array with four items in it, and you're referencing array index 4 here - but arrays are zero-indexed, so only indexes 0, 1, 2, and 3 are valid. Try:
window.standings[window.standings.length - 1]
Edit:
Declare an empty array:
window.standings = []
Instead of: window.standings[window.iURL] = data;, do this:
window.standings.push(data)
Your if function now checks for length:
if(window.standings.length === 4)
This is a relatively crude solution. Check out this SO post for more information about the when function.
I'm pushing an object into an array of objects. One of the properties of the object (which is an array) is showing up as undefined, after the object is pushed to the array. If you look at my code below, where it says, "WHY IS THIS SHOWING IN CONSOLE?" - it doesn't make sense because if the array is undefined, it should have been undefined in both of the previous two checks (where it says "THIS IS NOT SHOWING IN CONSOLE."). This is driving me crazy. SOS. Please help.
var pathCopy = unit.path; // THIS IS AN ARRAY AND IT IS NOT EMPY
var directionCopy = unit.direction;
if (pathCopy.length < 1) {
console.log('THIS IS NOT SHOWING IN CONSOLE');
}
var object = {
'type': 'move',
'time': gameTime,
'name': unit.name,
'vector': pathCopy,
'direction': directionCopy
};
currentGameEvents.push(object);
if (object.vector.length < 1) {
console.log('THIS IS NOT SHOWING IN CONSOLE');
}
for (var i = 0; i < currentGameEvents.length; i++) {
if (currentGameEvents[i].vector.length < 1) {
console.log('WHY IS THIS SHOWING IN CONSOLE??');
}
}
I’m not sure how or why, but your example works, if I insert the undefined variables (i.e. I replaced unit.path with [345,34623,52] or something that is an array literal).
The only difference to that is, that you actually assign the reference to that array, instead of declaring a new array.
var pathCopy = unit.path; // pathCopy and unit.path are the SAME array
var pathCopy = [345,346,345]; // pathCopy is a completely NEW array
To fix this, try something like
var pathCopy = unit.path.slice(); // pathCopy is a completely NEW array that contains the same values as unit.path
whenever you want to copy an array; add .slice() at the end!
Again, this is deducing from the only difference between your code and what I tried, and this is probably the reason, why your code doesn’t work.
Another assumption is that currentGameEvents is an Array. Your code wouldn’t work at all, otherwise. I’m mentioning this because the title states that you were trying to push to an object, which isn’t possible.
I have a list of objects as shown in the image.
These all have the property statusCode: 62467 but the journey property goes like: 0,1,2,3,3,4,4,4,4
I want to loop through these objects and return the FIRST of the duplicated (they are not the same object, just that both have the same journey number and the same status code) objects with the same journey number.
So I want to return the bold objects: 0,1,2,3,3,4,4,4,4
$.each(points, function (index, point) {
for (i = 0; i < journeyNumber.length; i++) {
if (point.k.journey === journeyNumber[i] && point.k.statusCode === '62467') {
console.log(point);
latlngs.push(point.j.aa.k);
latlngs.push(point.j.aa.B);
}
}
});
The screenshot is the log of console.log(point), so ideally I would like another loop inside which returns only the first object of the same journey number.
Hope this makes sense and thank you for your time.
Try this,
var temp = [];
$.each(points, function (index, point) {
if (temp.indexOf(point.k.journey) === -1) {
temp.push(point.k.journey);
console.log(point);
latlngs.push(point.j.aa.k);
latlngs.push(point.j.aa.B);
}
});
Create a fresh object with status codes and check against that.
var journeys = {};
for(object in points){
// extract the properties you want (or use them directly, this is not necessary)
var journey = points[object].journey;
var status = points[object].statusCode;
// use the typeof operator to see if the journey has already been set before
if(typeof journeys[journey] == "undefined"){
// then define it.
journeys[journey] = status;
}
}
(Please note I am not actually correctly referencing the journey and statusCode, you'd have to do something like objects[object][k].journey to access the right property, but thats not really the point)
You can even add anything you want into the journeys object, nesting another object with the extracted latitude and longitude, or even just nesting the entire object in the journey!
journeys[journey] = points[object];
Now you can get every journey by looping through them again, and the associated first statusCode:
for(journey in journeys){
console.log("First instance of journey " + journey + " had statusCode " + journeys[journey]);
}
I'm using the Datatables jQuery plugin. The table is pulling the data from an AJAX source (a SQL Server query, processed by ASP.NET into a JSON object). I want to create a live view of the table so that changes appear in real time. But rather than reloading the entire table every few seconds with fnReloadAjax() (which, from experience, has proven to be quite burdensome on the browser) I'm only updating the records that are new or modified, using fnAddData() and fnUpdate().
After getting a JSON object of just the new or modified records, here's my code to process the object.
var newData = updatedDataJSON.aaData;
if (newData[0] != null) {
for (i = 0; i < newData.length; i++) { //Loop through each object
if (newData[i].bNewCase === true) { //Process new cases
oTable.fnAddData(newData[i]);
} else { //Process modified cases
var tableArray = oTable.fnGetData();
var index;
var found = false;
var serial = newData[i].serial;
var dataObject = newData[i];
//First gotta find the index in the main table for
// the record that has been modified. This is done
// by matching the serial number of the newData
// object to the original aData set:
for (ii = 0; ii < tableArray.length; ii++) {
var value = tableArray[ii]['serial'];
value = value.replace(/<\/?[^>]+(>|$)/g, "");
if (value === serial) {
index = ii;
found = true;
}
}
if (found) {
oTable.fnUpdate(dataObject, index);
console.log('Updated ' + newData[i].serial);
}
}
}
}
My problem is that even though the newData.length property of the first for loop could be greater than 1, the for loop exits early (after one iteration). I added the console.log statement at the end and it started passing errors saying that newData[i].serial was undefined. This makes me think that the entire newData array was destroyed or something...
I'm really hoping that I've just made a stupid mistake (though I've checked and checked and checked some more but can't find one). Maybe there's something that I'm overlooking. If anyone has any advice, it would be greatly appreciated.
Credit goes to #elclarnrs for the solution, posted above in the comments. The solution was declaring the values of i and ii in the scope of the function. That got everything working smoothly. Good to know for future reference.