Variable Becomes Undefined in Last Iteration of Loop - javascript

I have a application where I am getting route info from one location to another, possibly between multiple locations. In my function, I'm looping over checked locations and writing out the directions to the page with jQuery. On the last iteration of the loop the 'myRoute' variable becomes undefined. So, if I have three locations, it bombs on the third, but if I use those same three and add a fourth, the first three works and the fourth bombs. I traced the behavior in Firebug, and while the myRoute variable does get filled properly, as soon as it moves to the next line, it is all of a sudden undefined. I removed instances of myRoute and replaced with directionResult.routes[0].legs[i] but still got the undefined error. What's going on here?
function setInstructions(directionResult, idname, start) {
//clean tour list
$('#tours_list .tour').remove();
$('#routeTitle').show();
var checkboxArray = $(".selector.Favs" + idname).find("li");
var idx = 0;
var linkMap = $('#routeTitle').find('.link-map')[0];
linkMap.href = __mapLink+'saddr=' + start;
var firstStop = true;
//iterate selected properties
for (var i = 0; i < checkboxArray.length; i++) {
var curChk = checkboxArray[i];
if ($(curChk).hasClass('active')) {
//get steps
var myRoute = directionResult.routes[0].legs[i]; //this is what becomes undfined
var title = $('<div>').addClass('mileage').append($('<p>').append($('<strong>').html(myRoute.distance.text + "<br /> about " + myRoute.duration.text)));
var ol = $('<ol>').addClass('directions');
for (var j = 0; j < myRoute.steps.length; j++) {
var step = myRoute.steps[j];
var li = $('<li>').append($('<div>').addClass('direction').html(step.instructions));
li.append($('<div>').addClass('distance').html(step.distance.text + " - " + step.duration.text));
ol.append(li);
}
//add tour with directions
$('#tours_list').append(temp);
}
}
}

The issue is that the array in directionResult.routes[0].legs is not as long as checkboxArray.length so your code is trying to access beyond the end of directionResult.routes[0].legs and thus gets undefined.
It isn't helping you that you are testing for only active class items because i goes from 0 to checkboxArray.length - 1 regardless.
I don't follow exactly what you're trying to do, but you might be able to work around this by only iterating the items that have .active in the first place so i never goes beyond the number of active items. You might be able to do that by changing this:
var checkboxArray = $(".selector.Favs" + idname).find("li");
to this:
var checkboxArray = $(".selector.Favs" + idname).find("li.active");
And, then remove the if check for the active class. This will make it so your index i never goes higher than the number of active items.
You could also just keep a counter of the active items you've processed and use that counter to index into directionResult.routes[0].legs[cntr] instead of using i.

Related

Apps Script JS adding items to array from range (if not already in array) fails

I am looping through various cells and want to add their string content do an array, if the content is not already in the array. It works perfectly fine when I do it manually like so, trying to add 'eJobs' to the array (see below "var item = 'eJobs') which already containts 'eJobs':
var divisionarray = ['eJobs']
for (var i = 0; i < cells_users.length-1; ++i) {
var row_users = cells_users[i];
if (row_users[0] == user_ldap) {
var podarray = row_users[1].split(', ')
for (j = 0; j < podarray.length; j++) {
for (var k = 0; k < cells_edit.length; ++k) {
var row_edit = cells_edit[k]
if (podarray[j] === row_edit[0]) {
var item = 'eJobs'
if (!(divisionarray.indexOf(item) >= 0)) {
divisionarray.push(item)
}
}
}
}
Logger.log(divisionarray)
As expected, the log file shows [17-10-08 19:11:04:111 BST] [eJobs], illustrating that the code works and 'eJobs' has not been added to the array as it is already in the array.
Now, when I change var item='eJobs' to values of a range
var item = sheet_pods_edit.getRange(startRow+k, startColumn+1).getValue();
the code does not work anylonger, as the log file shows:
[17-10-08 19:14:03:770 BST] [eJobs, eJobs, BestJobs, Vivre Deco, ...
Note I have a range of thousands of cells, so I get alot of duplicates added. What am I missing? Note the cells of the defined range are indeed just strings with a single word (e.g. 'eJobs').
The code is working and the log file is indicating what the problem is..
[eJobs, eJobs, BestJobs, Vivre Deco,
In the second eJobs there is a white space before eJobs, so the first value and the second value don't match.
Without seeing your data and going by the 'just strings with a single word' I would say that using a .replace(" ", "") on the text string should work, this will find the first " " in the string and remove it. I.e. " eJobs" would become "eJobs".
2.
Is this line of code just for testing? You should never use a method like this in a script. It will be extremely inefficient
var item = sheet_pods_edit.getRange(startRow+k, startColumn+1).getValue();
Instead get the full range using .getValues()and iterate over it then.
3.
Is there a reason you are using === in if (podarray[j] === row_edit[0]) unless you need to check for type always use ==

Google apps script - Broken for loop

I'm working in Google apps script and seem to have screwed up one of my for loops. I'm sure that I am missing something trivial here, but I can't seem to spot it.
Code Snippet:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var lastRow = sheets[3].getLastRow();
var zw = sheets[3].getRange(2, 1, lastRow - 1, 26).getValues();
for (var j = 0; j < zw.length; ++j) {
if (zw[j][9] === 'Yes') {
var masterEmail = [];
var firstLetterLastName = [];
var first2Letter = [];
var masterEmail.push(zw[j][22]);
var firstLetterLastName.push(zw[j][1].charAt(0).toLowerCase());
var first2Letter.push(zw[j][1].charAt(0).toLowerCase() + zw[j][1].charAt(1).toLowerCase());
//The rest of the function follows...
}
}
What's Not Working:
The for loop doesn't increment. When running the code in a debugger, var j stays at a value of 0.0, and the rest of the function only runs based of off the values in the 0 position of zw.
What I need it to do (AKA - How I thought I had written it:)
The ZW variable is holding a 2 dimensional array of cell values from a Google sheet. I'm looping through that, checking the 9th value of each array entry for a string of "Yes" and then running the rest of the function (for each column with a "Yes") if the condition is true.
I thought I had this working before, but recently had to restructure and optimize some things. Now I'm starting to think I may need to rethink things and use a different loop method. Can anyone educate me?
Edit: Here's a bit more context as requested:
function menuItem1() {
var ui = SpreadsheetApp.getUi();
var response = ui.alert('Are you sure you want to send emails?', ui.ButtonSet.YES_NO);
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var lastRow = sheets[3].getLastRow();
var zw = sheets[3].getRange(2, 1, lastRow - 1, 26).getValues();
if (response === ui.Button.YES) {
for (var j = 0; j < zw.length; j++) {
if (zw[j][9] === 'Yes') {
var firstLetterLastName = [];
firstLetterLastName.push(zw[j][1].charAt(0).toLowerCase());
//Other Stuff....
}
}
}
}
I have a menu item attached to a simple onOpen, that calls menuItem1(). Calling the function prompts the user with a warning that they are about to send emails, then goes about getting data to assign email addresses based on the contents of the sheets. firstLetterLastName is an example.
I'm still not getting the loop to function, is it because I have it between two if statements? (Here is a link to the sheet)
Indeed it is quite trivial. You have mixed up your increment. You wrote
for (var j = 0; j < zw.length; ++j)
which means that you do 1 + i (and we know that at the start i = 0 which means your value will always be 1) instead of using the usual
for (var j = 0; j < zw.length; j++)
which would mean that you do i + 1 and update i, so you will get the expected 0 + 1 1 + 1 etc
EDIT:
First, I recommend instead of something like
if (responseMir === ui.Button.YES) {
// Your For loop
doing
if (responseMir !== ui.Button.YES) {
return
}
and in a similar fashion in the for loop
if (zw[j][9] !== 'Yes') {
break
}
It mostly helps increase readability by not including large blocks of code under a single if, when all you want to do is to stop execution.
Your for loop gets broken because of the mistake here:
teacherEmailMir.push(selValsMir[j][7]);
So your loop will go over once. However on the next itteration, you try to push selValsMir[1][7] which does not exist. Note that each itteration you have var selValsMir = []; inside the loop, which means that for every j selValsMir will always be an empty array. So with the following line
selValsMir.push([zw[j][0], zw[j][1], zw[j][2], zw[j][3], zw[j][4], zw[j][5], zw[j][7], zw[j][22], zw[j][23], zw[j][24]]);
your array will always have selValsMir.lenght = 1 and selValsMir[0].length = 10. So obviously trying to access anything from selValsMir[1] will throw you an error and stop the script right there.
I also recommend looking over the if statements that look at the first and first 2 letters of the name as I believe you can accomplish the same with less code. Always try to streamline. Consider using switch() where you end up using a lot of else if

Loop reload same trial instead of next one Qualtrics

I have a problem with Qualtrics Loop and Merge + Javascript. Basically, Qualtrics reload the previous trial of my loop instead of a new one.
First, here is what I try to do: I have two sets of pictures, at each loop, one picture of each set is randomly picked and displayed (randomly to the left or right side). At the end of the loop, each pictures of the set will be displayed (33 in each set, so 33 trials of the loop), without picking twice the same picture. At each trial, I also display randomly the name of the condition "in relationship" or "friends"
The problem in details:
I have coded something that seems to work quite well, except at some point: It happens that when loading the next trial of the loop, it doesn't display new pictures but instead the one that were presented in the previous trial (and also the same name of condition). This can go for a while presenting me always the same pictures, or this can just be one time.
In any case, this trial which is represented count as a trial, as my loop always ends after 33 trials, whatever if some of these are actually not new pictures presented.
This problem doesn't appear always at the same trial, and can appear several times in a loop.
It also happened that only one picture displayed is new, and the other is the one presented in the previous trial. (I really don't get how this can happen).
I coded the randomisation of picture in an empty question, just before the question where it displays the pictures. This empty question is supposed to not require to press the "next" button. When the trial are correct, this is working well, but when the next trial is going to be one "reloaded" trial, it requires to press the "next" button.
I tried the survey with Firefox and Edge, and the problem is the same.
My javascript code:
In the first question of the survey:
Qualtrics.SurveyEngine.addOnload(function () {
function shuffle(o) { //v1.0
for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
return o;
};
var FacesM_list = "IM_7U04c3TQlA7dT3D,IM_eE5iBcKfoOblbJH,IM_a3mklzA9E1OuRWl,IM_bQnoSJOGwa0vn9P,IM_5inNCVgPdHHTVSR,IM_bHlurQJWSXDRDFj,IM_3rCr2DIzW2cvqLP,IM_509x1wz7pM6PP0N,IM_3UGdsp02IcCqaSV,IM_eLrY7bwDPiT7apn,IM_3LdlHnBb6tnkBEh,IM_3pY6z6JDgaDvwq1,IM_9HtZxBS79DiOfR3,IM_03c9pDSpcwcqIyF,IM_6WGKJOzUWK4TJat,IM_2rxQPEGO7SEvsY5,IM_9YN3UCUtWEWTfGR,IM_8jZTSUAGJfuVECV,IM_9nQkFhRY2cLIVgN,IM_2abcJA7B79jt30h,IM_cD31N8XPTGliUN7,IM_0eL8iQd4PVdyuQl,IM_cuOoV9gSAe6CWd7,IM_9Nv3X7lWYEsTzsF,IM_5ccXAuuomEEyamp,IM_9mnvThFiNA5U84t,IM_e3UGNNuMdrKH8cl,IM_3aggsd5P9MMlUDr,IM_4ORY6GEMW8CmNPT,IM_50WOBkz8ADTFGHH,IM_3rqtlVBfijYCccZ,IM_3CzDsr0tYv7PH5H,IM_4SmeprjDgOeCl5b"
var FacesF_list = "IM_6fkHDs5f6ItAuk5,IM_0ri9MLjDhHxyonP,IM_bKlHtoAaxnBFlnT,IM_1WUqtBPpdhERpjf,IM_ac0yWos8tAqSMNT,IM_3xCePACn1Lq97tH,IM_6o1ZPLGUM682Au1,IM_babATdN3VtBLIsl,IM_8HSUICLvFvDXaN7,IM_0ebTztq3ML5Zh0V,IM_3lB8j5dhMs8i8ip,IM_0iC0pwDlpOkcTGt,IM_cIRojwU6sx3W7Od,IM_9ZHNbignrAfcThX,IM_8iFXvVcCqk5hemN,IM_6rrwImdl4Nss0u1,IM_6mPEaoIdazwqAWp,IM_b8lrxhsPGcc1HaR,IM_23uYWeF2gYVMsap,IM_6ycfrm5xOlewjFb,IM_7UKValFGc9Kmpp3,IM_8Bbkzsmc7CyMvqt,IM_d5S95FnSgo8j06F,IM_brXT4VUU8QJiRwN,IM_9MEkpgEmOwXhril,IM_6KG9qokOlD16GDH,IM_ellgVnYbtb8ZSbb,IM_eg6qSYMQ56z5JpX,IM_5vfbDNPdZeP1XCZ,IM_cDbOprwCUUSnUZT,IM_cumIGHXOFByV4Pz,IM_0jh2Va4JTfGsQDz,IM_0CGlFRy4dW8lDcF"
var FacesM_order = [];
for (var i = 1; i <= 33; i++) {
FacesM_order.push(i);
}
FacesM_order = shuffle(FacesM_order);
var FacesF_order = [];
for (var i = 1; i <= 33; i++) {
FacesF_order.push(i);
}
FacesF_order = shuffle(FacesF_order);
var nTrial = 0;
Qualtrics.SurveyEngine.setEmbeddedData("FacesM_order", FacesM_order.toString());
Qualtrics.SurveyEngine.setEmbeddedData("FacesF_order", FacesF_order.toString());
Qualtrics.SurveyEngine.setEmbeddedData("FacesF_list", FacesF_list.toString());
Qualtrics.SurveyEngine.setEmbeddedData("FacesM_list", FacesM_list.toString());
Qualtrics.SurveyEngine.setEmbeddedData("nTrial", nTrial.toString());
});
And in the first question of the Block with Loop and Merge:
Qualtrics.SurveyEngine.addOnload(function () {
var nTrial = Number("${e://Field/nTrial}") + 1;
var FacesF_list = "${e://Field/FacesF_list}".split(',');
var FacesM_list = "${e://Field/FacesM_list}".split(',');
var FacesF_order = "${e://Field/FacesF_order}".split(',');
var FacesM_order = "${e://Field/FacesM_order}".split(',');
var FacesF = FacesF_list[FacesF_order[nTrial]];
var FacesM = FacesM_list[FacesM_order[nTrial]];
var rand = Math.random()
if (rand < 0.5) {
var left = FacesF
var right = FacesM
} else {
var left = FacesM
var right = FacesF
}
Qualtrics.SurveyEngine.setEmbeddedData("nTrial", nTrial.toString());
Qualtrics.SurveyEngine.setEmbeddedData("left", left.toString());
Qualtrics.SurveyEngine.setEmbeddedData("right", right.toString());
this.clickNextButton.delay(.00000000000001);
});
Thanks a lot for your help!

Break loop based on element not existing

I have built a cms that allows users to add up to 10 images into the slideshow, which all output in the front end in divs with ids of showcaseSlide with a number from 0-9 appended on the end, e.g. showcaseSlide0, showcaseSlide1 etc. For the javascript that controls the slideshow, I need to output all of the div id's into an array, but end the array when the slides finish, eg if the div ids went from showcaseSlide0 - showcaseSlide3, I would need the array to go from slides[0] - slides[3].
Here is the current code and some commented out code that I have tried before:
var slides = new Array();
var count = 0;
for(i=0; i<=10; i++){
slides[i] = "showcaseSlide"+i;
document.write(slides[i]); //so that I can see which id's are in the array
var div = document.getElementById(slides[i]);
//if(div) { break; } <- doesn't break
//if(document.getElementById(slides[i]) == null) break; <-breaks after 1st
//if(document.getElementById(slides[i]) == undefined) break; <- breaks after 1st
};
Edit:
I've found out (thanks to Teemu who commented below) that it wasn't working because it was called before the page load, therefore before the objects were rendered. I also have to thank Peter Kelly (who also commented below), who pointed out that I needed to use a ! in my breaking if statement and Frazer who pointed out my loop was 1 too big.
Here is the new code (including the other elements of the initialising function):
var count = 0;
var wait = 4000;
var slides = [];
function startShowcase() {
for(var i=0; i<10; i++){
slides[i] = "showcaseSlide"+i;;
if(!document.getElementById(slides[i])) { break; }
};
setInterval(showcase, wait);
};
I wouldn't be so complex. I guess you have a class applied to all your slides div? If you do, use something like the following:
var slides = []
var divs = document.getElementsByClassName('slide-class')
for (var i = 0, l = divs.length; i < l; ++i) {
slides.push("showcaseSlide" + i)
}
Btw, several comments about your code:
Don't use new Array(). Instead, use []. See here to understand why.
You didn't use the var keyword to declare your i variable, which means this variable is global. Global is evil.
document.write is evil.
I guess your count variable has some use later?
You have DIVs numbered 0-9 but your loop runs 11 times.
Not actual code, but this explains it.
for(i=0; i<=10; i++){
0 = 1st
1 = 2nd
2 = 3rd
3 = 4th
4 = 5th
5 = 6th
6 = 7th
7 = 8th
8 = 9th
9 = 10th
10 = 11th
}

Why does removing an element with javascript prevent iteration of elements?

I am trying to replace all text fields on a page with labels.
function replaceInputTextFieldsWithValues() {
var inputFields = document.getElementsByTagName("input");
for(var i = 0; i < inputFields.length; i++) {
if(inputFields[i].getAttribute("type")== "text") {
var parent = inputFields[i].parentNode;
var value = inputFields[i].value;
parent.removeChild(inputFields[i]);
var label = document.createElement('label');
label.setAttribute('for', value);
label.innerHTML = value;
parent.appendChild(label);
}
}
}
My HTML document is organized in tables. This function only seems to work on the first element in each table.
On the other hand, when I remove the line:
parent.removeChild(inputFields[i]);
The code seems to work fine. Why is this happening and how do I fix it?
What you get back from getElementsByTagName is an HTMLCollection, which is live. (This is true for the other getElementsByXYZ methods, but not querySelectorAll.) That means if you remove the element at index 0, the HTMLCollection's length will go down and you'll have a new element at index 0 instead of the one you just removed.
Just work your way through it backward and you'll be fine:
for(var i = inputFields.length - 1; i >= 0; i--) {
// ...
}
Alternately, convert the HTMLCollection into an array and then loop through the array. (See the live example and code below).
Edit: Or, as Chris Shouts points out in the comments, you can just make use of the changing length, but it's not quite as simple as Chris' suggestion because you're only removing the elements sometimes. It would look like this:
var inputFields = document.getElementsByTagName("input");
var i = 0;
while (i < inputFields.length) {
if(inputFields[i].getAttribute("type")== "text") {
// Remove it and DON'T increment `index`
}
else {
// Skip this one by incrementing `index`
++index;
}
}
Which of these three approaches to use will depend on the situation. Copying to an array gives you a nice static dataset to work with, and if you make sure to release the reference to the HTMLCollection, you're giving the browser the opportunity to realize it doesn't have to keep that list up-to-date when things change, which could reduce overhead. But you're copying the references briefly, which increases overhead a bit. :-)
Additional: Here's an example showing this effect, and also showing a fairly efficient (but obscure) way to create an array from a HTMLCollection:
HTML:
<ul>
<li>LI0</li>
<li>LI1</li>
<li>LI2</li>
</ul>
JavaScript:
var lilist, liarray;
// Get the HTMLCollection, which is live
lilist = document.getElementsByTagName('li');
// Create an array of its elements
liarray = Array.prototype.slice.call(lilist, 0);
// Show initial length of both
display("lilist.length = " + lilist.length); // Shows 3
display("liarray.length = " + liarray.length); // Shows 3
// Show what the 0th element of both is (both show "LI0" in the live example)
display("lilist[0].innerHTML = " + lilist[0].innerHTML); // Shows LI0
display("liarray[0].innerHTML = " + liarray[0].innerHTML); // Shows LI0
// Remove the first list item
display("Removing item 0");
lilist[0].parentNode.removeChild(lilist[0]);
// Show the length of both, note that the list's length
// has gone down, but the array's hasn't
display("lilist.length = " + lilist.length); // Shows 2, not 3
display("liarray.length = " + liarray.length); // Still shows 3
// Show what the 0th element of both *now* is
display("lilist[0].innerHTML = " + lilist[0].innerHTML); // Shows LI1 now
display("liarray[0].innerHTML = " + liarray[0].innerHTML); // Still shows LI0
Live copy

Categories