Google apps script - Broken for loop - javascript

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

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 Script for a Sheet - Maximum Execution time Exceeded

I'm writing a script that's going to look through a monthly report and create sheets for each store for a company we do work for and copy data for each to the new sheets. Currently the issue I'm running into is that we have two days of data and 171 lines is taking my script 369.261 seconds to run and it is failing to finish.
function menuItem1() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName("All Stores");
var data = sheet1.getDataRange().getValues();
var CurStore;
var stores = [];
var target_sheet;
var last_row;
var source_range
var target_range;
var first_row = sheet1.getRange("A" + 1 +":I" + 1);
//assign first store number into initial index of array
CurStore = data[1][6].toString();
//add 0 to the string so that all store numbers are four digits.
while (CurStore.length < 4) {CurStore = "0" + CurStore;}
stores[0] = CurStore;
// traverse through every row and add all unique store numbers to the array
for (var row = 2; row <= data.length; row++) {
CurStore = data[row-1][6].toString();
while (CurStore.length < 4) {
CurStore = "0" + CurStore;
}
if (stores.indexOf(CurStore) == -1) {
stores.push(CurStore.toString());
}
}
// sort the store numbers into numerical order
stores.sort();
// traverse through the stores array, creating a sheet for each store, set the master sheet as the active so we can copy values, insert first row (this is for column labels), traverse though every row and when the unique store is found,
// we take the whole row and paste it onto it's newly created sheet
// at the end push a notification to the user letting them know the report is finished.
for (var i = stores.length -1; i >= 0; i--) {
ss.insertSheet(stores[i].toString());
ss.setActiveSheet(sheet1);
target_sheet = ss.getSheetByName(stores[i].toString());
last_row = target_sheet.getLastRow();
target_range = target_sheet.getRange("A"+(last_row+1)+":G"+(last_row+1));
first_row.copyTo(target_range);
for (var row = 2; row <= data.length; row++) {
CurStore = data[row-1][6].toString();
while (CurStore.length < 4) {
CurStore = "0" + CurStore;
}
if (stores[i] == CurStore) {
source_range = sheet1.getRange("A" + row +":I" + row);
last_row = target_sheet.getLastRow();
target_range = target_sheet.getRange("A"+(last_row+1)+":G"+(last_row+1));
source_range.copyTo(target_range);
}
}
for (var j = 1; j <= 9; j++) {
target_sheet.autoResizeColumn(j);
}
}
Browser.msgBox("The report has been finished.");
}
Any help would be greatly appreciated as I'm still relatively new at using this, and I'm sure there are plenty of ways to speed this up, if not, I'll end up finding a way to break down the function to divide up the execution. If need be, I can also provide some sample data if need be.
Thanks in advance.
The problem is calling SpreadsheepApp lib related methods like getRange() in each iteration. As stated here:
Using JavaScript operations within your script is considerably faster
than calling other services. Anything you can accomplish within Google
Apps Script itself will be much faster than making calls that need to
fetch data from Google's servers or an external server, such as
requests to Spreadsheets, Docs, Sites, Translate, UrlFetch, and so on.
Your scripts will run faster if you can find ways to minimize the
calls the scripts make to those services.
I ran into the same situation and, instead of doing something like for(i=0;i<data.length;i++), I ended up dividing the data.length into 3 separate functions and ran them manually each time one of them ended.
Same as you, I had a complex report to automate and this was the only solution.

compare array values javascript replace values matched

I have two sheets, one is updated from time to time, one is the backup which it may contain some the same values of the first one, the first one needs to be updated in some columns with recorded data already saved in the backup, I have written this script which is working fine some times but other times completely wrong (!) is taking right value form wrong row! second sheet is sorted descendant way so that inedxOf will match the right value for sure as first one.
var M = data.length;
for ( var r = 0; r < M ;r++){
var L = datiArchivio.length;
var row = data[r];
var nave = row[4];
var imo1 = data [r][23]
if ( imo1 == "" && nave!= ""){
for (var j = 0; j < L;j++){
var roww = datiArchivio[j];
var IMO = roww[23];
var naveArchivio = roww[4];
var GT = roww[25];
if ( IMO == ""){continue;} else {
if ( naveArchivio == nave) {
row.splice(23,1,IMO);
row.splice(25,1,GT);
}
}
}
}
}
I guess you meant something like this, but it is not working, in the archivio sheet there could be matching row with empty value at the reference column as well as fulfil values, it is ordered so that it should match first one which fullfil the condition, hence I added roww[23]=="" condition in order to skip that j row. it is challenging, keep on work on the logic.
This last version is working, and also quickly, I was stucked in the old "rubbish in rubbish out".

jquery.sheet trigger formula calculation on cell

So I am loading csv files from a server an inserting js function calls that create tables/sheets with jquery.sheet. Everything works thus far but when I put functions into the list they do not calculate.
The sheets (simplified)data object for the td has this before I modify anything:
Object {td: x.fn.x.init[1], dependencies: Array[0], formula: "", cellType: null, value: "=A2+B2+C2"…}
When I set the formula value it changes to:
Object {td: x.fn.x.init[1], dependencies: Array[0], formula: "=A2+B2+C2", cellType: null, value: "=A2+B2+C2"…}
So I understand how to set formula and value but what i wish to do is trigger an event to auto calculate a cell hopefully based on an "X,Y" co-ordinate, or find out if I am taking the wrong approach.
I dont know if this helps but when I go to edit a cell it will appear as ==A2+B2+C2 not =A2+B2+C2
I would supply my code but because of the C# asp and js interaction it is not short I don't think it would help.
Solved:
Two things are essential to load a formula from a csv file to a jquery.sheet instance and then have it calculate. First is to manually set the objects ["formula"] property, while leaving off the '=' in the beginning because it adds its own. then you must trigger the "calc(s,true)" function with s as the sheets index and in my case I put true as the second parameter because I believe it forces calculations on cells with a function.
var sheets = jQuery.sheet.instance[0];
for (var s = 0 ; s < names.length; s++) {
var sheet = sheets.spreadsheets[s];
for (var k = 1; k < sheet.length; k++) {
var row = sheet[k];
for (var j = 1; j < row.length; j++) {
var col = row[j];
//alert(cell.value);
if (col.value.startsWith("=")) {
col["formula"] = col.value.substring(1, col.value.length);
}
}
}
sheets.calc(s, true);
}
If a better way is found please let me know. I do not think this is very scalable as it is O(n^3).

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
}

Categories