Fill column based on row data - google-apps-script

I'm looking to use a button to run a script within a Google Sheet. When the button is pressed, I'd like a column to populate with "Approved" for a given row, based on the user that's logged in and the email that's listed in the row.
For example, Column A has a list of different email addresses. Press a button, and column B is filled with "Approved" if your login email address is the same as what's in column A. Ideally, it could search and make sure the entry in column B isn't already populated with "Rejected". Think about a supervisor going through a list, and instead of using a pull-down for "Approved" on each line, they can hit a button and it would approve all of the entries their email is attached to.
I have it working, but it's slow and inefficient as it goes through each row, evaluates, then populates the appropriate column. It also doesn't set the approval for the last row of data. It runs through each row, populates, but doesn't fill the Approved on the last row of data entered.
I'd like to figure out how to do this with an array to make it more efficient.
Here is what I have so far:
function supervisorApproval(){
//This function will approve all of the peole in the respected supervisors section
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("AS");
var approverLogin = Session.getActiveUser().getEmail();
var ui = SpreadsheetApp.getUi();
//Calculate which row is the last row (last entry)
var column = sheet.getRange('W:W');
var values = column.getValues();
var lastrow = 1;
//this is inefficient code, doesn't write "approved" for last row of data
while (values[lastrow][0] != ""){
var supEmail = sheet.getRange(lastrow, 23).getValue();
var approvalCell = sheet.getRange(lastrow, 27);
if (approverLogin == supEmail){
approvalCell.setValue("Approved");
}
lastrow++;
}
}

It should be a lot faster if you read and write values to the spreadsheet only once. Try this.
function supervisorApproval() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("AS");
var approverLogin = Session.getActiveUser().getEmail();
var ui = SpreadsheetApp.getUi();
var range = sheet.getRange(1, 23, sheet.getLastRow(), 5);
var values = range.getValues();
for(var i = 0; i < values.length; i++) {
var row = values[i];
var supEmail = row[0];
if (approverLogin === supEmail){
row[4] = "Approved";
}
}
range.setValues(values);
}

Related

Google Apps Script: Prevent duplicate copies based on two columns

I am working with some colleagues who want the following to happen within a Google Sheet:
A Google Form contains a question that asks which Counselor a student is assigned to (among other questions)
Forms are submitted throughout the year by students
When a form is submitted, the form data goes into a Google Sheet in a Responses sheet
The Counselors would like a copy of each row to appear in another sheet within the main Sheet, based on the name of the Counselor
In their own sheets, each Counselor needs to be able to manipulate the data (sorting, highlighting rows, adding notes to the row/submission) ←hence a copy is needed instead of a query
I have the following script that copies the rows in the correct Counselor sheet, and does not copy a row into a Counselor sheet if it already appears. However, if a Counselor modifies anything in the row, the script will make a duplicate row (with the original data) the next time it is run, perhaps because it sees the modified row as not an exact match.
Is there a way to modify my script so it can check against a unique part of a row in the Responses sheet (the columns at indexes 0 and 1 together in the same row create a unique entry) in any part of a Counselor sheet before it creates a copy? In other words, it would not create a duplicate row if the Counselor modifies anything except for columns 0 and 1.
function copyData() {
var formResponses = SpreadsheetApp.getActive().getSheetByName("Form Responses 1");
var formValues = formResponses.getDataRange().getValues();
formValues.shift(); // remove the header row
formValues.forEach(function(row) {
var sheetName = row[4]; // the value of "My College Counselor is" column
var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
var range = sheet.getDataRange();
var data = range.getValues();
var duplicate = false;
for (var i = 0; i < data.length; i++) {
var currentRow = data[i];
if (currentRow.join() == row.join()) {
duplicate = true;
break;
}
}
if (!duplicate) {
sheet.appendRow(row);
}
});
}
I'm stuck at this point and am not sure how to proceed.
NOTE: I have code to add a button to the menu list for the Counselors to run this script as needed since the forms can be submitted at any time. Using "onFormSubmit" does not work because there is a potential for multiple students to submit the form at the same time, which I've seen can cause a row or two to not be copied over.
If I understand your question correctly, you want to find a way to avoid duplicated rows, even if you edit them.
In order to do that, you have to define a value for each row that won't change and that is unique. My suggestion would be the following :
Installable trigger with the function custom_onFormSubmit
In the function get Uid (unique ID), and add it to each row submitted
Edit your code in order to search duplicate only with this Uid
First, add this function your Google Form Apps Script:
//add unique ID at a defined column each time a google form is submitted
function custom_onFormSubmit(e){
var uuid = e.triggerUid;
//alternatily you can use:
//var uuid = Utilities.getUuid();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName([SHEETNAME]);
var range = sheet.getDataRange();
var row = range.getLastRow();
sheet.getRange(row, 10).setValue(uuid); //column 10 is for example, adapt to your need
}
------ EDIT: alternative function without trigger onFormSubmit, add this function before
function check_insert_uuid(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName([SHEETNAME]);
var range = sheet.getDataRange();
var values = range.getValues();
for (var x = 0; x < values.length; x++) {
if (values[x][10] == "") {
let uuid = Utilities.getUuid();
range.offset(x, 10, 1, 1).setValue(uuid);
}
}
SpreadsheetApp.flush(); //force new data to sync before copyData
copyData(); //call copy function
}
------ END EDIT -------
Then you just have to edit your function copyData
FROM:
for (var i = 0; i < data.length; i++) {
var currentRow = data[i];
if (currentRow.join() == row.join()) {
duplicate = true;
break;
}
}
TO:
for (var i = 0; i < data.length; i++) {
var currentRow = data[i];
if (currentRow[10] == row[10]) { //same example of column index 10
duplicate = true;
break;
}
}
References:
Installable Triggers
Google Form Events
Apps Script getuuid (Unique ID are not 100% unique in time and space, but will certainly answer your project)
Based on the help from #waxim-corp, here is the final script that accomplishes my goal:
function onOpen(e) {
let ui = SpreadsheetApp.getUi();
ui.createMenu("🤖 Copy Data 🤖")
.addItem("Let's Do This!", 'checkForID')
.addToUi();
};
function checkForID(){
var ss = SpreadsheetApp.getActive().getSheetByName("Form Responses 1");
var range = ss.getDataRange();
var values = range.getValues();
for (var x = 0; x < values.length; x++) {
if (values[x][0] == "") {
let uuid = Utilities.getUuid();
range.offset(x, 0, 1, 1).setValue(uuid);
}
}
SpreadsheetApp.flush(); //force new data to sync before copyData
copyData(); //call copy function
}
function copyData(){
var formResponses = SpreadsheetApp.getActive().getSheetByName("Form Responses 1");
var formValues = formResponses.getDataRange().getValues();
formValues.shift(); // remove the header row
formValues.forEach(function(row) {
var sheetName = row[5]; // the value of "My College Counselor is" column
var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
var rangeC = sheet.getDataRange();
var data = rangeC.getValues();
var duplicate = false;
for (var i = 0; i < data.length; i++) {
var currentRow = data[i];
if (currentRow[0] == row[0]) {
duplicate = true;
break;
}
}
if (!duplicate) {
sheet.appendRow(row);
}
});
}
I'm sure it could be more efficient, but it works well.

How to make autofill a cell with datavalidation without changing the entire row on Google sheet?

I'm automating the appointment process at my company and I have been successfully implementing some google scripts with time trigger.
Whenever someone takes an appointment on Calendly it creates at row with several information through Zapier.
I then have a script with several functions that operates on the newly added rows.
One function auto sort the new row based on the date column then two others functions fill two columns with a checkbox(FALSE) and a datavalidation based on a list of choice. All of those functions are time triggered, let's say 30 minutes.
The problem is whenever the trigger happens it automatically checks the checkbox to TRUE for the entire column and to the first choice of the list for the entire datavalidation column.
How can I solve that ?
var SORT_COLUMN_INDEX = 4;
var ASCENDING = true;
var NUMBER_OF_HEADER_ROWS = 1;
var activeSheet;
function autoSort() {
console.log(sheet, activeSheet)
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var range = sheet.getDataRange();
if (NUMBER_OF_HEADER_ROWS > 0) {
range = range.offset(NUMBER_OF_HEADER_ROWS, 0);
}
range.sort( {
column: SORT_COLUMN_INDEX,
ascending: ASCENDING
} );
}
// Fonction to automatically add data validation in column K
function setDataValidationComing() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var listOfChoices = ["Coming","Not Coming","Message Left", "Unreachable"]
var validation = SpreadsheetApp.newDataValidation().requireValueInList(listOfChoices).build();;
sheet.getRange("K2").setDataValidation(validation);
var lr = sheet.getLastRow();
var fillDownRange = sheet.getRange(2, 11, lr-1);
sheet.getRange("K2").copyTo(fillDownRange);
}
// Fonction to automatically add checkbox for appointment honored in column L
function setCheckboxCame() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var validation = SpreadsheetApp.newDataValidation().requireCheckbox().build();
sheet.getRange("L2").setDataValidation(validation);
var lr = sheet.getLastRow();
var fillDownRange = sheet.getRange(2, 12, lr-1);
sheet.getRange("L2").copyTo(fillDownRange);
}
Here is a screenshot of the google sheet. Google sheet screenshot
Thanks for your help, I've just started using Google Script a week ago !
The problem is whenever the trigger happens it automatically checks the checkbox to TRUE for the entire column and to the first choice of the list for the entire datavalidation column.
It is not actually checking it to true for the entire column. It is copying the value of the first row after the header, and applying that value to each checkbox in the column. The first row of data shows 'Coming' so if you run that script, it will apply Coming to all of them. If you change it to 'Not Coming', it would apply 'Not coming' to every row. This is because of this line:
sheet.getRange("K2").copyTo(fillDownRange);
You don't want to copy the value of K2, you want to only copy the validation. So that line should really be:
`sheet.getRange("K2").copyTo(fillDownRange, SpreadsheetApp.CopyPasteType.PASTE_DATA_VALIDATION, false);`
As for the checkbox, that's a little bit trickier, but the same concept applies:
function setCheckboxCame() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
// UPDATED THIS PART
var validation = SpreadsheetApp.newDataValidation().requireCheckbox();
validation.setAllowInvalid(false);
validation.build();
var lr = sheet.getLastRow();
var fillDownRange = sheet.getRange(2, 12, lr-1);
// CHANGE THIS:
//sheet.getRange("L2").copyTo(fillDownRange);
// TO THIS:
fillDownRange.setDataValidation(validation);
}

GAS - Select (Get) Spreadsheet per GForm input

My 1st question in Stack Overflow.
I have a Google Spreadsheet with 4 individual sheets.
Sheet1 is responses from GForm. Sheet2,3,4 have same data but in 3 languages English, Tamil, Hindi and named English, Tamil, Hindi.
At present the Script takes cell value date from Sheet1, matches the data in the language Tamil sheet, mail merges with the correct language Tamil GDoc and emails it.
Now, I cannot figure out how to make the Script pick a different language Gsheet, one of the 3, depending upon the evalues4 from Form input, which is a Radio Button.
Can someone help me to figure it out?
Select appropriate language
Gsheet using ss.getSheetByName and
GDoc using DriveApp.getFileById
based on Form response evalues4.
Here is the code and which works fine, if I input just one language by name/sheetID/DocID.
//Get information from the form and set as variables
function onFormSubmit(e)
{
var Time = e.values[0];
var email = e.values[1];
var fog = e.values[2];
var mog = e.values[3];
var lang = e.values[4];
var do_date = e.values[5];
//Match chosen date from data sheet & get Row Number
function findInColumn() {
var data = do_date;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName("Tamil");
var range = sheet1.getRange(1,1,sheet1.getLastRow(),1);
var values= range.getValues();
var row = 0;
while ( values[row] && values[row][0] !== data ) {
row++;
}
if (values[row][0] === data)
return row+1;
else
return -1;
}
//Use above Row number to load in Range, get values, assign to variable cells
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet3 = ss.getSheetByName("Tamil");
// Get range of cells in All columns and All rows with entries
var dataRange = sheet3.getRange(findInColumn(), 1, 1, sheet3.getLastColumn());
// Get entries for each row in the Range.
var data = dataRange.getValues();
// Set each value to a unique variable
for (i in data) {
var cell = data[i];
// Get document template, copy it as a new doc with Name and email, and save the id
var copyId = DriveApp.getFileById("1Nxxxxxxxxxxxxxx2k")
Sorry, completely forgot that I had asked the question here. I solved it the same weekend, by using the If - else [Note: Instead of var ss I used var ssn, and instead of var sheet3 I used var tocopyID in the rewrite]
//Use above Row number to load in Range, get values, assign to variable cells
var ss = SpreadsheetApp.getActiveSpreadsheet();
if (lang === "Tamil"){
var ssn = ss.getSheetByName("Tamil");
var tocopyId = DriveApp.getFileById("1NxxxxTamil");
}
else if (lang === "Hindi"){
var ssn = ss.getSheetByName("Hindi");
var tocopyId = DriveApp.getFileById("Hindi");
}
else if (lang === "English"){
var ssn = ss.getSheetByName("English");
var tocopyId = DriveApp.getFileById("1NxxxxEnglish");
}
You could use you lang variable to get the correct sheet, since they've got the same name. Like :
var sheet1 = ss.getSheetByName(lang); // where lang = "Tamil" for example
For using the correct template, you can store the id of your template with the Properties like:
var templateId = PropertiesService.getScriptProperties().getProperty(lang); // where lang = "Tamil" for example
var copyId = DriveApp.getFileById(templateId);
Where you have properties set with the language name you want. You can set them with code :
PropertiesService.getScriptProperties().setProperty('Tamil', '1Nxxxxxxxxxxxxxx2k');

move a row in google spreadsheet based on todays date

I'm trying to move a row of data from one sheet to another in the same spreadsheet based on a value of today's date.
In column "A", I have a date. I want to move the row if the date entered in column "A" is older than today's date. (it's a flight schedule for aircraft and I want to move flights that have already occured onto a sheet called "Past Flights".) The name of the active sheet is "Flight Schedule".
After the row is moved, I want it to delete off the "Flight Schedule" sheet. I know where to add scripts, but have no idea how to make this happen.
Here is what I have tried. I think on line "If (data.link >1..." data.link isn't the right one to use. But I can't find something for indicating older than todays date.
function approveRequests() {
var ss = SpreadsheetApp.getActiveSpreadsheet()
sheet = ss.getActiveSheet(),
sheetName = sheet.getName(),
data = sheet.getDataRange().getValues();
if (sheetName == "Flight Shedule") {
var range = sheet.getActiveRange(),
startRow = range.getRowIndex(),
numRows = range.getNumRows(),
numCols = range.getNumColumns()
if (numCols == 9) {
if (data.length > 1) {
var values = range.getValues(),
nextSheet = ss.getSheetByName("Past Flight"),
lastRow = nextSheet.getLastRow();
nextSheet.getRange(lastRow+1,1,numRows,3).setValues(values);
sheet.deleteRows(startRow,numRows);
}
}
}
}
Any help would be huge!
Thanks!
Ok, I will go in with some general tips based on your current code first.
In your function you do a sheet = ss.getActiveSheet() which is redundant because you already have SpreadsheetApp.getActiveSpreadsheet().Also I would recommend to avoid this
var ss = SpreadsheetApp.getActiveSpreadsheet()
sheet = ss.getActiveSheet(),
sheetName = sheet.getName(),
data = sheet.getDataRange().getValues();
in favour of this:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var sheetName = sheet.getName();
var data = sheet.getDataRange().getValues();
which is much more easy to read and change without making mistakes.
data.length has nothing to do with current date, it will simply be the length of the array. So if you select 1 row of data, it will be 1, if you select 2 rows it will be 2 etc. .getValues() will return an array where data[row][col]. What you are looking for is getting the value of the flight time, converting it into a date object (not a google specific thing, just general javascript). Then use var now = new Date() and compare the two.
I would also recommend to re-think your if statements. There are a lot of better ways to grab the row data than selecting the row manually and then running the function. You can save a lot of lines of code should you decide to actually make this run automatically, because as it is, it will run only when called manually.
This sample is working:
function approveRequests() {
// Initialising
var ss = SpreadsheetApp.getActiveSpreadsheet();
var scheduleSheet = ss.getSheetByName("Flight Shedule");
var pastSheet = ss.getSheetByName("Past Flight");
var lastColumn = scheduleSheet.getLastColumn();
// Check all values from your "Flight Schedule" sheet
for(var i = scheduleSheet.getLastRow(); i > 0; i--){
// Check if the value is a valid date
var dateCell = scheduleSheet.getRange(i, 1).getValue();
if(isValidDate(dateCell)){
var today = new Date();
var test = new Date(dateCell);
// If the value is a valid date and is a past date, we remove it from the sheet to paste on the other sheet
if(test < today){
var rangeToMove = scheduleSheet.getRange(i, 1, 1, scheduleSheet.getLastColumn()).getValues();
pastSheet.getRange(pastSheet.getLastRow() + 1, 1, 1, scheduleSheet.getLastColumn()).setValues(rangeToMove);
scheduleSheet.deleteRow(i);
}
}
}
}
// Check is a valid date
function isValidDate(value) {
var dateWrapper = new Date(value);
return !isNaN(dateWrapper.getDate());
}
So yes, It's not the optimized solution (cause of the use of several sheet.getRange() method), but it's working and allowing to have a clear code.

Javascript on Google Sheets - Get Column Number from Prompt Input

I have two sheets on a Google Spreadsheet. One has a lot of information and references and the other has the same reference in the first cell of a column with link names and links below. I am trying to get around the "no multi-hyperlinking in one cell" limitation by having the user input the reference they want to search and then searching through the second sheet to find the reference and have a pop-up box with the links.
So far, I am able to get the links from the second spreadsheet column and display them in a pop-up box with this code:
function main(){
var column = SearchAndFind()
showURL(getLinks(column))
}
function getLinks(col){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.setActiveSheet(ss.getSheets()[1]);
var cell = ss.getActiveCell();
var values = sh.getDataRange().getValues();
var myArray = []
for(n=1;n<values.length;++n){
var cell = values[n][col] ;
myArray.push(cell);
}
return myArray;
}
function showURL(data){
var app = UiApp.createApplication().setHeight(40+8*data.length).setWidth(200);
app.setTitle("Show URLs");
var panel = app.createVerticalPanel();
app.add(panel);
for(var d=0 ; d<data.length;d=d+2){
var link = app.createAnchor(data[d],data[(d+1)]);
panel.add(link);
}
var doc = SpreadsheetApp.getActiveSpreadsheet();
doc.show(app);
return;
}
When I hard-coded a random column number to the getLinks function and it worked fine but I need to be able to get the column number from a user search of the first cell in each column in the second sheet.
This is the code I have right now that doesn't work:
//I know that it will always be the second sheet on the spreadsheet
//Search the column headers on the second sheet
//When one matches, return the index
function SearchAndFind(){
//Make the 2nd sheet active
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.setActiveSheet(ss.getSheets()[1]);
var range = sh.getRange(1, 1, sh.getMaxRows(), 1);
var values = range.getValues();
//Get the user input for the text they want to search
var ui = SpreadsheetApp.getUi();
var search = ui.prompt('Enter the ID: ');
var searchString = search.getResponseText()
//for loop to iterate through the first row and find the matching cell
//return the index of that column
for (n = 0; n < values[0].length; n++){
var cell = values[0][n]
if (cell === searchString){
return n;
}
}
}
When I run all of the code (including the function SearchAndFind that doesn't work), the pop-up box comes up with undefined, linking nowhere. Admittedly, I don't have a lot of experience with Javascript so I think I just don't understand it well enough to find the bug here.
You are pulling only one column and then checking for the match in columns.
var range = sh.getRange(1, 1, sh.getMaxRows(), 1);
Gives you only the first column. And
for (n = 0; n < values[0].length; n++){
Looks through those columns. So your values[0].length is 1, and your loop only runs once.
Are you trying to loop through all rows form first column, or through first row of all columns.
Also you should change your loop to
for (var n = 0; n < values[0].length; n++){