Spreadsheet to calendar - google-apps-script

I manage bookings for my business on Google Spreadsheet. I discovered a wonderful script to update my calendar according to the bookings on the spreadsheet: https://github.com/Davepar/gcalendarsync
So one row = one booking = one event in the calendar.
It works perfectly, but I need a new feature.
As of right now, the script will update one specific calendar, and I would like to update two separate calendars, depending on the value of a specific column.
Example:
Let's say I manage a limo business, and I have two limos, a blue one and a red one.
I put the bookings in a worksheet, specifying for each row if i'll assign the blue or red limo.
I would like to have 2 separate calendars to check the availability of each one separately.
I'm no programmer, but I looked around and apparently this can be done fairly easily, by adding a function:
"if value of column C is "blue", assign this calendar ID, if value of column C ir "red", assign this calendar ID."
I just don't know exactly how to do it.
So, here is the script: https://raw.githubusercontent.com/Davepar/gcalendarsync/master/gcalendarsync.js
According to me, what has to be modified is this:
var calendarId = '<your-calendar-id>#group.calendar.google.com';
// Synchronize from spreadsheet to calendar.
function syncToCalendar() {
// Get calendar and events
var calendar = CalendarApp.getCalendarById(calendarId);
if (!calendar) {
errorAlert('Cannot find calendar. Check instructions for set up.');
I replaced by:
var calendarId1 = 'calendaridofbluelimo#group.calendar.google.com';
var calendarId2 = 'calendaridofredlimo#group.calendar.google.com';
// Synchronize from spreadsheet to calendar.
function syncToCalendar() {
// Get calendar and events
var calendar = CalendarApp.getCalendarById(calendarId1);
if (!calendar) {
errorAlert('Cannot find calendar. Check instructions for set up.');
I haven't modified much at all, I still need to add the function that determines which calendar has to be modified.
I tried to do it according to this response: Create events in multiple Google Calendars from Spreadsheet
But I couldn't figure it out (it is for a different script)
Note: I removed all the "Calendar to Spreadsheet" syncing function - I don't want a modification on the calendar to affect the spreadsheet.
Any help would be very appreciated!
Thanks

You need a conditional statement to check the value of the Spreadsheet cell. Something like the following could work:
var calendarId1 = 'calendaridofbluelimo#group.calendar.google.com';
var calendarId2 = 'calendaridofredlimo#group.calendar.google.com';
function syncToCalendar() {
// Open the Spreadsheet and get the active page.
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
// Then, you need to get the data range to use in the conditional.
// This gives you an array you can loop through and test each row.
// For the sake of illustration, we'll say it's in col B
var data = sheet.getRange("B:B").getValues();
// Test each row in the conditional
for(var i=0; i<data.length; i++) {
if(data[i] == "blue") {
var calendar = CalendarApp.getCalendarById(calendar1);
} else if(data[i] == "red") {
var calendar = CalendarApp.getCalendarById(calendar2);
} else if(!data[i]) {
errorAlert('Cannot find calendar. Check instructions for set up.');
}
// rest of your function...
}

Thanks a lot for your answer. I tested your code, and it doesn't seem to work. The script works, but it will only update the first calendar.
Here is the code I put (the Red/blue variable is in the column R:R)
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var data = sheet.getRange("R:R").getValues();
// Test each row in the conditional
for(var i=0; i<data.length; i++) {
if(data[i] == "Blue") {
var calendar = CalendarApp.getCalendarById(calendarId1);
} else if(data[i] == "Red") {
var calendar = CalendarApp.getCalendarById(calendarId2);
} else if(!data[i]) {
errorAlert('Cannot find calendar. Check instructions for set up.');
}
}
I tested a few things to eliminate potential issues. Like i put B:B (where there is no Rosa/Negro variable) instead of R:R, and the script doesn't work. So that's good.
Also, more interesting, i tried to switch the ids of both calendars. For example, i had
var calendarId1 = 'calendaridofbluelimo#group.calendar.google.com';
var calendarId2 = 'calendaridofredlimo#group.calendar.google.com';
And i put:
var calendarId2 = 'calendaridofbluelimo#group.calendar.google.com';
var calendarId1 = 'calendaridofredlimo#group.calendar.google.com';
What happened is that it added all the events in the Red Limo calendar, and it didn't delete the events off the blue calendar (like it's supposed to do). So I tried many other combinations (after deleting each event in the calendar) and I couldn't find the logic, sometimes it adds all the events to the blue calendar, sometimes all the events to the red.

Related

Is there a way to find if one or more events within a recurring series has been deleted?

I'm using the following code to check if there are cancelled events in a Google Calendar. It works fine for regular, non-recurring events. However, I run into issue when the user has deleted a single event in a recurring event series.
function checkForCancelledEvents(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Data Source");
ss.setActiveSheet(sheet);
//sort by date added
var lastRow = sheet.getLastRow();
var range = sheet.getRange(1,2,lastRow, 5)
var values = range.getValues();
//in the array the 3 position is the eventId, the 4 position is the calendar ID, I'm putting more into the array since I was playing around with also using the start date or time to solve the problem
//loop through all using Calendar.Events.get
for (i=1;i<values.length;i++) {
var splitEventID = values[i][3].toString().split("#")[0] //remove the calendarID from the eventID
if (Calendar.Events.get(values[i][4],splitEventID).status === "cancelled") {
//the below clears the content of the row that contains the cancelled events
//the range has to start at i + 1 since the values include the header row, but the for loop starts at 1 instead of 0 to prevent the API from calling with data from header row (which produces an error). So i + 1 gives the accurate row number for which the cancelled event lives
var clearRange = sheet.getRange(i + 1,1,1,7)
clearRange.clearContent()
} else {
//Logger.log("this is NOT cancelled")
}
}
}
The issue is that recurring events all contain the same eventID and calendarID. They have the same iCalUID as well. Recurring events do have a different id but non-recurring events don't have the same id format. I tried to use Calendar.event.list and add the timeMin of each event within the recurring series, however the event was still listed as confirmed even though it was deleted.
Is there a way to find if a single event within a recurring series has been deleted?
I believe your goal is as follows.
From Is there a way to find if a single event within a recurring series has been deleted?, you want to know whether even one of the events from the recurring events is removed using Google Apps Script.
In this case, I thought that when the method of "Events: instances" is used, your goal might be able to be achieved. The sample script is as follows.
Sample script:
Before you use this script, please enable Calendar API at Advanced Google services.
var calendarId = "###"; // Please set the calendar ID.
var eventId = "###"; // Please set the event ID.
var ar = [];
var pageToken = "";
do {
var res = Calendar.Events.instances(calendarId, eventId, {maxResults: 2500, showDeleted: true, pageToken});
if (res.items.length > 0) ar = [...ar, ...res.items.filter(({status}) => status == "cancelled")];
pageToken = res.nextPageToken;
} while (pageToken);
if (ar.length > 0) {
// When "ar" has the values, the events are deleted from the recurring events.
// do something.
}
In the above script, you can see the information of the deleted events at ar.
Note:
This is a simple sample script. So please modify this for your actual situation.
Reference:
Events: instances

Show/Hide Columns Using A Checkbox

I am a wanna-be developer who is trying to figure out how to hide a set of columns based off a checkbox being clicked.
Would anyone want to help with this code?
I have 12 different sheets(one for each month) and I would like to hide columns A-H with the checkbox in I being clicked.
Ideally I can implement on each individual sheet.
Link to spreadsheet
There are few ways one can do it.
Easiest and most recommended among all is to group those column and it will have pretty much same use which you're looking for.
If you're willing to use appscript for it. Here how it should be done:-
Open Script Editor from your spreadsheet.
Declare the onEdit simple trigger which will run every time when sheet will be edited.
So whenever you'll click on tickbox on I1 this function will trigger.
When a trigger fires, Apps Script passes the function an event object as an argument, typically called e.
For this object, we're gonna have the information we need to do our task, and also to restrict our operation to only to those months sheet and range belongs to it.
Here is the code, I tried my best to explain what happening in the code:-
function onEdit(e)
{
var rangeEdited = e.range; // This will us range which is edited
var sheetEdited = rangeEdited.getSheet().getName() // from range we can get the sheetName which is edited
var mySheets = ["Jan List","Feb List"] // Put all the month sheet name in this array where you want to have this functionality
var rowEdited = rangeEdited.getRow() // From Range we can get Row which is edited
var columnEdited = rangeEdited.getColumn() // From Range we can get Column which is edited
if(mySheets.indexOf(sheetEdited) > -1) // Now we want to only restrict the operation on those sheets,so if other sheet is edited, we shouldn't run our hide function
{
if(rowEdited === 1 && columnEdited === 9) // we're further restricting the range of operation to run this function when only I1 is edited that is Row:- 1 and Col:- 9
{
hideUnhide(sheetEdited) // calling our hide function within OnEdit and passing sheetName as an argument in it
}
}
}
function hideUnhide(sheetEdited) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ssname = ss.getSheetByName(sheetEdited) // accessing the sheet which is edited
var isItHidden = ssname.isColumnHiddenByUser(1) // checking if range is already hidden
if(isItHidden === false) // if No, hide that range
{
ssname.hideColumns(1, 6)
}
else // if Yes, unhide that range
{
var hideThisRange = ssname.getRange('A:H')
ssname.unhideColumn(hideThisRange)
}
}
Documentation:-
AppScript Events

Google Sheet Script Triggers issues

I was trying to create a script for google sheet that counts certain colors and it seems to be working perfectly, but when I tried to make a trigger for it, this is when I start to get problems.
I created the trigger with the OnEdit() event, but everytime I edit the table i get this exception "interval not found". So, if I want it to work correcly, I have to cut and paste the formula on the table (Because the trigger isn't working).
This is my code:
function countColor(countRange) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var range = sheet.getRange(countRange);
var rangeWidth = range.getWidth();
var bg = range.getBackgrounds();
var score = 0;
for (var i = 0; i < rangeWidth; i++)
{
if (bg[0][i] == "#38761d")
{
score += 1;
}
else if (bg[0][i] == "#ffff00")
{
score = score + 0.5;
}
else if (bg[0][i] == "#274e13")
{
score += 1.5;
}
}
return score;
};
1 - So I want to know why the trigger isn't working correcly.
2 - I would like also to know, how to do this--> when I copy and paste the formula in another cell, these correspond to the row in which I have pasted it. (just like normal tables do). For example is the range was a1:b1 in the first cell, when I copy it on the second cell it would be a2:b2
In most cases time based triggers cannot be used with functions that accept parameters. In your case the parameter countrange will probably be populated with the triggers event object which will likely result in some very weird behavior.
Functions that you write to be used in cells are called custom functions in google apps script and you cannot run custom function from a trigger.
Custom Functions

How to update a single event without duplicating the rest of the events

I am currently using app script to sync my google sheet to google calendar. The process is quite simple, my script only takes a date and title from the spreadsheet and creates an all day event on that date with that title.
The problem I am facing is that if I accidentally key in the wrong date or I have to update one of the dates inside the spreadsheet, on running the scheduleShifts function again, all the events are created again which results in many duplicate events that I did not intend to be there. I'm trying to find a solution that helps either updates the title of the event or deletes the event and create a new one in the case where the date that is in the spreadsheet is wrong.
It also isn't very efficient to update the data in the spreadsheet and then update the calendar because in the event where quite a few dates or titles have to be changed, it would take quite a bit of time to change them all in the calendar. It would also be very troublesome to delete the current calendar, create a new one, copy that id into the spreadsheet and then update everything again.
This is what my current code looks like:
function scheduleShifts()
{
/*Identify calendar*/
var spreadsheet = SpreadsheetApp.getActiveSheet();
var calendarId = spreadsheet.getRange("C1").getValue();
var eventCal = CalendarApp.getCalendarById(calendarId);
/*Import data from the spreadsheet*/
var signups = spreadsheet.getRange("C4:F73").getValues();
/*Create events*/
for (x=0; x<signups.length; x++)
{
var shift = signups[x];
var title = shift[0];
var date = shift[3];
eventCal.createAllDayEvent(title, date);
}
}
/*Make the script shareable for others to use*/
function onOpen()
{
var ui = SpreadsheetApp.getUi();
ui.createMenu('Sync to Calendar')
.addItem('Schedule shifts', 'scheduleShifts')
.addToUi();
}
I have tried to avoid duplicating events by retrieving all the events with Advanced Calendar Service, pushing their titles into an array and then verifying with IndexOf. However, I am unsure if this method will work if the title stays the same while there is an update in the date of that event.
The code that I referenced from to do this:
var existingEvents=Calendar.Events.list(calendarId);
var eventArray=[];
existingEvents.items.forEach(function(e){eventArray.push(e.summary)});
for (x=0; x<signups.length; x++) {
var shift = signups [x];
var startTime = shift[0];
var endTime = shift[1];
var inspector = shift[2];
if(eventArray.indexOf(inspector)==-1){
eventCal.createEvent(inspector, startTime, endTime);
}else{
Logger.log('event exists already');
}
}
If anyone needs more info feel free to ask in the comments, your help would be greatly appreciated.
You have developed a script to create events using data from a sheet.
You want to be able to update previously created events while avoiding creating duplicates.
Step 1. Avoid creating duplicates:
In order to avoid creating duplicates, you could make the script write the corresponding eventId when each event has been created. This way, next time the script runs, it can check whether the eventId is populated (in which case the event already exists), and only create the event if it doesn't exist. It could be something like this (in this sample, the eventIds are written to column G):
function scheduleShifts() {
const spreadsheet = SpreadsheetApp.getActiveSheet();
const calendarId = spreadsheet.getRange("C1").getValue();
const eventCal = CalendarApp.getCalendarById(calendarId);
const signups = spreadsheet.getRange("C4:G7").getValues();
for (let x = 0; x < signups.length; x++) {
const shift = signups[x];
const title = shift[0];
const date = shift[3];
const eventId = shift[4];
if (eventId == "") { // Check the event doesn't exist
const newEvent = eventCal.createAllDayEvent(title, date);
const newEventId = newEvent.getId();
spreadsheet.getRange(4 + x, 7).setValue(newEventId); // Write new eventId to col G
}
}
}
Step 2. Update events:
Regarding the update process, I'd suggest you to install an onEdit trigger, either manually or programmatically. This this action requires authorization, a simple onEdit trigger would not work here (see Restrictions).
This way, you can use the event object to only update the event corresponding to the row that was edited, thus avoiding having to update all events every time, which would make the process very inefficient. The update process itself would consist on calling setTitle and setAllDayDate.
The function fired by the onEdit trigger could be something like this:
function updateEvent(e) {
var editedRow = e.range.getRow();
var editedData = e.source.getActiveSheet().getRange(editedRow, 3, 1, 5).getValues()[0];
var eventId = editedData[4];
try {
var event = CalendarApp.getEventById(eventId);
event.setTitle(editedData[0]);
event.setAllDayDate(editedData[3]);
} catch(err) {
console.log("There was a problem updating the event. This event might not exist yet.");
}
}
Notes:
You may not want to manually set the range (C4:G73) if the number of events might vary. You can use methods like getLastRow() no make this range dynamic, based on the spreadsheet content.
The eventId that are used here, corresponding to Class CalendarEvent, are not identical to the API Event resource. Take this into account in case you use the Advanced Service.

onEdit installable trigger not firing when defining variable through array object

Hej!
I have a script that is supposed to be triggered onEdit.
onEdit() as a simple trigger is not an option for me, since the script needs authorizations. When setting an installable trigger with the editor the trigger is only fired when a specific variable is manually defined, but not when it is defined with an array.
Since I have no clue where the problem could be (I have tried to comment out the code part by part, but it always ends up not working when comming to the last part.
function myFunction() {
//Source Sheet for different sheets
var ss = SpreadsheetApp.getActiveSpreadsheet();
var activeSheet = ss.getActiveSheet();
var activeRange = ss.getActiveRange();
var activeRow = activeRange.getRowIndex();
var CategoryValues = ss.getSheetValues(activeRow, 4, 1, 14);
var ID = activeSheet.getRange(activeRow,2).getValue();
var red = '#e06666'
// Defining the sourceWeekNumber in the sourceSheet with a while loop
var sourceWeeks = activeSheet.getRange(1,1,activeRow).getValues();
sourceWeeks.reverse();
var sourceWeekColor = activeSheet.getRange(1,1,activeRow).getBackgrounds();
sourceWeekColor.reverse();
var sourceWeekNumber = [];
var sourceWeekIndex=1;
while (sourceWeekColor[sourceWeekIndex-1]!=red) {
if (sourceWeekColor[sourceWeekIndex]==red) {
sourceWeekNumber.push(sourceWeeks[sourceWeekIndex])
}
sourceWeekIndex++;
}
//Target sheet is always mastersheet
var ts = SpreadsheetApp.openById('ID').getSheetByName('mastersheet');
//Finding the right spot in mastesheet
//Validate the weeknumber
var validWeek = ts.getRange(2, 1, ts.getLastRow()).getValues();
var rowIndex = [];
//-----> var sourceWeekNumber = 'v50'; <-----
// When defining this variable manually, onEdit works fine.
// When defining sourceWeek with the while loop above, the onEdit does not trigger.
// Find the right week and it's row in the mastersheet
var validWeekIndex = 1;
while (validWeek[validWeekIndex] != sourceWeekNumber) {
if (validWeek[validWeekIndex] == sourceWeekNumber) {
var validID = ts.getRange(validWeekIndex+3,2, ts.getLastRow()).getValues();
rowIndex.push(validWeekIndex);
}
validWeekIndex++;
}
}
I have tried different things, always using Logger.log() or Browser.msgBox() trying to identify the problem. Any ideas or workarounds? I have been stuck with this for 3 days now and can't find any solution.
I have prepared a sample sheet as well, if it is needed then I can edit it in.
Thanks in advance!
Edit:
Here is the link to the sample sheet: https://docs.google.com/spreadsheets/d/1xCZur6gpfsQFPtwKcTl0XPPfG8zlTqGVlm1GZZ8X1xM/edit?usp=sharing
I figured it out.
It was of course not the trigger, but different factors that made me think it was:
Trigger notifications: I was sure I set notification to immidiatly, but it was set to 4pm every day. That's the reason I didn't get the failure notifications.
The while loop: Because the while loop can't identify when the value from the 1D array turns up in the 2D array, it keeps running and times-out. Therefore the script runs for 5 minutes without possibility to cancel it.
I learned that Google got a quota of 30 min / day of script running time for triggers, so that's why I could only run 6 failed scripts before the quota was full and the script woulnd't run at all until the next day without me realizing it was an issue.
Edit: For future references, I simply used sourceWeekNumber.toString() to be able to get it to match with the other array, comparing a string instead of and object inside of an array.