GSheets Trigger for updating entries wont work - Script does work manually tho - google-sheets

I want to run a script for GSheets by a trigger every minute.
The script works without an error if I run it manually, but the trigger will throw an error message 100% of the time.
The idea is to populate a spreadsheet via GForms. The script should update the entries when a new form of an existing data set is submitted (identifier is in row 4). It does this by looking for duplicates and deleting the old set of data.
The trigger should run the script every minute or so to ensure data quality and to eliminate multiple duplicates if they occur.
This is the code for my script:
function updateExisting(columnWithUniqueIdentifier,sheetTabName) {
var dataFromColumnToMatch,lastColumn,lastRow,rowWithExistingUniqueValue,rowOfDataJustSaved,
sh,ss,valueToSearchFor;
// USER SETTINGS - if the values where not passed in to the function
if (!columnWithUniqueIdentifier) {//If you are not passing in the column number
columnWithUniqueIdentifier = 4;//Hard code column number if you want
}
if (!sheetTabName) {//The sheet tab name was not passed in to the function
sheetTabName = "Formularantworten 2";//Hard code if needed
}
//end of user settings
ss = SpreadsheetApp.getActiveSpreadsheet();//Get the active spreadsheet - this code must be in a project bound to spreadsheet
sh = ss.getSheetByName(sheetTabName);
lastRow = sh.getLastRow();
lastColumn = sh.getLastColumn();
Logger.log('lastRow: ' + lastRow)
rowOfDataJustSaved = sh.getRange(lastRow, 1, 1, lastColumn).getValues();//Get the values that were just saved
valueToSearchFor = rowOfDataJustSaved[0][columnWithUniqueIdentifier-1];
Logger.log('valueToSearchFor: ' + valueToSearchFor)
dataFromColumnToMatch = sh.getRange(1, columnWithUniqueIdentifier, lastRow-1, 1).getValues();
dataFromColumnToMatch = dataFromColumnToMatch.toString().split(",");
Logger.log('dataFromColumnToMatch: ' + dataFromColumnToMatch)
rowWithExistingUniqueValue = dataFromColumnToMatch.indexOf(valueToSearchFor);
Logger.log('rowWithExistingUniqueValue: ' + rowWithExistingUniqueValue)
if (rowWithExistingUniqueValue === -1) {//There is no existing data with the unique identifier
return;
}
sh.getRange(rowWithExistingUniqueValue + 1, 1, 1, rowOfDataJustSaved[0].length).setValues(rowOfDataJustSaved);
sh.deleteRow(lastRow);//delete the row that was at then end
SpreadsheetApp.getActiveSpreadsheet().toast("Updating Data Set Successful")
}
The Trigger will throw this Error Message:
23.11.2020, 10:27:03 Info lastRow: 7
23.11.2020, 10:27:03 Info valueToSearchFor: undefined
23.11.2020, 10:27:03 Fehler Exception: Konvertierung von "[object Object]" in int nicht möglich.
at updateExisting(Code:28:30)
GSheet_Trigger_Error_Messeage
Any idea of what I am doing wrong here?
Thanks so much in advance.
EDIT:
Here is a debugger log for rowofdatajustsaved & columnWithUniqueIdentifier.
Seems like they work as intended.
Debugger_Log_UpdateExisting

Problem solved: there has to be a trigger var in the var statement. I figured that there is an unnecessary if statement in the code as well and fixed it. Updated code below.
function updateExisting(trigger, columnWithUniqueIdentifier,sheetTabName) {
var dataFromColumnToMatch,lastColumn,lastRow,rowWithExistingUniqueValue,rowOfDataJustSaved,
sh,ss,valueToSearchFor;
Logger.log('columnWithUniqueIdentifier' + columnWithUniqueIdentifier)
// USER SETTINGS - if the values where not passed in to the function
columnWithUniqueIdentifier = 4;//Hard code column number if you want
Logger.log('sheetTabName' + sheetTabName)
if (!sheetTabName) {//The sheet tab name was not passed in to the function
sheetTabName = "Formularantworten 2";//Hard code if needed
}
//end of user settings
ss = SpreadsheetApp.getActiveSpreadsheet();//Get the active spreadsheet - this code must be in a project bound to spreadsheet
sh = ss.getSheetByName(sheetTabName);
lastRow = sh.getLastRow();
lastColumn = sh.getLastColumn();
Logger.log('lastRow: ' + lastRow)
rowOfDataJustSaved = sh.getRange(lastRow, 1, 1, lastColumn).getValues();//Get the values that were just saved
Logger.log(rowOfDataJustSaved)
debugger
valueToSearchFor = rowOfDataJustSaved[0][columnWithUniqueIdentifier-1];
Logger.log('valueToSearchFor: ' + valueToSearchFor)
dataFromColumnToMatch = sh.getRange(1, columnWithUniqueIdentifier, lastRow-1, 1).getValues();
dataFromColumnToMatch = dataFromColumnToMatch.toString().split(",");
Logger.log('dataFromColumnToMatch: ' + dataFromColumnToMatch)
rowWithExistingUniqueValue = dataFromColumnToMatch.indexOf(valueToSearchFor);
Logger.log('rowWithExistingUniqueValue: ' + rowWithExistingUniqueValue)
if (rowWithExistingUniqueValue === -1) {//There is no existing data with the unique identifier
return;
}
sh.getRange(rowWithExistingUniqueValue + 1, 1, 1, rowOfDataJustSaved[0].length).setValues(rowOfDataJustSaved);
sh.deleteRow(lastRow);//delete the row that was at then end
SpreadsheetApp.getActiveSpreadsheet().toast("Updating Data Set Successful")
}

Related

getactive() function returning to null and defaulting to first sheet

I am trying to have my function use three sheets within its embedded spreadsheet: two fixed sheets and one active/open sheet. I need it to read the sheet I have open because that is the sheet I am changing week to week, but it is automatically defaulting to using the first sheet rather than the sheet I have opened. I altered this function from an existing function I have that works, and on this new one I only changed the message and its assigned variables. I really know absolutely nothing about coding but have been learning so I can create a custom message from a code a previous coworker wrote. I appreciate all of the help I can get x10000
function createMessage(address, dirtrider, day, window, outby, phone) {
{ var message = 'Hello ' + address + ', welcome to IVCC\'s composting program! You\'ll be receiving weekly automated reports from this number (a weekly reminder to put your bucket out and a notification if any incorrect items were discarded into your bucket). To stop receiving these messages, reply STOP. We are currently restructuring our biking routes, therefore you may be receieving a new Dirtrider according to this message. Starting next week, you\'re assigned Dirtrider will be ' + dirtrider + ', and you\'re new bucket will arrive weekly on ' + day + '\'s sometime between ' + window + '. Please have your bucket outside your front door by ' + outby + ' on this day weekly unless notified of a change. This is an automated messaging service, so please reach out to your Dirtrider directly at ' + phone + ' with any questions or concerns about your service. Thanks for composting with us! -IVCC Team';
}
return message;
}
// function to verify numbers and messages before sending //
function PrintMessages() {
var mainsheet = SpreadsheetApp.getActiveSheet();
var data = mainsheet.getDataRange().getValues();
var contactsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Contact Sheet");
var contacts = contactsheet.getDataRange().getValues();
var messageLog_sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Message Log");
var messageLog = messageLog_sheet.getDataRange().getValues;
var mLrow = 2 //row to start at on Message Log spreadsheet
// runs through every row of active spreadsheet, i = row //
for (var i = 2; i < data.length; i++) {
// set variables based on values in sheet and create message//
var address = data[i][0];
var day = data[i][1];
var window = data[i][2];
var outby = data[i][3];
var dirtrider = data[i][4]
var phone = data[i][5]
var message = createMessage(address, dirtrider, day, window, outby, phone);
// reference seperate contact sheet, j = column //
var nresidents = contacts[i][2];
for (var j = 1; j <= nresidents; j++) {
// log address, phone number, and message function //
var address = contacts[i][0];
var number = contacts[i][j+2];
var messageArray = [[address, number, message]];
var range = messageLog_sheet.getRange(mLrow, 1, 1, 3); //ENTER COMMENTS
range.setValues(messageArray);
var mLrow = mLrow + 1;
}
}
}

Google Sheet If dropdown

I have drop down for column A where I want to select value from drop down only for certain times (ie. 3 times), after I select value from dropdown 3 times, if I try to select it 4th time, value should be removed from dropdown or not able to select. Is this possible using excel or google sheet?
https://docs.google.com/spreadsheets/d/1nbXAkK565V24KDTAzE68q8rQgQWzn-jDJz_6piNYyEw/edit?usp=sharing
In above google sheet, I had selected Red 3 times, now if I want to select Red 4th time, I should not be able to select or Red should be removed from list.
I know using excel VBA, I can do same using below code, but can we add same to google sheets?
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rngDV As Range
Dim oldVal As String
Dim newVal As String
Dim lVal As Long
Dim check2 As Long
If Target.Count > 2 Then GoTo exitHandler
On Error Resume Next
Set rngDV = Cells.SpecialCells(xlCellTypeAllValidation)
On Error GoTo exitHandler
If rngDV Is Nothing Then GoTo exitHandler
If Intersect(Target, rngDV) Is Nothing Then
'do nothing
Else
Application.EnableEvents = False
newVal = Target.Value
Application.Undo
oldVal = Target.Value
Target.Value = newVal
lVal = Application.WorksheetFunction _
.CountIf(Columns(Target.Column), _
"*" & newVal & "*")
If lVal > 3 Then
If newVal = "" Then
'do nothing
Else
MsgBox "Not available"
Target.Value = oldVal
End If
Else
If Target.Column >= 47 And Target.Column <= 56 Then
If oldVal = "" Then
'do nothing
Else
If newVal = "" Then
'do nothing
Else
Target.Value = oldVal _
& ", " & newVal
End If
End If
End If
End If
End If
exitHandler:
Application.EnableEvents = True
End Sub
So after doing some research I found the way of achieving what you were aiming for here.
Solution
In the class data validation it mentions a method to set the validation to null within the range class. You can check more details about this method here. In that method it mentions that if the parameter is set to null, it will disable the data validation (dropdowns) therefore not allowing to select other values.
Here is a piece of sample code self explained with comments to achieve what you want in your specific case:
function onEdit(){
// get the sheet and values to check if in that range there are more than 3 elements selected
var ss = SpreadsheetApp.getActiveSheet();
var values = ss.getRange('A1:A18').getValues();
// this variable is for counting the amount of elements selected
var count = 0;
for(i=0;i<values.flat().length;i++){
// if an element in that range is not empty
if(values.flat()[i]!=''){
count++;
}
}
// if the count is over 3 then disable the dropdowns
if(count>3){
ss.getRange('A1:A18').setDataValidation(null);
}
}
I hope this has helped you. Let me know if you need anything else or if you did not understood something. :)

running script on an Inactive sheet

I have been using the following script to move "Finished" columns from one sheet to another:
function onEdit(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "Sheet1" && r.getColumn() == 15 && r.getValue() ==
"Finished") {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("Finished");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).moveTo(target);
s.deleteRow(row);
}
}
Im trying to figure out how to get the script to run even if the sheet has not been opened or edited. Im just not sure how to go about changing it to use a time trigger every minute or so.
There are two aspects to the change to a timed trigger from onEdit.
The first concerns revisions to the code, the second is the trigger details.
Code
The code can't be re-used because the timed trigger doesn't provide the same event details as OnEdit.
In addition, it is possible that several rows might be tagged "Finished" between each trigger event, and the code needs to respond to them all. lastly, each "finished" row can't be deleted as it is found, because this affects the row number of all remaining rows in the column.
The following code would do the job:
Most of it will be familiar to the questioner. The main except is to keep a record of each row number that is moved to "Finished". This is done by pushing the row number onto an array. Then after all data has been examined and moved, there is a small loop that takes the row numbers recorded in the array and deletes the relevant row. The loop works from the highest row number to the lowest; this is so that the deletion of a row does not affect the row number of any remaining rows to be deleted.
function so_53305432() {
// set up the spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
// identify source and target sheets
var sourceSheet = ss.getSheetByName("Sheet1");
var targetSheet = ss.getSheetByName("Finished");
// get some variables to use as ranges
var sourcelastRow = sourceSheet.getLastRow();
var numColumns = sourceSheet.getLastColumn();
var targetLastRow = targetSheet.getLastRow();
// get data from the Source sheet
var sourceData = sourceSheet.getRange(1, 1, sourcelastRow, numColumns).getValues();
// set up some variables
var finishedRows = [];
var i = 0;
var x = 0;
var temp = 0;
// loop through column 15 (O) checking for value = "Finished"
for (i = 0; i < sourcelastRow; i++) {
// If value = Finished
if (sourceData[i][14] == "Finished") {
// define the target range and move the source row
var targetLastRow = targetSheet.getLastRow();
var target = targetSheet.getRange(targetLastRow + 1, 1);
sourceSheet.getRange(+i + 1, 1, 1, numColumns).moveTo(target);
// keep track of the source row number.
finishedRows.push(i);
}
}
// set up variables for loop though the rows to be deleted
var finishedLength = finishedRows.length;
var startcount = finishedLength - 1
// loop throught the array to delete rows; start with the highest row# first
for (x = startcount; x > -1; x--) {
// get the row number for the script
temp = +finishedRows[x] + 1;
// delete the row
sourceSheet.deleteRow(temp);
}
}
Trigger
The trigger needs to be revised. To do this:
1) Open the script editor, select Current Project Triggers. OnEdit should appear as an existing trigger with an event type of OnEdit.
2) Change "Choose which function to run" to the new function,
3) Change "Select Event Source" from Spreadsheet to "Time Driven".
4) Select "Type of time based trigger" = "Minutes Timer".
5) Select "Select Minute Interval" = , and select a time period and interval.
6) Save the trigger, and then close the Trigger tab
If "Every Minute" is found to be too often, then the Questioner could try "Every 5 minutes".

Google sheets- loop search column, select tow

I have a google spreadsheet that i have one last problem i cant seem to solve.
i added a button to this script, and when i press the button it triggers the AddClient function.
How can i make the script below loop down all rows in column 3 searching for the yes value, when it finds it, copy the row below it to sheet "client" and then stop?
function AddClient(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "SETUP" && r.getColumn() == 3 && r.getValue() == "yes") {
var row = r.getRow() + 1; // Add 1 to the active row
var targetSheet = ss.getSheetByName("client");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 2, 1, 4).copyTo(target, {contentsOnly:true}); //Only selecting column 2, then the following 4 columns
}
}
Edit*
Example document:
https://docs.google.com/spreadsheets/d/1DFbAp0IN8_UFv9u8kWiZBTxDogj4e7FnuAPzC0grDw0/edit?usp=sharing
Any help greatly appreciated!
Cheers,
Reiel
Since you have a static form the position of the informatin to be copied will not change
Since we know we want to copy over the data we won't need to do any validation of where we are so all of that stuff can go
Sheets have an appendRow method that take care of the bookkeeping involved with finding the last row
This allows us to simplify the script to the following:
function AddClient() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data = ss.getSheetByName("SETUP").getRange(25, 2, 1, 4).getValues()[0];
ss.getSheetByName("client").appendRow(data);
}
Edit:
To remove duplicates you could do the following:
function AddClient() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data = ss.getSheetByName("SETUP").getRange(25, 2, 1, 4).getValues()[0];
var clients = ss.getSheetByName("client").getDataRange().getValues();
if (!clients.filter(function(row) {return data.join("|") === row.join("|");})) {
ss.getSheetByName("client").appendRow(data);
}
}
Note that for the particular example there are some problems because the leading zero gets cut off. Sheets is a bit weird and sometimes tries to force the format to be a number even when you set the cells' formats to Text...

How to email a row in google sheets

First post. I have just started to learn to code (2 days in) but this is way over my head for now.
I want to be able to email the contents of a row (all cells in that row) by right clicking the grey cell that highlights the entire row and then choosing an option in the drop down menu that either allows me to enter an email address or sends to a specific address that is entered into the code (I will always be sending to the same address).
In fact, choosing the option in the drop down menu isn't a necessity, just how I envisage it but any solution that allows me to bypass copying the row and pasting it into an email would work.
Any help would be greatly appreciated.
Cheers
Ok, so I know this is probably terrible coding but as I said, until a couple of days ago I had never even read a code, let alone tried to write anything. So please be nice!
So with some serious googling, copy/pasting, editing for my needs etc and using the very little I know, here is what I have come up with:
function onEdit(event){
//transfer a row once specific value entered into a cell within that row (without deleting original row)
// assumes source data in sheet named Needed
// target sheet of move to named Acquired
// test column with yes/no is col 4 or D
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "Sheet1" && r.getColumn() == 5 && r.getValue() == "Email JM") {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("Sheet2");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).copyTo(target);
}
};
function onEditEmail(){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet2');
var startRow = 1; // First row of data to process
var numRows = 1; // Number of rows to process
// Fetch the range of cells
var dataRange = sheet.getRange(startRow, 1, numRows, 5)
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var message = row[0] + ", " + row[1] + ", " + row[2] + ", " + row[3]; //whole row
var subject = "New Order";
if (row[0] != ""){
MailApp.sendEmail("example#email.com", subject, message);
//delete row straight after it is sent
sheet.deleteRows(1, 1);
}
}
}
Row needing to be emailed is transferred to a different blank sheet when I enter "Email JM" at the end of the row and by setting data validation for this column this can be done by clicking the drop down arrow (no typing). My onEditEmail trigger is set to every minute, which sends all the columns I need from "Sheet2) and then immediately deletes the row so it is not resent a minute later.
The only problem is if 2 orders are entered inside a minute, but this is a very small chance of happening (although I guess it will sooner or later).
Please point out where I can improve this.
Cheers

Resources