Looping Through Both Rows and Columns - google-apps-script

I am self taught in Google Script and not very good at it... LOL One thing I have always struggled with is loops. :) Going to try and be as detailed as possible while still keeping this simple... What I need to do is:
Start at Row 113
Capture the values in the following columns:
B, D, F, S, AD, AM, AV, BB, BH, BK, BN, BQ, BT, BW, BZ, CC, CH, CM, CR, CW, and DB
Side note, in case it matters: the columns in-between all of the above are not blank
Starting at B225, paste each of those values horizontally
So, B113 is being pasted into B225, D113 is being pasted into C225, F113 into D225, so on and so forth until DB113 is pasted into V225
Repeat the above for Row 114 picking up where Row 113 left off - so, B114 is pasted into W225, D114 into X225, so on and so forth
Repeat again, for all rows 115 to 132 continuing to paste horizontally along Row 225
Below is what I was able to come up with after a lot of Google Research and a bunch of failed swings and misses. I am getting stuck on how to get the loop to move horizontally across the columns; turns out that A + 1 does not equal B... LOL
function testLoopTwo() {
var teamSheet = SpreadsheetApp.getActive();
for (var rowCounter = 113; rowCounter < 133; rowCounter = rowCounter + 1) {
teamSheet.getRange("B225").activate();
teamSheet.getCurrentCell().setValue(teamSheet.getRange('B' + rowCounter).getValues());
//This is were I am stuck... how do I move on to C225, D225, etc?
teamSheet.getCurrentCell().setValue(teamSheet.getRange('D' + rowCounter).getValues());
//now the active cell should move another column right, to column D
teamSheet.getCurrentCell().setValue(teamSheet.getRange('F' + rowCounter).getValues());
//active cell shifts right again, so now it would be column F
teamSheet.getCurrentCell().setValue(teamSheet.getRange('S' + rowCounter).getValues());
//So on and so forth...
}
};
Below was my original thought on how to approach this, but it doesn't work... The idea was a loop to run from Rows 113 to 132 then embedded within that loop another loop to capture each value from the looped role and paste it into Row 225, starting at Column B, one by one. I feel like this approach made more sense than my latest approach above, but I couldn't get either of them to work and not sure what it is that I am doing wrong... :(
function testLoop() {
var teamSheet = SpreadsheetApp.getActive();
for (var colCounter = 1; colCounter < 421; colCounter = colCounter + 1){
for (var rowCounter = 113; rowCounter < 133; rowCounter = rowCounter + 1) {
teamSheet.getRange(225,colCounter).setValue(teamSheet.getRange(rowCounter,1).getValue())
teamSheet.getRange(225,colCounter+1).setValue(teamSheet.getRange(rowCounter,3).getValue())
teamSheet.getRange(225,colCounter+2).setValue(teamSheet.getRange(rowCounter,5).getValue())
teamSheet.getRange(225,colCounter+3).setValue(teamSheet.getRange(rowCounter,18).getValue())
teamSheet.getRange(225,colCounter+4).setValue(teamSheet.getRange(rowCounter,29).getValue())
teamSheet.getRange(225,colCounter+5).setValue(teamSheet.getRange(rowCounter,38).getValue())
teamSheet.getRange(225,colCounter+6).setValue(teamSheet.getRange(rowCounter,47).getValue())
teamSheet.getRange(225,colCounter+7).setValue(teamSheet.getRange(rowCounter,53).getValue())
teamSheet.getRange(225,colCounter+8).setValue(teamSheet.getRange(rowCounter,59).getValue())
teamSheet.getRange(225,colCounter+9).setValue(teamSheet.getRange(rowCounter,62).getValue())
teamSheet.getRange(225,colCounter+10).setValue(teamSheet.getRange(rowCounter,65).getValue())
teamSheet.getRange(225,colCounter+11).setValue(teamSheet.getRange(rowCounter,68).getValue())
teamSheet.getRange(225,colCounter+12).setValue(teamSheet.getRange(rowCounter,71).getValue())
teamSheet.getRange(225,colCounter+13).setValue(teamSheet.getRange(rowCounter,74).getValue())
teamSheet.getRange(225,colCounter+14).setValue(teamSheet.getRange(rowCounter,77).getValue())
teamSheet.getRange(225,colCounter+15).setValue(teamSheet.getRange(rowCounter,80).getValue())
teamSheet.getRange(225,colCounter+16).setValue(teamSheet.getRange(rowCounter,85).getValue())
teamSheet.getRange(225,colCounter+17).setValue(teamSheet.getRange(rowCounter,90).getValue())
teamSheet.getRange(225,colCounter+18).setValue(teamSheet.getRange(rowCounter,95).getValue())
teamSheet.getRange(225,colCounter+19).setValue(teamSheet.getRange(rowCounter,100).getValue())
teamSheet.getRange(225,colCounter+20).setValue(teamSheet.getRange(rowCounter,105).getValue())
}
}
};
I really appreciate your help on this!! :D

Try this:
function copyvaluestoasingleline() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("teamSheet");
const vs = sh.getRange("A113:DB132").getValues();
let n = 2;//column counter starts at two and increments by arr[0].length
vs.forEach(([,b,,d,,f,,,,,,,,,,,,,s,,,,,,,,,,,ad,,,,,,,,,am,,,,,,,,,av,,,,,,bb,,,,,,bh,,,bk,,,bn,,,bq,,,bt,,,bw,,,bz,,,cc,,,,,ch,,,,,cm,,,,,cr,,,,,,,,,,db],i) => {
let arr = [[b,d,f,s,ad,am,av,bb,bh,bk,bn,bq,bw,bz,cc,ch,cm,cr,db]];//The assignment in the first parameter of the forEach method is something I learned from Tanaike.
sh.getRange(225,n,arr.length,arr[0].length).setValues(arr);
n+= arr[0].length;//add number of columns to column counter
});
Logger.log('First Line of Input: %s',JSON.stringify(vs[0]))
Logger.log('Output: %s', JSON.stringify(sh.getRange("225:225").getValues()[0]))
}
3:27:14 PM Notice Execution started
3:27:15 PM Info First Line of Input: ["A113","B113","C113","D113","E113","F113","G113","H113","I113","J113","K113","L113","M113","N113","O113","P113","Q113","R113","S113","T113","U113","V113","W113","X113","Y113","Z113","AA113","AB113","AC113","AD113","AE113","AF113","AG113","AH113","AI113","AJ113","AK113","AL113","AM113","AN113","AO113","AP113","AQ113","AR113","AS113","AT113","AU113","AV113","AW113","AX113","AY113","AZ113","BA113","BB113","BC113","BD113","BE113","BF113","BG113","BH113","BI113","BJ113","BK113","BL113","BM113","BN113","BO113","BP113","BQ113","BR113","BS113","BT113","BU113","BV113","BW113","BX113","BY113","BZ113","CA113","CB113","CC113","CD113","CE113","CF113","CG113","CH113","CI113","CJ113","CK113","CL113","CM113","CN113","CO113","CP113","CQ113","CR113","CS113","CT113","CU113","CV113","CW113","CX113","CY113","CZ113","DA113","DB113"]
3:27:15 PM Info Output: ["","B113","D113","F113","S113","AD113","AM113","AV113","BB113","BH113","BK113","BN113","BQ113","BW113","BZ113","CC113","CH113","CM113","CR113","DB113","B114","D114","F114","S114","AD114","AM114","AV114","BB114","BH114","BK114","BN114","BQ114","BW114","BZ114","CC114","CH114","CM114","CR114","DB114","B115","D115","F115","S115","AD115","AM115","AV115","BB115","BH115","BK115","BN115","BQ115","BW115","BZ115","CC115","CH115","CM115","CR115","DB115","B116","D116","F116","S116","AD116","AM116","AV116","BB116","BH116","BK116","BN116","BQ116","BW116","BZ116","CC116","CH116","CM116","CR116","DB116","B117","D117","F117","S117","AD117","AM117","AV117","BB117","BH117","BK117","BN117","BQ117","BW117","BZ117","CC117","CH117","CM117","CR117","DB117","B118","D118","F118","S118","AD118","AM118","AV118","BB118","BH118","BK118","BN118","BQ118","BW118","BZ118","CC118","CH118","CM118","CR118","DB118","B119","D119","F119","S119","AD119","AM119","AV119","BB119","BH119","BK119","BN119","BQ119","BW119","BZ119","CC119","CH119","CM119","CR119","DB119","B120","D120","F120","S120","AD120","AM120","AV120","BB120","BH120","BK120","BN120","BQ120","BW120","BZ120","CC120","CH120","CM120","CR120","DB120","B121","D121","F121","S121","AD121","AM121","AV121","BB121","BH121","BK121","BN121","BQ121","BW121","BZ121","CC121","CH121","CM121","CR121","DB121","B122","D122","F122","S122","AD122","AM122","AV122","BB122","BH122","BK122","BN122","BQ122","BW122","BZ122","CC122","CH122","CM122","CR122","DB122","B123","D123","F123","S123","AD123","AM123","AV123","BB123","BH123","BK123","BN123","BQ123","BW123","BZ123","CC123","CH123","CM123","CR123","DB123","B124","D124","F124","S124","AD124","AM124","AV124","BB124","BH124","BK124","BN124","BQ124","BW124","BZ124","CC124","CH124","CM124","CR124","DB124","B125","D125","F125","S125","AD125","AM125","AV125","BB125","BH125","BK125","BN125","BQ125","BW125","BZ125","CC125","CH125","CM125","CR125","DB125","B126","D126","F126","S126","AD126","AM126","AV126","BB126","BH126","BK126","BN126","BQ126","BW126","BZ126","CC126","CH126","CM126","CR126","DB126","B127","D127","F127","S127","AD127","AM127","AV127","BB127","BH127","BK127","BN127","BQ127","BW127","BZ127","CC127","CH127","CM127","CR127","DB127","B128","D128","F128","S128","AD128","AM128","AV128","BB128","BH128","BK128","BN128","BQ128","BW128","BZ128","CC128","CH128","CM128","CR128","DB128","B129","D129","F129","S129","AD129","AM129","AV129","BB129","BH129","BK129","BN129","BQ129","BW129","BZ129","CC129","CH129","CM129","CR129","DB129","B130","D130","F130","S130","AD130","AM130","AV130","BB130","BH130","BK130","BN130","BQ130","BW130","BZ130","CC130","CH130","CM130","CR130","DB130","B131","D131","F131","S131","AD131","AM131","AV131","BB131","BH131","BK131","BN131","BQ131","BW131","BZ131","CC131","CH131","CM131","CR131","DB131","B132","D132","F132","S132","AD132","AM132","AV132","BB132","BH132","BK132","BN132","BQ132","BW132","BZ132","CC132","CH132","CM132","CR132","DB132"]
3:27:16 PM Notice Execution completed
My data sheet is generated by putting the A1Notation of each cell so that you can tell what is being copied and where it came from.
array destructuring
It's a bit faster using Array.map and flattening it:
function copyThesevalues() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("teamSheet");
const vs = sh.getRange("A113:DB132").getValues();
let o = [vs.map(([,b,,d,,f,,,,,,,,,,,,,s,,,,,,,,,,,ad,,,,,,,,,am,,,,,,,,,av,,,,,,bb,,,,,,bh,,,bk,,,bn,,,bq,,,bt,,,bw,,,bz,,,cc,,,,,ch,,,,,cm,,,,,cr,,,,,,,,,,db],i) => [b,d,f,s,ad,am,av,bb,bh,bk,bn,bq,bw,bz,cc,ch,cm,cr,db]).flat()];
sh.getRange(225,2,o.length,o[0].length).setValues(o);
Logger.log('First Line of Input: %s',JSON.stringify(vs[0]))
Logger.log('Output: %s', JSON.stringify(sh.getRange("225:225").getValues()[0]))
}

Related

Better/faster way to pass 50+ values from one Google sheet to another

I'm brand new to App Script, so please forgive my ignorance.
The Google sheet I use to hold student data is so long and unwieldy (50+ columns) that I decided to create another sheet to act as a front-end for data entry. Through hours of tutorial videos + bumbling trial and error, I've come up with a working script that takes values from my data entry form-like sheet ('Students') and passes those values to the first empty row in my destination/container sheet ('Master').
I'm really pleased with how the script working - except for the fact that it is ridiculously slow. Based on what I've read, I think I'm making too many calls to the Sheets API, and I need to figure out how to pass all the values from 'Students' to 'Master' en masse rather than one-by-one, but I don't have the skills to do that, and I can't seem to find an example.
I'm sure there's a really simple, elegant solution. Can anyone help?
Here's a little piece of my code (hopefully it's enough to see the inefficient strategy I'm using):
function submitStudentData(){
var caseloadManager = SpreadsheetApp.getActiveSpreadsheet();
var enterStudents = caseloadManager.getSheetByName('Students');
var masterSheet = caseloadManager.getSheetByName('Master');
var clearFields = enterStudents.getRangeList(['C6:C18', 'C22', 'E6:E18','G6:G14','G20','I6:I14','K6:K16', 'M6:M18']);
var blankRow = masterSheet.getLastRow()+1;
masterSheet.getRange(blankRow,1).setValue(enterStudents.getRange("Z1").getValue()); //Concatenated Student Name
masterSheet.getRange(blankRow,3).setValue(enterStudents.getRange("C6").getValue()); //First Name
masterSheet.getRange(blankRow,2).setValue(enterStudents.getRange("C8").getValue()); //Last Name
masterSheet.getRange(blankRow,4).setValue(enterStudents.getRange("C10").getValue()); //Goes By
masterSheet.getRange(blankRow,6).setValue(enterStudents.getRange("E6").getValue()); //DOB
masterSheet.getRange(blankRow,7).setValue(enterStudents.getRange("E8").getValue()); //Grade
masterSheet.getRange(blankRow,5).setValue(enterStudents.getRange("E10").getValue()); //Student ID
masterSheet.getRange(blankRow,10).setValue(enterStudents.getRange("E14").getValue()); //Last FIE
masterSheet.getRange(blankRow,11).setValue(enterStudents.getRange("Z2").getValue()); //Calculated FIE Due Date
masterSheet.getRange(blankRow,8).setValue(enterStudents.getRange("E12").getValue()); //Last Annual Date[enter image description here][1]
masterSheet.getRange(blankRow,13).setValue(enterStudents.getRange("G6").getValue()); //PD
masterSheet.getRange(blankRow,14).setValue(enterStudents.getRange("G8").getValue()); //SD
masterSheet.getRange(blankRow,15).setValue(enterStudents.getRange("G10").getValue()); //TD
masterSheet.getRange(blankRow,16).setValue(enterStudents.getRange("G3").getValue()); //Concatenated Disabilities
masterSheet.getRange(blankRow,18).setValue(enterStudents.getRange("G12").getValue()); //Program Type
masterSheet.getRange(blankRow,12).setValue(enterStudents.getRange("G14").getValue()); //Evaluation Status
masterSheet.getRange(blankRow,20).setValue(enterStudents.getRange("I6").getValue()); //DYS
masterSheet.getRange(blankRow,21).setValue(enterStudents.getRange("I8").getValue()); //GT
masterSheet.getRange(blankRow,19).setValue(enterStudents.getRange("I10").getValue()); //EB
masterSheet.getRange(blankRow,24).setValue(enterStudents.getRange("I12").getValue()); //ESY
masterSheet.getRange(blankRow,22).setValue(enterStudents.getRange("I14").getValue()); //BIP
masterSheet.getRange(blankRow,29).setValue(enterStudents.getRange("K6").getValue()); //TR
masterSheet.getRange(blankRow,30).setValue(enterStudents.getRange("K8").getValue()); //OT
It goes on and one like this for 52 values before clearing all the fields in 'Students.' It works, but it takes well over a minute to run.
I'm trying to attach a picture of my 'Students' form-like sheet in case my description isn't clear.
Thanks so much for helping a humble special educator who knows not what she's doing. :)
Image of 'Students' form/sheet
Read best practices Even though your data isn't a contiguous range it is part of one so get the whole range with getValues() and use the appropriate indices to access the ones that you want. In the end if will be much faster. You may not want to use setValues to write the data because of other issues like messing up formulas. Avoid the use of setValue() and getValue() whenever possible
function submitStudentData() {
const ss = SpreadsheetApp.getActive();
const ssh = ss.getSheetByName('Students');
const msh = ss.getSheetByName('Master');
const nr = msh.getLastRow() + 1;
const vs = ssh.getRange(nr, 1, ssh.getLastRow(), ssh.getLastColumn()).getValues();
let oA1 = [[vs[0][25], vs[7][2], vs[5][2], vs[9][2], vs[9][4], vs[5][4], vs[7][4], vs[11][4]]];
msh.getRange(msh.getLastRow() + 1, 1, oA1.length, oA[0].length).setValues(oA1);//This line replaces all of the below lines
msh.getRange(nr, 1).setValue(vs[0][25]);//Concatenated Student Name
msh.getRange(nr, 2).setValue(vs[7][2]); //Last Name
msh.getRange(nr, 3).setValue(vs[5][2]); //First Name
msh.getRange(nr, 4).setValue(vs[9][2]); //Goes By
msh.getRange(nr, 5).setValue(vs[9][4]); //Student ID
msh.getRange(nr, 6).setValue(vs[5][4]); //DOB
msh.getRange(nr, 7).setValue(vs[7][4]); //Grade
msh.getRange(nr, 8).setValue(vs[11][4]); //Last Annual Date[enter image description here][1]
You could also do a similar thing by using formulas to map all of the data into a single line or column making it much easier to run the scripts.
Here is the working example. Just complete the mapping array as desrbied in the code. The runtime is below 1 second.
const mapping= [
// enter the array [ sourceRange, destinationRow ] for each cell you want to copy form Students to Master
['Z1',1],
['C6',3],
['C8',2],
['C10',4],
['E6',6]
// ... and so on
]
function submitStudentData() {
console.time('submitStudentData')
const caseloadManager = SpreadsheetApp.getActive();
const enterStudents = caseloadManager.getSheetByName('Students');
const masterSheet = caseloadManager.getSheetByName('Master');
const data = enterStudents.getDataRange().getValues()
const destRow = []
mapping.forEach((m,i)=>{
[rowi,coli] = rangeToRCindex(m[0])
const destRowIndex = m[1] - 1
destRow[destRowIndex] = data[rowi][coli]
})
masterSheet.appendRow(destRow)
console.timeEnd('submitStudentData')
}
function rangeToRCindex(range){
const match = range.match(/^([A-Z]+)(\d+)$/)
if (!match) {
throw new Error(`invalid range ${range}`)
}
const col = letterToColumn(match[1])
const row = match[2]
return [row-1,col-1]
}
function letterToColumn(columnLetters) {
let cl = columnLetters.toUpperCase()
let col = 0
for (let i = 0; i < cl.length; i++) {
col *= 26
col += cl.charCodeAt(i) - 65 + 1
}
return col
}
As Cooper said you want to avoid reading and writing to the sheet(s) as much as possible. (I had the same issue when I started with Google Script)
This means that you should read the whole range into a variable and then write your rows out to the master sheet.
Below is an example of what you could use to avoid the setValue() and getValue() slowness you are experiencing
function submitStudentData(){
var caseloadManager = SpreadsheetApp.getActiveSpreadsheet();
var enterStudents = caseloadManager.getSheetByName('Students');
var masterSheet = caseloadManager.getSheetByName('Master');
var clearFields = enterStudents.getRangeList(['C6:C18', 'C22', 'E6:E18','G6:G14','G20','I6:I14','K6:K16', 'M6:M18']);
var blankRow = masterSheet.getLastRow()+1; //You will not need this
//First we will all the data from the students sheet. This will make and array of arrays [[row],[row],[row]].
studentData = enterStudents.getRange(1,1,enterStudents.getLastRow(),enterStudents.getLastColumn()).getValues()
Logger.log(studentData)
//We are going to build an array of arrays of the data that we want to write back to the master sheet. We will start by creating our first array
writeData = []
//Then we loop through all the student data
for (var i = 0; i < studentData.length; i++) {
Logger.log(studentData[i][0])
//We are selecting data from each row to add to our array. in "studentData[i][0]" the [0] is the column number (remember we are starting with 0)
rowData = []
rowData.push(studentData[i][0])
rowData.push(studentData[i][2])
rowData.push(studentData[i][1])
//Then we send the full row to the first array we made
writeData.push(rowData)
}
Logger.log(writeData)
// Now to write out the data. Normally it would not be a good idea to loop a write like this but this as an atomic operation that google will automatically batch write to the sheet.
for (var i = 0; i < writeData.length; i++) {
masterSheet.appendRow(writeData[i])
}
}
Hope this helps get you started.

Google Sheets add a note by appending column based on matching value

The goal is to add a note or several notes over time to a google sheet based on the value chosen.
The script will allow for a button to be added in the sheet and run the script function to append to the next available column.
For example, the name Tim is chosen. A note is written, the button is pressed to run the function and it will add it to Column D (since it is the next available column for Tim).
Another example, the name Jeff is chosen. A note is written. Since there are no more columns the append column should automatically create a new column and allow for the note to be written in "Jeff's" row, which would be Column G
Not sure if this is at all possible but hope to get some suggestions or ideas.
A
B
C
D
E
F
Mark
B+
completed assignment partly but needed extra time since..
89433
other
Tim
A
checked
Jeff
C
n/a
assignment # 4
done
other
Steve
A
completed
get file
received
Elon
B-
three out of four
check email
Here's what I think you're trying to achieve -
And, this code should help you get started -
function main() {
const name = "Steve";
const note = "HELLO"
addNote(name, note);
}
function addNote(name, note) {
const ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const values = ss.getDataRange().getValues();
for (var rowIndex = 0; rowIndex < values.length; rowIndex++) {
let row = values[rowIndex];
let nameColumn = row[0];
if (nameColumn == name) {
let rowValues = ss.getRange(`${rowIndex+1}:${rowIndex+1}`).getValues()[0]
.filter(value => value !== "")
let newColIndex = rowValues.length;
ss.getRange(rowIndex+1, newColIndex+1).setValue(note);
}
}
}
Although, I'd highly recommend choosing something else as a unique identifier as opposed to a name because if there are 2 rows with the same name, then the code would actually add notes against both (incorrectly).

Script to set formula every 25th row increasing date by 1 day

I need to set a formula into ColM every 25th row, starting with M4. The formula I'm setting needs to increase the date by one day each time.
The script I've previously used successfully on a different sheet, doesn't work after I've adapted it for this new sheet. I'm sure it's something simple I've missed, buy my dyslexia makes it very hard to see what.
I want to set formulas like this...
=FILTER(ZapUPDATE!T2:Z, ZapUPDATE!Q2:Q=date(2016,10,1), ZapUPDATE!R2:R="SUP") // Into M4
=FILTER(ZapUPDATE!T2:Z, ZapUPDATE!Q2:Q=date(2016,10,1)+1, ZapUPDATE!R2:R="SUP") // Into M29
=FILTER(ZapUPDATE!T2:Z, ZapUPDATE!Q2:Q=date(2016,10,1)+2, ZapUPDATE!R2:R="SUP") // Into M54
Etc and every 25th row down to M754. Here's the script...
function setFormulas() {
var sheet = SpreadsheetApp.getActiveSheet();
for (var k = 0; k < 775; k++) {
sheet.getRange(4 + 25*k, 13).setFormula('=FILTER(ZapUPDATE!T2:Z, ZapUPDATE!Q2:Q=date(2016,10,1)+' + k + ', ZapUPDATE!R2:R="SUP")');
}
}
Link to the sheet is below. Any help gratefully received, thanks.
Copy of sheet for Stack Overflow
Try this:
function setFormulas() {
var sheet = SpreadsheetApp.getActiveSpreadsheetSheet().getSheetByName('SUP10');
var i = 0;
for (var k = 4; k < 775; k = k + 25) {
sheet.getRange(k, 13).setFormula('=FILTER(ZapUPDATE!T2:Z, ZapUPDATE!Q2:Q=date(2016,10,1)+' + i + ', ZapUPDATE!R2:R="SUP")');
i++;
}
}
If you do Logger.log(SpreadsheetApp.getActiveSheet().getName()) you'll see that GAS considers another sheet (AVL) active — who knows why but in your case, I think, it can easily be overcome by using getSheetByName().
Another thing is that iterating through every k value until it reaches 774 is really slow (and it creates 7k additional rows in your spreadsheet) — it hits the time limit and still doesn't finish the loop. Changing the loop's settings to (var k = 4; k < 775; k = k + 25) makes it run in 0.075 seconds.
And the last thing: you don't need to create a separate project for every single function you have :) You may write them all in one file or create a bunch of new .gs files inside a project — it'll be more convenient to work in such a fashion.
Hope it helps.

Guild Roster Sheet - Setting value row-by-row in one column

I'm just starting to learn how to code with Google Apps Script. Barely.
I have a guild roster sheet (toon names in range B2:B and Realm (We also add prospects to this list) in range C2:C.
In Column D, I would like to set the value of each cell row-by-row to a function that uses the data in B and C (i.e. "=wowi(B2,C2)")
My intent is to just be able to add as many names as I want in Column B, and run the function manually to update the values for each in column D, and sleeps for a second between each (so as to not abuse the UrlFetch in the "=wowi(toonName, realmName)" function when the names column gets a little long)
Sadly, this is about as far as I got before I gave up:
function rosterUpdate() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var destCell = sheet.getRange("D2");
for (x = 0, x //(less than or equal to)// sheet.getRange("B2:B").getNumRows(), x ++) {
if (x != 0 {
NOCLUEWHATTODO-IDKWHYIEVENTRY;
sleep(1000);
}
}
}
I know I need to use a for, but not quite sure on how to go about reading the value and modifying the D cell's value. I'm quite possibly overthinking this, as it's currently 5:30 in the morning and I'm running on about 2 hours of sleep, but would really appreciate the help.
Thanks in advance. Sorry if I'm bad. :[
Hopefully this answers your question in terms of getting and setting values (you should check the documentation: https://developers.google.com/apps-script/reference/spreadsheet/range) and how to sleep for one second between function calls, however it's pretty inefficient as it will iterate over the whole list each time.
Please be aware this is written off the top of my head very quickly so you may need to play with the i values to get it working properly, and there is probably a much, much cleaner way to do it!
var listLength = sheet.getLastRow() - 1;
for(var i = 0; i < listLength; i++){
var toonName = sheet.getRange("B" + (i+2)).getValue();
var realmName = sheet.getRange("C" + (i+2)).getValue();
sheet.getRange("D" + (i+2)).setValue(yourFunction(toonName, realmName));
Utilities.sleep(1000);
}

Format row color based on cell value

I am trying to adapt the example script from this previous, related question. For rows where the cell value in column K is zero, I want to make the row yellow.
Here is my current adapted code:
function colorAll() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 3;
var endRow = sheet.getLastRow();
for (var r = startRow; r <= endRow; r++) {
colorRow(r);
}
}
function colorRow(r){
var sheet = SpreadsheetApp.getActiveSheet();
var c = sheet.getLastColumn();
var dataRange = sheet.getRange(r, 1, 1, c);
var data = dataRange.getValue();
var row = data[0];
if(row[0] === "0"){
dataRange.setBackground("white");
}else{
dataRange.setBackground("yellow");
}
SpreadsheetApp.flush();
}
function onEdit(event)
{
var r = event.source.getActiveRange().getRowIndex();
if (r >= 3) {
colorRow(r);
}
}
function onOpen(){
colorAll();
}
My problem is, I can't figure out how to reference column K. In the linked answer above, the script's creator claims, "[h]ere is a Google Apps Script example of changing the background color of an entire row based on the value in column A." First, and most importantly, I can't figure out where he's referencing column A. I thought changing "var dataRange = sheet.getRange(r, 1, 1, c);" to "var dataRange = sheet.getRange(r, 11, 1, c);" would do it, but that just added 10 blank columns to the end of my sheet, and then the script crashed. I do not understand why.
Secondly, but more as an aside, his claim that the script affects entire rows is inaccurate, as his original "var dataRange = sheet.getRange(r, 1, 1, 3);" only colored the first three columns - which is why I added "var c" and changed "3" to "c".
Furthermore, when I play/debug the script, or run "onEdit" from the spreadsheet script manager, I get "TypeError: Cannot read property "source" from undefined." I can see that "source" is undefined - I had mistakenly assumed it was a Method at first - but I'm not sure how to fix this issue either.
Lastly, column K will not always be the reference column, as I mean to add more columns to the left of it. I assume I'll have to update the script every time I add columns, but there is a column heading in row 2 that will never change, so if someone can help me devise a bit of code that will look for a specific string in row 2, then get that column reference for use in function colorRow(), I would appreciate it.
I can't tell if this script is structured efficiently, but ideally, I want my spreadsheet to be reactive - I don't want to have to rerun this script after editing a driving cell, or upon opening; it reads like it's supposed to do that (were it not buggy), but this is my first attempt at using Google Apps Script, and I don't feel certain of anything.
I'm not great with scripting, but I took a programming fundamentals/Python class in grad school back in 2006, and spent 4 years working with Excel & Access shortly after that, often creating and adapting Macros. I can't really design from scratch, but I understand the basic principles and concepts, even if I can't translate everything (e.g., I don't understand what the "++" means in the third argument in the "for" statement I'm using: "for (var r = startRow; r <= endRow; r++)." I think I'm allegorically equivalent to a literate Spanish speaker trying to read Italian.
Help, and educational explanations/examples, will be much appreciated. Thank you kindly for reading/skimming/skipping to this sentence.
Rather than rewriting the code which you have already got some help with, I will try to give you explanations to the specific questions you asked. I see that you have some of the answers already but I am putting thing in completely as it helps understanding.
My problem is, I can't figure out how to reference column K.
Column A = 1, B = 2,... K = 10.
I can't figure out where he's referencing column A.
You were close when you altered the .getRange. .getRange does different things depending on how many arguments are in the (). With 4 arguments it is getRange(row, column, numRows, numColumns).
sheet.getRange(r, 1, 1, c) // the first '1' references column A
starts at row(r) which is initially row(3), and column(1). So this is cell(A3). The range extends for 1 row and (c) columns. As c = sheet.getLastColumn(), this means you have taken the range to be 1 row and all the columns.
When you changed this to
var dataRange = sheet.getRange(r, 11, 1, c) // the '11' references column L
You have got a range starting at row(3) column(L) as 11 = L. This runs to row(3) column(getLastColumn()).
This is going to do weird things if you have gone out of range.
You may have pushed it in to an infinite for loop which would cause the script to crash
Secondly, but more as an aside, his claim that the script affects entire rows is inaccurate, as his original "var dataRange = sheet.getRange(r, 1, 1, 3);"
only colored the first three columns - which is why I added "var c" and changed "3" to "c".
You are correct. The (3) says that the range extend for 3 columns.
"TypeError: Cannot read property "source" from undefined."
What is happening here is not intuitively clear. You can't run the function onEdit(event) from the spreadsheet script manager because it is expecting an "event".
onEdit is a special google trigger that runs whenever any edits the spreadsheet.
it is passed the (event) that activated it and
event.source. refers to the sheet where the event happened so
var r = event.source.getActiveRange().getRowIndex(); gets the row number where the edit happened, which is the row that is going to have its color changed.
If you run this in the manager there is no event for it to read, hence undefined. You can't debug it either for the same reasons.
Lastly, column K will not always be the reference column, as I mean to
add more columns to the left of it. I assume I'll have to update the
script every time I add columns, but there is a column heading in row
2 that will never change, so if someone can help me devise a bit of
code that will look for a specific string in row 2, then get that
column reference for use in function colorRow(), I would appreciate
it.
Before I give you code help her, I have an alternative suggestion because you are also talking about efficiency and it is often faster to run functions in the spreadsheet than using scripts. You could try having column A as an index columns where ColumnA(Row#) = ColumnK(Row#). If you put the following into cell(A1), ColumnA will be an exact match of Column K.
=ArrayFormula(K:K)
Even better, if you add/remove Columns between A and K, the formula will change its reference without you doing anything. Now just hide columnA and your sheet is back to its originator appearance.
Here is your code help, utilizing some of your own code.
function findSearchColumn () {
var colNo; // This is what we are looking for.
var sheet = SpreadsheetApp.getActiveSheet();
var c = sheet.getLastColumn();
// gets the values form the 2nd row in array format
var values = sheet.getRange(2, 1, 1, c).getValues();
// Returns a two-dimensional array of values, indexed by row, then by column.
// we are going to search through values[0][col] as there is only one row
for (var col = 0; col < data[0].length; col++) { // data[0].length should = c
if (data[0][col] == value) {
colNo = col;
break; // we don't need to do any more here.
}
}
return(colNo);
}
If break gives you a problem just delete it and let the look complete or replace it with col = data[0].length;
I can't tell if this script is structured efficiently, but ideally, I
want my spreadsheet to be reactive - I don't want to have to rerun
this script after editing a driving cell, or upon opening; it reads
like it's supposed to do that (were it not buggy), but this is my
first attempt at using Google Apps Script, and I don't feel certain of
anything.
It is ok, the fine tuning of efficiency depends on the spreadsheet. function onEdit(event)
is going to run every time the sheet is edited, there is nothing you can do about that. However the first thing it should do is check that a relevant range has been edited.
The line if (r >= 3) seems to be doing that. You can make this as specific as you need.
My suggestion on a hidden index column was aimed a efficiency as well as being much easier to implement.
I'm not great with scripting,
You are doing ok but could do with some background reading, just look up things like for loops. Unfortunate Python is grammatically different from many other languages. A for loop in google script is the same as VBA, C, JAVA, and many more. So reading about these basic operations is actually teaching you about many languages.
I don't understand what the "++" means in the third argument in the "for" statement
It is why the language C++ gets its name, as a programmer joke.
r++ is the same as saying r = r+1
r-- means r = r-1
r+2 means r = r+2
So
for (var r = startRow; r <= endRow; r++)
means r begins as startRow, which in this case is 3.
the loop will run until r <= endRow, which in this case is sheet.getLastRow()
after each time the loop runs r increments by 1, so if endRow == 10, the loop will run from r = 3 to r = 10 => 8 times
1.The onEdit is a special function that is automatically called when you edit the spreadsheet. If you run it manually, the required arguments won't be available to it.
2.To change the colour of the entire row when column K is 0, you have to make simple modifications to the script . See below
function colorRow(r){
var sheet = SpreadsheetApp.getActiveSheet();
var c = sheet.getLastColumn();
var dataRange = sheet.getRange(r, 1, 1, c);
var data = dataRange.getValues();
if(data[0][10].toString() == "0"){ //Important because based on the formatting in the spreadsheet, this can be a String or an integer
dataRange.setBackground("white");
}else{
dataRange.setBackground("yellow");
}
SpreadsheetApp.flush();
}