Guild Roster Sheet - Setting value row-by-row in one column - google-apps-script

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);
}

Related

Using for and if loops in Google Apps Script

Dear programming Community,
at first I need to state, that I am not quite experienced in VBA and programming in general.
What is my problem? I have created a topic list in google sheets in order to collect topics for our monthly meeting among members in a little dance club. That list has a few columns (A: date of creation of topic; B: topic; C: Name of creator; ...). Since it is hard to force all the people to use the same format for the date (column A; some use the year, others not, ...), I decided to lock the entire column A (read-only) and put a formular there in all cells that looks in the adjacent cell in column B and sets the current date, if someone types in a new topic (=if(B2="";"";Now()). Here the problem is, that google sheets (and excel) does then always update the date, when you open the file a few days later again. I tried to overcome this problem by using a circular reference, but that doesn't work either. So now I am thinking of creating a little function (macro) that gets triggered when the file is closed.
Every cell in Column B (Topic) in the range from row 2 to 1000 (row 1 is headline) shall be checked if someone created a new topic (whether or not its empty). If it is not empty, the Date in the adjacent cell (Column A) shall be copied and reinserted just as the value (to get rid of the formular in that cell). Since it also can happen, that someone has created a topic, but a few days later decides to delete it again, in that case the formular for the date shall be inserted again. I thought to solve this with an If-Then-Else loop (If B is not empty, then copy/paste A, else insert formula in A) in a For loop (checking rows 1 - 1000). This is what I have so far, but unfortunately does not work. Could someone help me out here?
Thanks in advance and best regards,
Harry
function NeuerTest () {
var ss=SpreadsheetApp.getActive();
var s=ss.getSheetByName('Themenspeicher');
var thema = s.getCell(i,2);
var datum = s.getCell(i,1);
for (i=2;i<=100;i++) {
if(thema.isBlank){
}
else {
datum.copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
}}
}
The suggested approach is to limit the calls to the Spreadsheet API, therefore instead of getting every cell, get all the data at once.
// this gets all the data in the Sheet
const allRows = s.getDataRange().getValues()
// here we will store what is written back into the sheet
const output = []
// now go through each row
allRows.forEach( (row, ind) => {
const currentRowNumber = ind+1
// check if column b is empty
if( !row[1] || row[1]= "" ){
// it is, therefore add a row with a formula
output.push( ["=YOUR_FORMULA_HERE"] )
} else {
// keep the existing value
output.push( [row[0]] )
}
})
Basically it could be something like this:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Themenspeicher');
var range = sheet.getRange('A2:B1000');
var data = range.getValues(); // <---- or: range.getDisplayValues();
for (let row in data) {
var formula = '=if(B' + (+row+2) + '="";"";Now())';
if (data[row][1] == '') data[row][0] = formula;
}
range.setValues(data);
}
But actual answer depends on what exactly you have, how your formula looks like, etc. It would be better if you show a sample of your sheet (a couple of screenshots would be enough) 'before the script' and 'after the script'.

How to apply get range in combination with i++

Today I have come with something that I have been trying for quite some time now, but due to time issues, I do finally have decided to seek help.
For starters, I have this table:
Name
Score year 1
Score year 2
Now, each name will have it´s own tablepages for various reasons not relevant for this, so this forces me to be a bit inventive.
I have already figured out how to automatically enter the sheets with this function:
function sheetnames() {
var out = new Array()
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (var i=6; i<sheets.length ; i++) out.push( [ sheets[i].getName() ] )
return out
}
Now, What I want to know if, and if so how, one can use this to get the scores for each year once the sheet is made.
For sake of clarity, I will outline it this way.
I want the function to grab a bunch of values from any sheets that would be included in the function above automatically.
if I make the sheet Alex and 100+ and enter the values on these sheets, they should appear in this table.
Apologies if something is written erroneous, I am not an native English speaker.
Please feel free to ask questions for clarification.
So you have n Sheets, all with a name and they all have a score for k years next to each other in the first row?
Why are you starting at the 5th Sheet?
I never had anything to do with google services and what not, but after a look in their docs, I found a function that gets the content of an area:
getSheetValues(startRow, startColumn, numRows, numColumns)
It returns a two dimensional array of the Values. You should test if the columns are the first index or the second. The following code assumes an array of rows (column are the second index). Take everything with a chunk of salt, I know Javascript for like 2 weeks and just googled a bunch of stuff.
function sheetnames() {
var out = new Array();
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (var i=6; i<sheets.length ; i++) { //if you want to start at the 5th sheet otherwise i=0
var columns = sheets[i].getLastColumn(); // gets the last column with content (the length)
var content = sheets[i].getSheetValues(0, 0, 1, columns)[0]; // get one row
var newRow = content.unshift(sheets[i].getName());
out.push(newRow);
}
return out
}

Google App Script- Check value of cell against col A, then edit corresponding col B

so i have the following code that works perfectly for our purposes, it took me a long time to put together, and I know that is a horribly inefficient solution to my problem. In the interest of space, ive only linked the first if statement. This is repeated 14 more times with the other outcomes.
The problem is that we are going to have over 100 items soon, and while this works for 5, im afraid it is going to be far too much work and run very slowly to replicate.
function onEdit(event) {
var invinput = ss.getRange("L16").getValue();
var iteminput = ss.getRange("J16").getValue();
var item1 = ss.getRange("A2").getValue();
var item2 = ss.getRange("A3").getValue();
var item3 = ss.getRange("A4").getValue();
var item4 = ss.getRange("A5").getValue();
var item5 = ss.getRange("A6").getValue();
var inv1 = ss.getRange("B2").getValue();
var inv2 = ss.getRange("B3").getValue();
var inv3 = ss.getRange("B4").getValue();
var inv4 = ss.getRange("B5").getValue();
var inv5 = ss.getRange("B6").getValue();
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("invinputlog");
var itemList = ss.getRange(1,0,1000)
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
if(s.getName() == "IMI" && r.getColumn() == 14 && r.getValue() == "add" && (iteminput == item1)) {
ss.getRange("B2").setValue(invinput+inv1);
s.getRange(row, 1, 1, numColumns).copyTo(target);
ss.getRange("restockInput").clear();}
I've found a lot of solutions that are kind of helpful,some "for" statements that i tested, but I couldn't quite figure it out.
My question is that instead of having to compare "item1 == itemInput" for every item we have, I would like it to be something like
if iteminput == (anything in column A) {
ss.getRange("Cell B next to Cell A that matches iteminput").setValue
(invinput+"cell B that is next to cell A that matches iteminput");}
Thank you in advance if anyone has any ideas, I really enjoy these projects, so even getting me on the right track would be great. I am happy to figure it out myself, I'm just a little stuck.
If you are going to have 100 items, then it will be slow, as you say. What is slow is the access to the spreadsheet itself (what is fastest is the in-memory manipulation of every other 'command' which does not access the spreadsheet). So, for example, if you write the script to do ss.getRange("A3").getValue() one hundred times then this is going to be slow.
If I were to approach your scripting problem I would fetch all the values which are on the spreadsheet just the once (per onEdit of course) using something like var values = sheet.getDataRange().getValues();
This will create a 2Dimensional array, of the nature
[
[A1, B1, C1, ... L1 ],
[A2, B2, C2, ... L2 ],
...
...
[A100, B100, C100, ... L100]
]
You have the 'outer' array which holds multiple 'inner' arrays, each representing a row of the sheet, from the first† row up to the last row where there is any cell containing data and as wide as the first† column up to last column with any cell with data (every blank cell in between will be in the array too, which will be represented by empty string '' )
† .getDataRange() defines the range, from first to last-with-data, as above. You can use other range definitions too, e.g. getRange("A1:L100") ; nevertheless, the 2D nature of this range, and thus .getValues() will still be there.
Then you will have to manipulate this 2D array within google apps script. You can google search for loop in javascript OR google apps script to show you the code, which will be like:
for (var i = 0; i<values.length; i ++){
// YOUR CODE to manipulate this for..loop
}
The i variable within this for..loop increments each loop through YOUR CODE; thus it is the analog to each row (except it starts at zero i.e. one less than the row which starts at 1). So you will use i to reference each row in values. And you will have, as the second dimension, 0 as ColA, 1 as ColB etc to 11 as ColL
e.g. in the fourth loop, i will be 3, so values[i][1] will reference cell B4.
In this manner, withing one or a few line(s) of code within a for..loop, using i, you can compare 100 (or many more) rows.
You would do your if comparison as part of YOUR CODE within the for..loop.
When your if comparison finds a match, you should stop the loop running using the break statement. This will fix the var i, which you can then use within more script below the for..loop to write back to the spreadsheet. For this, I would consider using google apps script range.offset
(Note: your actual use case may be more complicated than I have indicated above, particularly in the code that you would need to write to achieve your required comparison (the if within the loop). However, your solution will almost certainly require the getValues() call to the spreadsheet so as to not make 100 getValue() calls, and thus you will have to work with 2D arrays.)

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.

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();
}