Google Sheets add a note by appending column based on matching value - google-apps-script

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).

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'.

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.

How to assign drivers and passengers to cars using google script

Hello I have about 120 employees working on two shifts and living in 4 cities to which they drive with company cars(9 people van, so driver + 8 passengers, the cars do not always have to be full).
I have prepared a sheet with two tabs. On the first I have:
Name of the empleyee in column A
City they live in in column B
Whether they have drivers' license(yes/no) in column C
Whether they are on vacation(yes/no) in column D
And on another tab I have created a table with a car registration plate(B1) and direction it drives to in cell B2.
I am trying to create a script that will(on button press, one button per car) push the data into D2:D10 based on if:
People are living in the same city AND they are not on vacation AND at least one of them has drivers' license.
I am not sure whether I even have the right idea for that script(or what to use to push the data) because the only script I have ever written was to archive a sheet and clear cells in the original one.
I would like to get some guidance on:
What I am doing is correct
How to take the data that will pass the if conditions and put it into the table
Here is a link to a copy of the sheet
function updateCar1() {
let ss = SpreadsheetApp.getActiveSpreadsheet();
let range = ss.getRange("A2:D61");
let town = ss.getCell("cars!B2");
for(range){
if(ss.getCell("cars!B2")==town && ss.getRange("D2:D61")=="No" && ss.getRange("C2:C61")=="Yes"){
}
}
}
Your idea is correct, the remaining issue is how to loop through a range
For this
first you need to get the values of the range, since you cannot loop through a range without it
then you need to loop through the rows of the 2D value range
for each row, you can access the values in the respective columns by their indices starting with 0 for the first column (in your case A)
Sample:
function updateCar1() {
let ss = SpreadsheetApp.getActiveSpreadsheet();
let range = ss.getRange("A2:D61");
let values = range.getValues();
let town = ss.getRange("cars!B2").getValue();
for(var i = 0; i < values.length; i++){
if(values[i][1] == town && values[i][3] =="No" && values[i][2] =="Yes"){
//do something
}
}
}

Return cell value by criteria in same row AND row above

This is a very complicated question.
I'm not even sure if what I'm hoping for is possible, but I have been surprised many times before. Here we go....
Here are the links to the two spreadsheets I am modeling my situation after:
John Doe and
Jane Doe.
In these spreadsheets, I recently set up a script for making dynamic dropdown lists according to this video: DYNAMIC DEPENDENT DROP DOWN LISTS IN GOOGLE SPREADSHEETS. It works great for me, but there is a whole level on top of it that I need to establish. First and foremost, be aware that I cannot move any of the cells/rows/columns.
In my system here, I have two individuals working on a TASK (TASK1), one a Writer and the other a Reviewer. The John Doe, for example, will log their task (TASK1), and in the Total line they will indicate the TASK TYPE (Write) and WRITING PARTNER (Jane Doe). Jane Doe will do the same and then give John Doe a score after her review in column O. The roles here could also be switched-- for example, see TASK2, where Jane Doe is the Writer this time and John Doe is the Reviewer.
The difference is the formula in column F under SCORE, where it populates differently according to the TASK TYPE specified in the individual's sheet: If it is specified as a Review task for that person, it will populate with the score their Reviewer gave; if it is not specified as a Review task, it will be kept blank; if their Reviewer has not yet left a Score, it will populate the placeholder "(Score)"; and lastly, if it is specified as a Write task.......
....the idea is that I want it to populate with the SCORE value in column F for the task having the same TASK NAME in whoever's spreadsheet is specified in the WRITING PARTNER column. I've figured out how to dynamically import the correct spreadsheet using a VLOOKUP within an IMPORTRANGE function: IMPORTRANGE(VLOOKUP(E4,'Named Ranges'!B:D,3,FALSE),"'2019'!whateverrangeIneed")
But beyond that, I haven't figured out how to return the correct value from Jane Doe's spreadsheet into John Doe's spreadsheet. Please see my attempts in John Doe's spreadsheet. The biggest issue is that I need to use not only column A="Total" as a criteria, but also column B of the row above the total="The TASK NAME to reference". If I could figure that out, I could paste that formula into "FORMULATOSOLVE" of the function in column F.
Ultimately, the point is to be able to report an Average Score for John Doe the Writer, based on his review scores, using the formula AVERAGE(FILTER('2019'!F:F,'2019'!D:D="Write",'2019'!E:E<>"None")).
As mentioned, I've recently used a script for the first time, so I'm not opposed to doing that here as well if needed, but I don't have the know-how to write it myself.
Thank you immensely for any help.
Since you have tried Apps Script I have written this simple script that does what you need.
This will work with the amount of Tasks that you want, since iterates over all the column length.
function getScore() {
// GET the two sheets
var writer = SpreadsheetApp.openById('WRITER SHEET ID').getSheetByName('2019');
var reviewer = SpreadsheetApp.openById('REVIEWER SHEET ID').getSheetByName('2019');
// Get the B column of each sheet
var writerTask = writer.getRange("B2:B").getValues();
var reviewerTask = reviewer.getRange("B2:B").getValues();
// Variables to save data
var writerScore = '';
var taskName = '';
// Iterate over every single row in column B of writer
for (var i = 1; i < writerTask.length; i++) {
// Check if the actual row is empty (which means is the total row)
// and also check if the row before is not empty
if (writerTask[i] == '' && writerTask[i-1] != ''){
// Assign the values inside the previous row to taskName (will be TASK1)
taskName = writerTask[i-1].toString();
// Get the cell where the score has to go
writerScore = writer.getRange('F'+(i+2))
// Iterate over the reviewers sheet
for (var j = 1; j < reviewerTask.length; j++) {
// Check if cell is empty and check the previous cell has the same
// value from the one on the writers sheet
if (reviewerTask[j-1].toString() == taskName && reviewerTask[j] == ''){
// Assign the value from the cell on reviewers sheet to the cell on the writers sheet
writerScore.setValue(reviewer.getRange('F'+(j+2)).getValue());
}
}
}
}
}
To run the script you can set up a button on the sheet you want, see: Clickable images and drawings in Google Sheets
if I understood it right, try:
=ARRAYFORMULA(IFERROR(IF(D4="Write", VLOOKUP(E4&B3,
{IMPORTRANGE("1rrshg9qcShUCqQDEhp74aRHzcyDRgsh5IWM60Lypup8", "2019!E2:E1000")&
IMPORTRANGE("1rrshg9qcShUCqQDEhp74aRHzcyDRgsh5IWM60Lypup8", "2019!B1:B1000"),
IMPORTRANGE("1rrshg9qcShUCqQDEhp74aRHzcyDRgsh5IWM60Lypup8", "2019!F2:F1001")}, 2, 0),
IF(D4="Review", IF(O4="", "(Score)", O4), )), "no data"))
note to allow access beforehand you do above:
This problem has been solved. The script and formula in use can be seen in the google sheets by following the links. The script used was:
function WriterScore(projectName, importedSheet){
// Create a set of variables
var foundScore = false;
var score = "";
var data = importedSheet;
while(foundScore == false){
for(var i = 0; i<data.length;i++){
if(data[i][1] == projectName){
for(var q = i; q<data.length;q++){
if(data[q][1] == projectName){
}else{
score = data[q][5]
return score
}
}
}
}
if(score == ""){
return "You do not have a project by that name.";
}
}
return score
}
And the final cell formula was:
=IF(D7="Write",WriterScore(B6,IMPORTRANGE(VLOOKUP(E7,'Named Ranges'!B:D,3,FALSE),"'2019'!A:F")),IF(D7="Review",IF(O7="","(Score)",O7),""))

Map checked cells from one sheet to others to summarize data

I have a sheet that has customer information and another sheet where the customer information needs to go if it meets certain conditions. For example sheet one I have a customer with a unique identifier, and on sheet 2 I have several columns that could be checked off. If these columns are checked off I need those column headers to be populated into one cell in sheet 1. So
Sheet 1:
John Doe 019384388 Eats cookies
Likes Movies
Needs Help from Stack exchange
Sheet 2:
Name uniques identifier Eats cookies Likes movies Is tired Needs help
John Doe 019384388 X X X
This is just something I'm playing with at work to simplify some spreadsheets. I have worked with C, C++, and Java so I do understand condition statements and loops, but I haven't really worked with Google Apps Script / JavaScript. I am having a hard time knowing how to call to the individual cells in the spreadsheets to make condition statements, e.g.
if (box == "x" && unique id == "xxxxxx")
cell ("Ai"(sheet1) = "Ai"(sheet2))
type things. Perhaps iterating through all the cells using a for loop that changes i until all cells are checked? Any and all help is appreciated.
You could create a custom Google Apps function, and add an onedit trigger to it.:
function autoCheck(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName("Sheet1");
var row = e.range.getRow();
var col = e.range.getColumn();
var sheet2 = ss.getSheetByName("Sheet2");
if (e.range.getSheet().getName()=="Sheet2" && col >=3 && col<=5) {
var str = "";
for (i=3; i<=6; i++) {
if (sheet2.getRange(row, i).getValue()=="X") {
if (str.length==0) {
str = sheet2.getRange(1, i).getValue();
} else {
str =str + "\n" + sheet2.getRange(1, i).getValue();
}
}
}
sheet1.getRange(row, 3).setValue(str);
}
}
Add a row that concatenates the headers if the box is checked:
=if(B1 = 'x', $A$1, '') & if(B2 = 'x', $A$2, '') & ...
then vlookup() on the ID and return the concatenated field.
Add comma delimiters to the if statements if you want them, and hide the concatenated field if you don't want to see it in the table.