Loop through a range of cells Google Sheets - google-apps-script

First of all, I'm new to coding - so the script that I have I combined it from a couple different scripts and it seems to work, but I would like to simplify it.
I have such a script:
function count() {
var Month = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Private').getRange('A2').getValues();
var PCC = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Private').getRange('B1').getValues();
var PCCQ = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(Month).getRange(PCC);
var PCCS = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(Month).getRange('B4').getValue();
var PhoneCC = GmailApp.search(PCCS);
PCCQ.setValue(PhoneCC.length);
}
What it does:
it takes the name of the sheet where I want the results from cell A2 of sheet Private.
it takes the cell where I want the result from cell B1 of sheet Private.
we get the range by combining the sheet name in 1 and cell from 2
it takes the query that we need from the sheet that get from 1 and cell B4
makes a search in Gmail with the query from 4
sets the value of the cell from point 3 as the length of the search from point 5
However I have a range of cells for point 2 that I need this to work through, let's say B1:B70.
Is there a way to adjust this script to do this?
Will be very thankful.

I'd make the following changes. Keep going.
var Month = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Private').getRange('A2').getValue();
var PCC = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Private').getRange('B1').getValue();
var PCCQ = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Month').getRange(PCC);//The range is not sensible unless it is a named range but the getRange does not work with named ranges.
var PCCS = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Month').getRange('B4').getValue();
var PhoneCC = GmailApp.search(PCCS); //how do you plan to display your results.

Related

Using cell input into function and ovewriting result into result in same cell in Google Sheet

In Google Sheet, I would like to take the input of a cell, make a calculation, and display the result in the same cell in a different format. This would likely always be a percentage value that I would use conditional formatting to color the cell to provide a 'dashboard' view of statistics.
Example would be usage statistics for a month.
Assets
Records
limit
50
1000
November
29
295
Assets
Records
limit
50
1000
November
58%
30%
I found a Quora post detailing how to create your own scripts, so I believe I have the baseline of taking and modifying content of a cell:
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var cell = ss.getActiveSelection();
var cell_input = cell.getValue();
var cell_address = cell.getA1Notation()
var cell2 = ss.getRange(cell_address);
cell2.setFormula('=2*'+cell_input)
}
In this example, I'm unsure how to reference the cell it should be dividing against.
Here's a general example that should give you an idea of how to manipulate the cells, formats and values in Sheets:
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var cell = ss.getActiveSelection();
var cell_input = cell.getValue();
var divisor = cell.offset(-1, 0)
if (e.range.getA1Notation()=="B3" || "C3"){
cell.setNumberFormat("#").setValue(Math.ceil((cell_input / divisor.getValue()) * 100) + "%")
}
}
If you create a sheet that looks like this you will get the behavior you were looking for in B3 and C3:
How it works:
Add the e parameter to the onEdit(e) trigger so you can use e.range to read the range that called the function.
To get the cell you're dividing against you need to either know its exact range or its relative position to the cell that called it. Since in your sample the percentage cell is below the divisor, you can just offset() it by -1, which returns the cell above it. If the relative position changes you'll need to take this into account to modify the offset but it should work in general if your table has a consistent structure.
You'll need to use an if to fire the trigger only in a specific range, otherwise the entire sheet will be affected. You do this by comparing the caller e.range to your desired range. In this example for the sake of simplicity I just had two cells so I just compared the individual B3 and C3 cells to e.range.getA1Notation(), but if you want a bigger range you'll probably want to use a more advanced technique like the ones in this question
To get around the format problem described in TheWizEd's comment I'm forcing the cell to a text value using setNumberFormat("#"). This way if you enter a value a second time it will read it as a number rather than a percent. I tested using the value elsewhere in the sheet and it still works as a percentage when needed, but watch out for unintended behavior.
Sources:
Simple triggers
Range object documentation
When working with triggers, take advantage of the event object.
Replace
function onEdit(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var cell = ss.getActiveSelection();
var cell_input = cell.getValue();
by
function onEdit(e){
var cell_input = e.value ?? 0;
Notes:
SpreasheetApp.Spreadsheet.getActiveSeletion() returns the first range that belongs to the active selection, this might cause problems because the active seleccion could include multiple cells, and the current cell could be any cell in the selection even one cell that is not part of the returned range.
SpreadsheetApp.Range.getValue() returns the value of the top left cell in the range. A user could select multiple cells and use the tab key to change the current cell, so under certain circunstances the value obtained this way might not be the value of the edited cell.
e.value ?? 0 is used to get 0 as the default value, i.e., when a cell is cleared.
As onEdit is triggered when any cell is edited your script should include a condition to only change the corresponding cells.
function onEdit(e){
const cell_input = e.value ?? 0;
if(e.range.rowStart === 3 && [2,3].includes(e.range.columnStart)){
const newValue = (100 * cell_input / e.range.offset(-1,0).getValue()).toFixed() + '%';
e.range.setValue(newValue);
}
}
The above uses
SpreadsheetApp.Range.offset to get the cell above of the edited cell.
Writes a the percentage as string, taking advange of the Google Sheets automatic data type assignation.
References
https://developers.google.com/apps-script/guides/triggers
https://developers.google.com/apps-script/guides/triggers/events

Google Sheets - sum a cell from a dynamic list of sheets with vlookup

I have a Google Sheet that I update with a new sheet every two days or so and title the name of the sheet the date that it is created. For organizational reasons, I do not want to combine all of the information into one sheet and parse it elsewhere, even though it would make this so much simpler.
I'd like to provide a named range of different sheet names, and have a script, function, etc go to each sheet, grab the contents of a cell and sum them. Bonus points if vlookup can be integrated to ensure that the right cell is being referenced.
So, for example:
named range = '7/3/21','7/1/21'
=sum('7/3/21'!B12,'7/1/21'!B12)
This is simple enough, but I don't want to alter this every time a new sheet is added to reference the new sheet.
In my mind it would look something like:
=sum(vlookup(A1(INDIRECT(NamedRange1&"!c1"))
where:
A1 = the name I want to lookup on the other sheets
NamedRange1 = range of cells that correspond to all of the different sheets
C1 = the cell that I want to add across all the other sheets.
function sumacellfromlistofsheets(obj) {
const listofsheets = obj.list;
const ss = SpreadsheetApp.getActive();
const shts = ss.getSheets().filter(sh => ~obj.list.indexOf(sh.getName()));
let sum = 0;
shts.forEach( sh => {
sum += sh.getRange(obj.cellinA1Notation).getValue();
});
}

Google Sheets, fill data from form sheet into specific cells on other sheets

I'm new to coding in general (other than experience with MATLAB which I don't think counts) but I'm starting with trying to code in the Google Sheets API for some advanced functionality.
The code I'm trying to write is for a spreadsheet that I track all my car expenses on. I have it doing a bunch of number crunching for MPG currently, but don't want to have to find the row and column each time to enter the date. Instead I'd like one sheet that is clean and simple that I enter the variables on (Miles driven, gallons pumped, price per gallon, estimated MPG from the car computer) and it fills the other sheets in the document with that information automatically when I hit save, then clears the form so I can do it again next time.
Here is what I have so far.
function submitData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formSS = ss.getSheetByName('Fill-Up'); //Form Sheet
var datasheet1 = ss.getSheetByName('Mileage Stats'); //Data Sheet 1
var datasheet2 = ss.getSheetByName('Costs and Savings'); //Data Sheet 2
//Input Values 1
var values1 = [[formSS.getRange('B3').getValue(),
formSS.getRange('B6').getValue()]];
datasheet1.getRange(datasheet1.getLastRow()+1, 2, 1, 2).setValues(values1);
//Input Values 2
var values2 = [[formSS.getRange('B4').getValue(),
formSS.getRange('B5').getValue()]];
datasheet2.getRange(datasheet2.getLastRow()+1, 2, 1, 2).setValues(values2);
}
It works with the exception of two issues that I haven't been able to solve yet.
1) It writes the information to a new row at the bottom of the page, not the next empty row.
2) It isn't writing the information in B6 to the correct cell. I want B3 written to column B in "Mileage Stats" sheet, and it is, B4 is written to column B in "Costs and Savings" as I want, and B5 is written to column C in "Costs and Savings" as I want, but B6 is written to column C in "Mileage Stats" but I want it in column G, and can't figure out how to change that with my current code, or any other code I can find.
Any help anyone can give would be awesome!
Are you using any ArrayFormulas on the sheets that you are trying to use getLastRow()? If so, ArrayFormula will add data into the cells for the whole range.
You may want to look at using the Sheet.appendRow() method like below and remove all the empty rows on your data sheets. appendRow() will add a new row at the bottom of the sheet with that data passed to the method.
datasheet1.appendRow(values1);
datasheet2.appendRow(values2);
Cells with functions are counted as data cells, that's why you're inserting the rows at the bottom of the sheet. To solve this, you can use getNextDataCell() method to a column range without functions (e.g. a cell in column B), which returns the last cell with data for a given direction. I tested the below code and worked for inserting the data in the last row with data in column B:
function submitData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formSS = ss.getSheetByName('Fill-Up'); //Form Sheet
var datasheet1 = ss.getSheetByName('Mileage Stats'); //Data Sheet 1
var datasheet2 = ss.getSheetByName('Costs and Savings'); //Data Sheet 2
//Input Values 1
var values1 = [[formSS.getRange('B3').getValue(),
formSS.getRange('B6').getValue()]];
var lastRowInB = datasheet1.getRange("B6").getNextDataCell(SpreadsheetApp.Direction.DOWN).getLastRow();
datasheet1.getRange(lastRowInB+1, 2, 1, 2).setValues(values1);
//Input Values 2
var values2 = [[formSS.getRange('B4').getValue(),
formSS.getRange('B5').getValue()]];
lastRowInB = datasheet1.getRange("B7").getNextDataCell(SpreadsheetApp.Direction.DOWN).getLastRow();
datasheet2.getRange(lastRowInB+1, 2, 1, 2).setValues(values2);
}

Copying Data Sheet1 to Sheet2 so you can sort & edit both sheets (google apps script?)

I am working in goggle sheets and think I need to use a google apps script to do what I want, but I am a psychologist at a non-profit University hospital trying to do some good and not a programmer (which probably shows) and I am desperately in need of help. I am trying to set up a series of spreadsheets to track participation in workshops for our treatment method.
1) I have a sheet “Participant_Registration” where basic information is entered
2) I want to transfer information from only the first four columns (A:D) of “Participant_Registration” to a second sheet “Learning_Sessions_Attendance”
3) I am also transferring the same information to a third sheet 'Consultation1_Attendance' – but I need to first filter and select only those people assigned to that group.
Here is a link to a copy of my spreadsheet.
https://docs.google.com/spreadsheets/d/17d0bT4LZOx5cyjSUHPRFgEZTz4y1yEL_tO3gtSJ4UJ8/edit?usp=sharing
More generically this is what I am trying to do. Is this possible in google app scripts? It seems it should be.
1) I have original data in sheet1
2) I want the first four columns (A:D) to transfer to sheet2 (it is fine if I need a trigger variable)
3) I want them to transfer in such a way that if you sort either sheet, the data are still fine (still linked to the right line).
4) Ideally if there is a change to the data in the source sheet (Sheet1) the same change will be made in Sheet2.
5) Ideally this would all happen automatically without human intervention through a script.
Any ideas?? I so need your help. I have been all over the forum, git hub, and done a ton of searches and tried following a lot of examples I saw but nothing works. I really need help.
Here are my sample scripts each with a problem:
//The following code copies a range from sheet1 to sheet2 as I wanted. A problem occurs if after if we copy the data from sheet1 we add data to other columns on sheet2. Later if we sort on some variable (which people are bound to do) if the function is deployed again it will overwrite data meaning the data from sheet1 are not connected to the right individual on sheet2
function CopyRange() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Participant_Registration");
var range = sheet.getRange("A14:D");
var values = range.getValues();
var target = ss.getSheetByName("Learning_Sessions_Attendance");
var target_range = target.getRange("A10:D");
range.copyTo(target_range);
}
So I tried again. This time I tried to just copy the last edited row from sheet1 to sheet2. This function does not appear to work for me.
function CopyRow2() {
// Get Spreadsheets
var source = SpreadsheetApp.openById("1egn6pnRd6mKMGuQxX_jtgwYDtkuMUv2QJItLdh7aIEs");
var target = SpreadsheetApp.openById("1egn6pnRd6mKMGuQxX_jtgwYDtkuMUv2QJItLdh7aIEs");
// Set Sheets
var source_sheet = source.getSheetByName("Participant_Registration");
var target_sheet = target.getSheetByName("Learning_Sessions_Attendance");
var rowIdx = source_sheet.getActiveRange().getRowIndex();
var rowValues = source_sheet.getRange(rowIdx,1,1,source_sheet.getLastRow()).getValues();
Logger.log(rowValues);
var destValues = [];
destValues.push(rowValues[0][0]);// copy data from col A to col A
destValues.push(rowValues[0][1]);//copy data from col B to col B
destValues.push(rowValues[0][2]);//copy data from col C to col C
destValues.push(rowValues[0][3]);//copy data from col D to col D
var dest=source.getSheets()[4];
dest.getRange(dest.getLastRow()+1,1,1,destValues.length).setValues([destValues]);//update destination sheet with selected values in the right order, the brackets are there to build the 2D array needed to write to a range
}
So I tried again and again. I have lots of examples but none seem to work.
Thanks so much.
Chandra
For that to happen automatically (one sheet's change updating another sheet), you will surely need an "event/trigger" to run a script whenever you change a cell. (that is the "onEdit()" function).
But since scripts are likely to fail sometimes (even when they are perfect, that's because of some Google issues), it's not guaranteed that the sheets will always contain the same data.
But, if I could suggest another way, do not let ID be optional. If that is a real ID (like the person ID card number), create another ID exclusively for working with the sheet.
I have edited your second sheet showing a suggestion of how to do it without using scripts. The only things you must be aware of are:
Do not create two people with the same ID.
You have to insert (only) the ID manually in the second sheet.
The VLOOKUP forumla will search for that ID in the first sheet and return the data in the same line. You can sort any sheet in whatever way you like. As long as you don't change people's IDs.
So, in sheet 2, use this in the First Name, Last Name and Email address:
=vlookup(A10,Participant_Registration!$A:$D,2,false)
=vlookup(A10,Participant_Registration!$A:$D,3,false)
=vlookup(A10,Participant_Registration!$A:$D,4,false)
Just extend this formula downwards
I hope this helps. I would avoid scripting for that at any cost. It would be my last resort. (Scripts also need to be changed if you want to rearrange your sheet, and if not, they might cause trouble, write over existing data...)
I also added a button (insert - drawing) and put a script in it (right button, click down arrow, "transfer? script" -- translated from Portuguese).
If you lock all four columns in sheet2 and lock the ID column in sheet 1, people will not be able to chang IDs and cause mess. They can edit people in sheet 1 and not change the formula in sheet2. Script is not affected by sorting or empty spaces (it adds the person in the first empty row it finds).
I added "named ranges" for the four column headers. (With named ranges, the script can refer to names instead of coordinates, which enables you to rearrange the sheet inserting and deleting columns, or moving them with CUT and paste - but the VLOOKUP formula will need manual update if you rearrange columns).
Here is the code: (it could get better if you manage to create dialog boxes and ask for the person's data inside that dialog, then you could lock everything - and you would need an edit button besides the add).
function AddPerson()
{
var S1Name = "Participant_Registration";
var S2Name = "Learning_Sessions_Attendance";
var ID1Name = "regID";
var ID2Name = "learnID";
//these vars are not used in this script
var FN1Name = "regFirstName";
var FN2Name = "learnFirstName";
var LN1Name = "regLastName";
var LN2Name = "learnLastName";
var Email1Name = "regEmail";
var Email2Name = "learnEmail";
var sSheet = SpreadsheetApp.getActiveSpreadsheet();
var Sheet1 = sSheet.getSheetByName(S1Name);
var Sheet2 = sSheet.getSheetByName(S2Name);
var ID1 = getRangeByName(sSheet, Sheet1.getName(), ID1Name);
var ID2 = getRangeByName(sSheet, Sheet2.getName(), ID2Name); Logger.log("ID2: " + ID2.getValue());
var Empty1 = getFirstEmpty(ID1);
var Empty2 = getFirstEmpty(ID2);
var Biggest1 = getBiggestID(ID1); Logger.log("Biggest 1: " + Biggest1);
var Biggest2 = getBiggestID(ID2); Logger.log("Biggest 2: " + Biggest2);
if (Biggest1 !== Biggest2)
Browser.msgBox("Warning: there are IDs in one sheet that are not in the other sheet");
var Biggest;
if (Biggest1 > Biggest2) Biggest = Biggest1;
else Biggest = Biggest2;
Biggest++;
Empty1.setValue(Biggest);
Empty2.setValue(Biggest);
}
function getFirstEmpty(Header)
{
while (Header.getValue() !== "")
{
Header = Header.offset(1,0);
}
return Header;
}
function getBiggestID(Header)
{
var Sheet = Header.getSheet();
var LastRow = Sheet.getLastRow();
var Values = Sheet.getRange(Header.getRow(), Header.getColumn(), LastRow - Header.getRow() + 1).getValues();
var len = Values.length;
var MaxID = 1;
for (var i = 0; i < len; i++)
{
var val = Number(Values[i]);
if (!isNaN(val) && val > MaxID)
MaxID = val;
}
return MaxID;
}
function getRangeByName(spreadSheet, sheetName, rangeName)
{
Logger.log("Trying range: " + "'" + sheetName + "'!" + rangeName);
return spreadSheet.getRangeByName("'" + sheetName + "'!" + rangeName);
}

Issue applying script to second sheet

I have no idea how to code. I use to make website on HTML, so my knowledge is limited. I piece together and alter existing codes. I got decent on Excel VBA, but then needed to start using google sheets. So, that said...
I have a spreadsheet with two sheets, 'MIS' and 'Admin'. I have a bunch of code a formula that assigns a value (1,2,3...) based on how many of two drop-down criteria that each line matches (1 if it matches criteria #1, 2 if matches criteria #2, and 3 if it matches both). The code then hides everything and unhides only those with numbers matching the criteria. I need this to be clean and quick, it's for people who can barely use computers.
The problem is, the code only works on the first page. I tried using the same code, tried amending the code, and I tried inserting 'Admin' in about half a million places. Please help. The admin function is my latest attempt. This is where I inserted 'Admin' in a dozen places. Also, if you see anything I'm using that is slowing down the code, I could use some help with that too. There are 6 functions, which basically do the same thing using the same code but corresponding to different number combinations. The one in question is below.
function Admin(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets('Admin')[0];
var range = sheet.getRange(1, 1, sheet.getLastRow());
sheet.hideRows(8,sheet.getLastRow());
var values = range.getValues();
for (var i=0; i<values.length; i++){
if(values[i][0] === 3){
sheet.showRows(i+1);
}
}
}
This line is your 'only the first sheet' problem, you're sort of combining two methods of defining a sheet:
var sheet = ss.getSheets('Admin')[0];
You can get a specific sheet by name by using the .getSheetByName() method i.e.
var adm_sheet = ss.getSheetByName('Admin');
var mis_sheet = ss.getSheetByName('MIS');
Or you can get a sheet by index
var adm_sheet = ss.getSheets()[0]; //returns first sheet
var mis_sheet = ss.getSheets()[1]; //returns second sheet
Lastly, you can get all of the sheets in your spreadsheet and list them in an array like this:
var sheets = ss.getSheets();
And then use the index of the sheet you need in this array like so:
sheets[0] //the first sheet
sheets[1] //the second sheet