Google Apps script. For loop is not working - javascript

I tried to write an Apps script for sending emails for those who forgot to update status report using google form.
It's my 1st script and I tried to fix it for almost 5 hours unsuccessfully.
Here is code:
function sendEmails() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("summary");
var lastRow = sheet.getLastRow();
for ( var i=2; i<=lastRow; i++){
var currentEmail = sheet.getRange(i,11).getValue();
var templateText = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("emailbody").getRange(1,1).getValue();
var emailRecipient = sheet.getRange(i,2).getValue();
var messageBody = templateText.replace("{name}",emailRecipient);
var oldCheck = sheet.getRange(i,12).getValue();
if (oldCheck = "Old"){
MailApp.sendEmail(currentEmail,"Reminder: Fill the weekly status update form",messageBody);
}
else {
continue;
}
}
}
It sends only 1 email for the first "old" check.

Explanation:
Issue:
You want to compare the value of oldCheck with "Old" and not assign "Old" to oldCheck. You need to use == instead of =.
Improvements:
It is computationally expensive to use getValue inside a for loop. Instead use getValues before the for loop and work with the arrays. In this way, you eliminate reduntant API calls that make your script heavy especially when i increases.
Solution:
function sendEmails() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getSheetByName("summary");
var data = sheet.getDataRange().getValues();
var templateText = spreadsheet.getSheetByName("emailbody").getRange(1,1).getValue();
for (var i=1; i<data.length; i++){
var currentEmail = data[i][12];
var emailRecipient = data[i][3];
var messageBody = templateText.replace("{name}",emailRecipient);
var oldCheck = data[i][13];
// be careful! == instead of =
if (oldCheck == "Old"){
MailApp.sendEmail(currentEmail,"Reminder: Fill the weekly status update form",messageBody);
}
else {
continue;
}
}
}

Related

How to deal with merged cells with GAS?? and how could I fix the error?

I am a very beginner, so I am sorry for asking a low-leveled question.
I want to drag out values on each merged cell (8 cells for each and listed down on a spreadsheet), and want to put them in a pull-down of google form.
I just tried to see values on the sheet but found out there was an error.
If someone know how to fix this, I am glad to hear that.
Error
TypeError: Cannot read property 'getRange' of undefined
updateFormList # Code.gs:13
function updateFormList() {
var formId = '---'
var sheetId = '---'
var sheetName = 'Sheet1'
var range = sheet.getRange("A3:A10");
var mergedRanges = range.getMergedRanges();
for (var i = 0; i < mergedRanges.length; i++) {
Logger.log(mergedRanges[i].getA1Notation());
Logger.log(mergedRanges[i].getDisplayValue());
}
}
To fix the error you have to assign a Class Sheet object to the variable named sheet.
function updateFormList() {
var formId = '---'
var sheetId = '---'
var sheetName = 'Sheet1'
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); // this works in scripts bounded to spreadsheet
var sheet = spreadsheet.getSheetByName(sheetName);
var range = sheet.getRange("A3:A10");
var mergedRanges = range.getMergedRanges();
for (var i = 0; i < mergedRanges.length; i++) {
Logger.log(mergedRanges[i].getA1Notation());
Logger.log(mergedRanges[i].getDisplayValue());
}
}
Regarding
I want to drag out values on each merged cell (8 cells for each and listed down on a spreadsheet), and want to put them in a pull-down of google form.
Merged cells might be helpful for data visualization, i.e. creating a report, but they might need more complex scripts and formulas. As a "very beginner" and when programming efficiency be important, whenever you can avoid having merged cells in your spreadsheets, specially if you will have to use formulas and scripts on their content.
Resources
https://developers.google.com/apps-script/guides/sheet
Try this:
function updateFormList() {
var formId = '---'
var sheetId = '---'
var sheetName = 'Sheet1'
const sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
var range = sheet.getRange("A3:A10");
var mergedRanges = range.getMergedRanges();
for (var i = 0; i < mergedRanges.length; i++) {
Logger.log(mergedRanges[i].getA1Notation());
Logger.log(mergedRanges[i].getDisplayValue());
}
}

Apps Script: Batch Create Calendar Events without Creating Duplicates

I'm working on a script that takes a list of tasks from Google Sheets and automatically creates events on Google Calendars, putting the event on the Calendar of the team member to whom the task is applied.
All that is working, now my issue is that I want to be able to run the script over the whole range of cells as tasks are added without creating duplicate events.
I tried using the method described here:
https://stackoverflow.com/a/57785525/12412425
BUT this only checks the event/task name, which doesn't work for me because I will likely be assigning tasks with the same name, but different dates, etc.
My thought was to make an extra column where, after an event is created, the code writes an "X" in the cell, and as the code executes, it checks this cell, and if it already has an "X", the row will be skipped. But I'm struggling to wrap my head around how to identify the cell I need to write to.
Basically, I would want to iterate through the rows of the specified range, and if the "CalendarID" cell contains a value, you can assume an event was created, therefore in the next cell over, write an "X". The rest would just be a simple if-statement to check for the "X".
My input data
Can anyone advise on how to do this? Or an alternate strategy?
(This is my first attempt at using Apps Script/JS)
Many thanks.
Current code:
function scheduleTasks() {
var spreadsheet = SpreadsheetApp.getActiveSheet();
var myRange = spreadsheet.getRange("E9:H").getValues();
for (x=0; x<myRange.length;x++)
{
var myRow = myRange[x];
var calendarID = myRow[3];
if(calendarID){
var eventCal = CalendarApp.getCalendarById(calendarID);
var task= myRow[0];
var startTime = myRow[1];
var endTime = myRow[2];
var existingEvents=Calendar.Events.list(calendarID);
var eventArray=[];
existingEvents.items.forEach(function(e){eventArray.push(e.summary)});
if(eventArray.indexOf(task)==-1){
eventCal.createEvent(task, startTime, endTime);
}else{
Logger.log('event exists already');
}
}
}
Where (I think) I'm trying to go with it (See comments)
function scheduleTasks() {
var spreadsheet = SpreadsheetApp.getActiveSheet();
var myRange = spreadsheet.getRange("E9:H").getValues();
for (x=0; x<myRange.length;x++)
{
var myRow = myRange[x];
var calendarID = myRow[3];
if(calendarID){
//if cell in "Scheduled" column, row x is false
var eventCal = CalendarApp.getCalendarById(calendarID);
var task= myRow[0];
var startTime = myRow[1];
var endTime = myRow[2];
eventCal.createEvent(task, startTime, endTime);
//write "X" in cell in "Scheduled" column, Row x
}
}
}
In your situation, how about the following modification?
Modified script 1:
In this sample script, the value of X is put in the "Scheduled" column.
function scheduleTasks() {
var spreadsheet = SpreadsheetApp.getActiveSheet();
var myRange = spreadsheet.getRange("E9:I" + spreadsheet.getLastRow()).getValues();
var rangeList = [];
for (x = 0; x < myRange.length; x++) {
var myRow = myRange[x];
var calendarID = myRow[3];
var scheduled = myRow[4];
if (calendarID && scheduled != "X") {
var eventCal = CalendarApp.getCalendarById(calendarID);
var task = myRow[0];
var startTime = myRow[1];
var endTime = myRow[2];
eventCal.createEvent(task, startTime, endTime);
rangeList.push(`I${x + 9}`)
}
}
if (rangeList.length == 0) return;
spreadsheet.getRangeList(rangeList).setValue("X");
}
When this script is run, when "calendarID" is existing and "Scheduled" column has no value of "X", the script in the if statement is run. In this case, in order to put the value of "X", I used RangeList.
Modified script 2:
In this sample script, the value of the event ID is put in the "Scheduled" column.
function scheduleTasks() {
var spreadsheet = SpreadsheetApp.getActiveSheet();
var lastRow = spreadsheet.getLastRow();
var myRange = spreadsheet.getRange("E9:I" + lastRow).getValues();
var scheduledAr = [];
for (x = 0; x < myRange.length; x++) {
var myRow = myRange[x];
var calendarID = myRow[3];
var scheduled = myRow[4];
if (calendarID && !scheduled) {
var eventCal = CalendarApp.getCalendarById(calendarID);
var task = myRow[0];
var startTime = myRow[1];
var endTime = myRow[2];
var event = eventCal.createEvent(task, startTime, endTime);
scheduledAr.push([event.getId()]);
} else {
scheduledAr.push([scheduled]); // Modified
}
}
spreadsheet.getRange("I9:I" + lastRow).setValues(scheduledAr);
}
When this script is run, when "calendarID" is existing and "Scheduled" column has no value, the script in the if statement is run. In this case, in order to put the value of event ID, I used setValues.
References:
getRangeList(a1Notations)
setValues(values)

How do i shorten time this GAS code(search correct column and data paste)

https://i.stack.imgur.com/7VAJk.png
i want to copy data from "dB" sheet A5:A29 and paste to correct column.
so i use the script to find the correct column.
there range B2:CX2 have 0(not-correct) or 1(correct) value, so i use 'for' & 'if'
BUT!! It's too delay!!
i use console.time() and i get 25909ms(timecheck2 value) !!!
please help me.....
here is my code
function save(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('dB');
console.time("timecheck1");
//find last row
var copyrangeO = sheet.getRange(5,1,25,1).getValues();
var lastrowO = copyrangeO.filter(String).length;
var copyrange = sheet.getRange(5,1,lastrowO,1);
console.timeEnd("timecheck1");
//my dB data start "B2".
var cv = 1;
//find correct value(1). B2 ~ CX2 (#100)
console.time("timecheck2");
for (var i=2; i<101;i++){
if(sheet.getRange(2,i).getValue()===1){
cv = i;
}
}
console.timeEnd("timecheck2");
//if data isn't correct, cv===1. so error msg print.
console.time("timecheck3");
if(cv ===1){
Browser.msgBox("ERROR")
}else {
//data copy and paste.
var columnToCheck = sheet.getRange(4,cv,1000).getValues();
var lastrow = getLastRowSpecial(columnToCheck);
var pasterange = sheet.getRange(lastrow+4,cv);
copyrange.copyTo(pasterange, SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
Browser.msgBox(lastrowO + " saved!");
}
console.timeEnd("timecheck3");
}
Issue:
If I understand your situation correctly, you want to find the cell in B2:CX2 in which the value is 1, but the script is taking too much time for this.
The problem here is that you are using getRange and getValue in a loop (sheet.getRange(2,i).getValue()===1). This greatly increases the amount of calls to the Sheets service, which slows down your script, as you can see at Minimize calls to other services.
Solution:
In that case, I'd suggest doing the following:
Get the values from all columns at once using getValues().
Use findIndex to get the column index for which value is 1.
In order to do that, replace this:
var cv = 1;
//find correct value(1). B2 ~ CX2 (#100)
console.time("timecheck2");
for (var i=2; i<101;i++){
if(sheet.getRange(2,i).getValue()===1){
cv = i;
}
}
With this:
var ROW_INDEX = 2;
var FIRST_COLUMN = 2; // Column B
var LAST_COLUMN = 102; // Column CX
var columnValues = sheet.getRange(ROW_INDEX, FIRST_COLUMN, 1, LAST_COLUMN-FIRST_COLUMN+1).getValues()[0];
var cv = columnValues.findIndex(columnValue => columnValue === 1) + FIRST_COLUMN;
Note:
If there's no cell in the range with value 1, findIndex returns -1 which, added to FIRST_COLUMN, results in 1. That's appropriate for your current script, but won't work if the FIRST_COLUMN stops being 2, so be careful with this (either change the condition if(cv ===1){ to something less strict, or don't assign the resulting value to cv if findIndex returns -1).
The function will spend most of its time in the for loop because it repeats the Range.getValue() call many times. You can speed things up quite a bit by getting all values with one Range.getValues() call, like this:
let cv = 1;
console.time("timecheck2");
sheet.getRange('B2:B100').getValues().flat()
.some((value, index) => (cv = 2 + index) && value === 1);
console.timeEnd("timecheck2");
Note that this is not a cleanest way of finding cv, but it should help illustrate why you have a performance issue. You may want to do a complete rewrite of the code, using declarative instead of imperative style.
Try this:
I don't know what you're doing in the save because to did not supply the helper function code.
function save(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('dB');
var vs0 = sh.getRange(5,1,25,1).getValues();
var lr0 = vs0.filter(String).length;
var crg = sh.getRange(5,1,lr0,1);
var cv = 1;
const vs1 = sh.getRange(2,2,1,99).getValues().forEach((c,i) => {
if(c == 1)cv = i + 2
})
if(cv == 1){
Browser.msgBox("ERROR")
}else {
var vs2 = sh.getRange(4,cv,1000).getValues();
var lastrow = getLastRowSpecial(vs2);
var drg = sh.getRange(lastrow+4,cv);
crg.copyTo(drg, SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
Browser.msgBox(lr0 + " saved!");
}
}

Pop up dialog box is value is duplicate

I am trying to create a google script that will scan through a specific column of one google sheet and check if any new additions are a duplicate. I have come up with the following, but it isn't working.
function hasDuplicates() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Customer and form Details");
var array = sheet.getRange('B2:B' + lastRow).getValues();
var valuesSoFar = [];
for (var i = 0; i < array.length; ++i) {
var value = array[i];
if (value in valuesSoFar) {
SpreadsheetApp.getUi().alert('Hello, world!');
}
else{
valuesSoFar.push(value);
}
}
}
As far as Im aware the above code creates an array called array and populates it with the specific range I am interested in. I then create a new, empty, array called valuesSoFar. The code then loops through the column and sequentially checks if that item has already been seen before, if so it gives you an alert. If not it adds it to the list of new items and keeps going.
It looks a bit like you are using the online documentation code.
But the following assignment is missing:
var lastRow = sheet.getLastRow();
or just replace lastRow with sheet.getLastRow()
Update:
I assume that the following solution should work if you actually have any duplicates in your column B:
function hasDuplicates() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Customer and form Details");
var array = sheet.getRange('B2:B' + sheet.getLastRow()).getValues();
var valuesSoFar = [];
for (var i = 0; i < array.length; ++i) {
var value = array[i][0];
if (value in valuesSoFar) {
SpreadsheetApp.getUi().alert('Hello, world!');
} else {
valuesSoFar.push(value);
}
}
}
If it isn't working yet, I am sorry.
As long as you can not provide any information on what kind of error you are running into, I will not look into this anymore.

GAS Spreadsheet avoid getting duplicates by marking as "SENT", not working?

I have this script in Google Spreadsheet and it fetches all the rows marked "READY" just fine, then it sets the value in column "W"(23) to "SENT", and then I am trying to avoid fetching duplicates, by marking the column as "SENT" but then when I run the code again, it ignores the "SENT" that it just pasted? What is wrong here?
var ss = SpreadsheetApp.openById("12y85GmJ94s6k3213j2nGK8rFr0GOfd_Emfk8WHu_MUQ");
var stitchSheet = ss.getSheetByName("Sheet8");
var orderSheet = ss.getSheetByName("Sheet1");
var SENT = "SENT";
function getOrders() {
var range = orderSheet.getDataRange();
var orders = range.getValues();
for (var i = 1; i < orders.length; i++) {
var row = orders[i];
var status = row[1];
var order = row[4];
var name = row[5];
var system = row[22];
if(system != SENT){
if(status.toString() === 'READY'){
orderSheet.getRange(i,23).setValue(SENT);
stitchSheet.appendRow([order,name]);
}
}
}
}
Your code is fine, so there must be a logical error somewhere. I've noticed that you made var i in the for loop 1. I do not know if this is intentional or not, but the index of arrays pretty much always starts with 0 in most programming languages, which means that you'll start at row 2 of your sheet, not row 1.
Finding logical errors
To find logical errors you need to learn how to use the debugger console in the script editor.
Put breakpoints on the lines I mark with a star below:
var ss = SpreadsheetApp.openById("12y85GmJ94s6k3213j2nGK8rFr0GOfd_Emfk8WHu_MUQ");
var stitchSheet = ss.getSheetByName("Sheet8");
var orderSheet = ss.getSheetByName("Sheet1");
var SENT = "SENT";
function getOrders() {
var range = orderSheet.getDataRange();
* var orders = range.getValues();
for (var i = 1; i < orders.length; i++) {
* var row = orders[i];
var status = row[1];
var order = row[4];
var name = row[5];
* var system = row[22];
if(system != SENT){
if(status.toString() === 'READY'){
orderSheet.getRange(i,23).setValue(SENT);
stitchSheet.appendRow([order,name]);
}
}
}
}
Start the debugger and it will stop at the first breakpoint. Inspect what value range has. Expanding This should let you find orderSheet and SENT (since they are outside the function and should be in the scope). If not you've got a problem here.
Go to the next breakpoint and inspect orders, it should have an array of arrays now. You can inspect that you've got the right values, if not, skip forward to the next breakpoint and look at what row is.

Categories