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)
Related
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!");
}
}
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;
}
}
}
I am pulling data from a google sheets which looks like this:
Now, I want to generate google slides for ONLY rows where the Timestamp column is between Last Deadline and Next Deadline. In the example, it would pull the record in A2:B2 as the Timestamp is between these two dates. I added this logic to my script but when I run it, it does not generate the slide, i.e. it does behave as expected but neither do I get an error. What could it be?
function generateSlides_master()
{
var dataSpreadsheetUrl = "https://docs.google.com/spreadsheets/d/1hhf";
var ss = SpreadsheetApp.openByUrl(dataSpreadsheetUrl);
var deck = SlidesApp.getActivePresentation();
var sheet = ss.getSheetByName('Form_Responses');
var values = sheet.getRange('A2:N20000').getValues();
var slides = deck.getSlides();
var templateSlide = slides[1];
var last_deadline = sheet.getRange('P4:P4').getValues();
var next_deadline = sheet.getRange('P2:P2').getValues();
values.forEach(function(page){ //for each row in google sheets
if(page[0]){
if (page[0] > last_deadline && Work_Week<= next_deadline ){ //THIS IS NOT WORKING AS EXPECTED!
var Work_Week = Utilities.formatDate(page[0], "GMT", "MM/dd/yyyy");
var Email = page[1];
templateSlide.duplicate(); //duplicate the template page
slides = deck.getSlides(); //update the slides array for indexes and length
newSlide = slides[2]; // declare the new page to update
var shapes = (newSlide.getShapes());
shapes.forEach(function(shape){
shape.getText().replaceAllText('{{Email}}',Email);
});
presLength = slides.length;
newSlide.move(presLength);
}
}// end our conditional statement
}); //close our loop of values
//Remove the template slide
//templateSlide.remove();
}
You've defined last_deadline and next_deadline to be 2-dimensional arrays, so your if-statement isn't actually checking against the dates. Use getValue() instead to get the individual values.
var last_deadline = sheet.getRange('P4').getValue();
var next_deadline = sheet.getRange('P2').getValue();
I also think you meant page[0] <= next_deadline, instead of comparing against Work_Week.
Here's a heavily edited example that will simply log the timestamp rather than creating a slide.
function generateSlides_master() {
var dataSpreadsheetUrl = "https://docs.google.com/spreadsheets/d/1hhf";
var ss = SpreadsheetApp.openByUrl(dataSpreadsheetUrl);
var sheet = ss.getSheetByName('Form_Responses');
var values = sheet.getRange('A2:N20000').getValues();
var last_deadline = sheet.getRange('P4').getValue();
var next_deadline = sheet.getRange('P2').getValue();
values.forEach(function(page) { //for each row in google sheets
var timestamp = page[0];
if (timestamp) {
if (timestamp > last_deadline && timestamp<= next_deadline) {
Logger.log('Create slide ' + timestamp);
}
}
});
}
Im trying to make a spreadsheet to manage my money/expenses and have run into a problem trying to automate my process
I want to make a piece of code that runs every time a cell has been edited in the sheet.
When triggered, i want it to calculate ssum, lsum and betal(in the loop), and then put it into 3 different cells. The code behaves as expected, but the onedit trigger doesnt work.
This is my code:
function regnudbetalprocent() {
var betal = 0;
var i = 1;
var app = SpreadsheetApp;
var ss = app.getActiveSpreadsheet();
var activeSheet = ss.getActiveSheet();
var sum = activeSheet.getRange(18, 5).getValue();
var ssum;
var lsum;
var ssumori = activeSheet.getRange(3, 8).getValue();
var lsumori = activeSheet.getRange(4, 8).getValue();
var fuld = activeSheet.getRange(18, 2).getValue();
while(betal < sum){
ssum = ((ssumori - fuld / 2) / 100) * i;
lsum = ((lsumori - fuld / 2) / 100) * i;
betal = ssum + lsum;
i++;
}
if (betal > sum) {
var output = [
[ssum,lsum],
["Samlet",betal]
]
return output;
}
}
The output variable sets the neighbouring cells accordingly from where the function is called
I've tried with setValue and clearContent, but it i cant edit outside the cell from where the function is called. I've used Edit -> current project's triggers to add an onEdit trigger, which increments each time i edit the sheet, but nothing happens.. I'm burned out
Can someone guide me? how do i get what i want?
The values for using with the calculation are the constant cells of "B18", "E18", "H3" and "H4".
You want to run the script when one of above cells is edited. You want to use OnEdit event trigger.
You want to put the result value to the cells of "D21", "E21" and "E22".
Your calculation has no issues.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Modification points:
In your script, I think that the simple trigger can be used instead of the installable trigger.
From your replying, about "Exception: You do not have permission to call setValue (line 7).", I think that you might be using the function of regnudbetalprocent() as the custom function. In this case, such error occurs.
You can know the coordinate of the edited cell using the event object.
When above points are reflected to your script, it becomes as follows.
Modified script:
The function of onEdit is used as the simple trigger. So in order to run the script, please manually edit one of cells "B18", "E18", "H3" and "H4". By this, the script is run and retrieved values from the cells of "B18", "E18", "H3" and "H4", and the calculated result is put to the cells of "D21:E22".
function onEdit(e) {
var a1Notation = e.range.getA1Notation(); // Added
if (a1Notation != "B18" && a1Notation != "E18" && a1Notation != "H3" && a1Notation != "H4") return; // Added
var betal = 0;
var i = 1;
var app = SpreadsheetApp;
var ss = app.getActiveSpreadsheet();
var activeSheet = ss.getActiveSheet();
var sum = activeSheet.getRange(18, 5).getValue();
var ssum;
var lsum;
var ssumori = activeSheet.getRange(3, 8).getValue();
var lsumori = activeSheet.getRange(4, 8).getValue();
var fuld = activeSheet.getRange(18, 2).getValue();
while(betal < sum){
ssum = ((ssumori - fuld / 2) / 100) * i;
lsum = ((lsumori - fuld / 2) / 100) * i;
betal = ssum + lsum;
i++;
}
if (betal > sum) { // Modified
var output = [[ssum,lsum], ["Samlet",betal]];
var r = e.range.getSheet().getRange("D21:E22");
r.clearContent(); // This line might not be required.
r.setValues(output);
SpreadsheetApp.flush(); // This line might not be required.
}
}
References:
Simple Triggers
Event Objects
offset(rowOffset, columnOffset, numRows, numColumns)
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.