Override existing google calendar event when new event is created via script - javascript

I am using a Google Form to schedule appointments based on open events on my Google Calendar. I've sectioned off time slots on the calendar and if the title of the event is "Open", then the time slot can be used to create an event. I would next like to know if it's possible to override and delete the existing open event with the new appointment that is created.
Here is the code I am using to push events to my calendar as they are created in the form:
var calendarID = "";
var startDtID = 5;
var endDtID = 5; // Column containing date/time
var nameID = 2; // Column containing name
var emailID = 3; // Column containing email
var phoneID = 4; // Column containing phone
var formTimeStampID = 1; // column containing timestamp
function getLatestAndSubmitToCalendar() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var lr = rows.getLastRow();
var startDt = sheet.getRange(lr,startDtID,1,1).getValue();
var endDt = sheet.getRange(lr,endDtID,1,1).getValue(); // remove hour and minute for the start/end time
var desc = "Comments :"+sheet.getRange(lr,phoneID,1,1).getValue(); // set comments as description, add in timestamp and submission
var title = sheet.getRange(lr,nameID,1,1).getValue()+" - "+sheet.getRange(lr,emailID,1,1).getValue(); // create title using name and email
createEvent(calendarID,title,startDt,endDt,desc);
}; // run create event function
function createEvent(calendarId,title,startDt,endDt,desc) {
var cal = CalendarApp.getCalendarById(calendarID);
var start = new Date(startDt);
var end = new Date(endDt);
var event = cal.createEvent(title, start, end, {
description : desc
}); // set options for event
};
The CalendarEvent class has a method to delete events through deleteEvent(), but I am confused as to how I can determine the right event to be deleted. How can I match up the date and time of the current event with that of the one that already exists so I can override it?

You'll need to call calendar.getEvents() first, then loop through the event(s) you want to delete, and call .delete() on each of them. Here's the API link: https://developers.google.com/apps-script/reference/calendar/calendar#getEvents%28Date,Date,Object%29
Something like this:
var now = new Date();
var twoHoursFromNow = new Date(now.getTime() + (2 * 60 * 60 * 1000));
var events = CalendarApp.getDefaultCalendar().getEvents(now, twoHoursFromNow,
{search: 'meeting'});
for(int i = 0; i < events.length; i++){
if this is the right event (compare title, times, etc?){
events[i].deleteEvent();
}
}
In this case, you'll probably want to compare the descriptions. Also note that you can pass options to the getEvents call; one you can use to filter during the search is the search parameter like I did above. It filters the returned events on that text.
EDIT: Ideally, you'll want to use this: https://developers.google.com/apps-script/reference/calendar/calendar#getEventSeriesById%28String%29
That gets the event by the ID; I answered with the assumption you didn't have the ID stored anywhere though. If you do, all the better, and you'll be certain you're replacing the right event.

It works! Here is my completed code for adding an event and deleting one that occurs at the same time.
function createEvent(calendarId,title,startDt,endDt,desc) {
var cal = CalendarApp.getCalendarById(calendarID);
var start = new Date(startDt);
var end = new Date(endDt);
var event = cal.createEvent(title, start, end, {
description : desc
});
var events = cal.getEvents(start, end, {
search: 'open'
});
for (i in events){
events[i].deleteEvent();
}
};

Related

Google Apps Script (Spreadsheet) - selecting an array in spreadsheet based on a condition in cells

I am trying to select an array from google sheets to create google calendar events based on that. The code chunk below runs just fine and gets the job done. But I want to be able to only select the range that has value of "select" in their column D.
I know it is probably a very easy answer but I am new to JS.
function calendarSync() {
var spreadSheet = SpreadsheetApp.getActiveSheet;
var eventCal = CalendarApp.getCalendarById(calendarId);
// Below instead of selecting the entire range I only need the rows that have a value of "select" in their D cell.
var eventArray = spreadSheet.getRange("A1:D100").getValues();
for (x=0; x<eventMatrix.length; x++){
var calEvent = eventArray[x];
var eventName = calEvent[0]
var startTime = calEvent[1];
var endTime = calEvent[2];
eventCal.createEvent(eventName, startTime, endTime);
}
In your situation, how about the following modification?
From:
var spreadSheet = SpreadsheetApp.getActiveSheet;
var eventCal = CalendarApp.getCalendarById(calendarId);
// Below instead of selecting the entire range I only need the rows that have a value of "select" in their D cell.
var eventArray = spreadSheet.getRange("A1:D100").getValues();
To:
var spreadSheet = SpreadsheetApp.getActiveSheet(); // Please add ()
var eventCal = CalendarApp.getCalendarById(calendarId);
var eventArray = spreadSheet.getRange("A1:D100").getValues().filter(r => r[3] == "select");
By this modification, eventArray has the values that select is included in the column "D".
Reference:
filter()

Google Apps Script - Changing values in a range of google sheets cells

In the following example I use Apps Script to schedule Google calendar events based on spreadsheet input. This code works just fine however I need to tweak it to do a small manipulation on the source sheet as well.
You can see here that I filter the range to only include rows that have "Pending" value in column D (r[3]). However I need to include a line of code in the loop so that after the filtered rows are synced to my Google Calendar the same cell value in column D changes to "Scheduled" for the respective cell. I have tried following this solution but could not implement it since I am new to JS.
Google Apps Script - .setValue in cell based on for loop matching
function calendarSync() {
var ss = SpreadsheetApp.getActiveSheet();
var calendarId = "My Calendar ID";
var eventCal = CalendarApp.getCalendarById(calendarId);
var eventArray = ss.getRange('A2:I500').getValues().filter(r => r[3] == "Pending Schedule");
for (x=0; x<eventArray.length; x++) {
var event = eventArray[x];
var eventName = event[0];
var startTime = event[1];
var endTime = event[2];
var exisEvents = eventCal.getEvents(startTime, endTime, {search: eventName}) //prevents creating duplicate events;
if (exisEvents.length == 0) {
eventCal.createEvent(eventName, startTime, endTime);
}
}
}
One simple solution is to change a bit logic of your script. Instead of using filter use an if statement then overwrite the whole range.
function calendarSync() {
var ss = SpreadsheetApp.getActiveSheet();
var calendarId = "My Calendar ID";
var eventCal = CalendarApp.getCalendarById(calendarId);
var eventArray = ss.getRange('A2:I500').getValues();
for (x = 0; x < eventArray.length; x++) {
var event = eventArray[x];
var eventName = event[0];
var startTime = event[1];
var endTime = event[2];
var status = event[3]; // Used to in the following comparison expression instead of filter
if (status === "Pending Schedule") {
var exisEvents = eventCal.getEvents(startTime, endTime, {
search: eventName
}) //prevents creating duplicate events;
if (exisEvents.length == 0) {
eventCal.createEvent(eventName, startTime, endTime);
eventArray[x][3] = "Scheduled"; // Update the status
}
}
}
ss.getRange('A2:I500').setValues(eventArray); // Overwrite the source data with the modified array
}
P.S. If you are using the default runtime instead of var it's better to use const and let, specially when writing complex scripts.

Question About the App Script of Dynamic Dropdown list with Section Navigation in Google Form

I am trying to make a dynamic dropdown list and Section Navigation in Google Form. However, my script can auto delete the choice when the quota has been met, the choice can’t navigate to the related page for other selections.
I am planning a health check event for my hospital. It needs to reserve by timeslot and date due to the crow control policy. The links below are my Google Spreadsheet for the form and my daft Google Form of the function.
https://forms.gle/ZV9Djni8hyQGdAd86
https://docs.google.com/spreadsheets/d/1F1dpGCTSlpEOUMh5txsZouhx784JmJvh66IsiGfDTtg/edit?usp=sharing
Reference:
How to set the go to sections on a Google Forms question using app script
https://www.pbainbridge.co.uk/2019/04/dynamically-remove-google-form-options.html
function appointmentSlots() {
var form = FormApp.openById("1VqFBKBD_-iKYk_3Ze40j2tvRIi093-alaoCDsXpFi8k");
var ss = SpreadsheetApp.getActiveSpreadsheet();
var date1timelist = form.getItemById("2101588132").asListItem();
var optionsSheet = ss.getSheetByName('Date Options');
var dateoptions = optionsSheet.getRange('A2:A3').getValues();
var dateleft = optionsSheet.getRange('C2:C3').getValues();
var day1sheet = ss.getSheetByName('9/3');
var day1timeoptions = day1sheet.getRange('A2:A4').getValues();
var day1left = day1sheet.getRange('C2:C4').getValues();
var formFieldsArray = [
["9/3", 2061926149],
["10/3", 1632977105]
];
for(var h = 2; h < formFieldsArray.length; h++) {
var datelist = form.getItemById(formFieldsArray[h][2]).asListItem();
var avaibledateoptions = [];
var sectionday1timeslots = form.getItemById("2101588132").asPageBreakItem();
var sectionday2timeslots = form.getItemById("1630116063").asPageBreakItem();
var datechoice = datelist.getChoices();
var optionsDataLength = dateoptions.length;
for (var i=0; i<optionsDataLength; i++) {
var choice = dateoptions[i][0];
var left = dateleft[i][0];
if ((choice != '') && (left > 0) == formFieldsArray[h][2]) {
if (formFieldsArray[h]= "9/3") {
datechoice.push(datelist.createChoice(avaibledateoptions,sectionday1timeslots));
}
else {
datechoice.push(datelist.createChoice(avaibledateoptions,sectionday2timeslots));
datelist.setChoices(avaibledateoptions);
}
}
}
}
var day1avaibledateoptions = [];
var optionsday1Length = day1timeoptions.length;
for (var i=0; i<optionsday1Length; i++) {
var day1timechoice = day1timeoptions[i][0];
var day1timeleft = day1left[i][0];
if ((day1timechoice != '') && (day1timeleft > 0)) {
day1avaibledateoptions.push(day1timechoice);
}
}
date1timelist.setChoiceValues(day1avaibledateoptions)
}
//etc for day2 timeslots choice and day3 timeslots
}
}
}
In order to modify your form depending on the changing cell values in your Spreadsheet (caused by new form submissions) you will need to set up an installable onChange trigger that will basically run your function when a change on your Spreadsheet is done (like one coming from a form submission). To create such a trigger, please access your trigger pannel and then click on Create trigger and select as the event type onChange assigning it to the function you will be using to create/delete the form items.
Once a user submits a new form and you do certain calculations on your Spreadsheet to determine how many slots are free for that time slot, you can take the value of the cell that tells you how many free appointments are free for that time and if that number is 0 you can proceed to delete that question element using the method deleteItem().
If you eventually end up resetting the form (because your time slot is free again or someone cancels the meeting), you can undo this by creating back the element.
The following piece of code is a basic example on how to delete and create form items based on the changes of a Spreadsheet cell. It has self explanatory comments:
function onChange() {
// Get the different sheets where you have all your left places in your time slots
var sheet = SpreadsheetApp.getActive().getSheetByName('A');
// Get your form
var form = FormApp.openById('FORMID');
// Here you would get each element that might depend on whether there are any
// appointments left or not
var element = form.getItems()[2];
// Get the cell value that tells you if the time slot is already full (full=0)
var value = sheet.getRange('C2').getValue();
// If the value is 0 it means that this time slot is all completed and nobody
// should be able to select it again
if (value == 0) {
// delete this item
form.deleteItem(element);
// if it is not full yet, it might be because your reset the time slot and therefore
// the element does not exist any more
} else {
// if the element exists dont do anything but if it doesnt and there are available
// apointments create it again
if (!element) {
form.addMultipleChoiceItem().setTitle('B').setChoiceValues(['Cats', 'Dogs']);
}
}
}
If you want to remove a choice option rather than an Item, you can look for the item, get all the choices as an array and then remove the choice you don't want any more from this array. Finally, you can update the item with your updated options with setChoices(). Here is a code example on how to achieve this:
function myFunction() {
// This is an example where I only have a single multiple choice item
var choiceItem = FormApp.getActiveForm().getItems(FormApp.ItemType.MULTIPLE_CHOICE)[0].asMultipleChoiceItem();
// Get current choices array
var choices = choiceItem.getChoices();
// Get choice you want to delete, this would be your times or dates obtained from
// the cell values
var choiceToBeRemoved = "A";
// remove choice from array
choices = choices.filter(function(choice){return choice.getValue() !== choiceToBeRemoved});
// Set updated choices
choiceItem.setChoices(choices);
}
References
setChoices
Javascript filter()

Increasing speed of script using cache

I have a script that gets all the values of a spreadsheet and uses those values to create entries in a calendar.
However, it takes too long to run and times out.
It didn't used to take that long because there wasn't enough entries, but now there's enough entries that it cant finish before it times out, so I need to increase the speed.
I believe the reason it runs so slow is because there's a loop that runs through every row of the spreadsheet and at the end of every loop it writes a calendar event. I think it's this that adds to the execution time because it has to reconnect to the calendar over and over again. I think this massively adds to the execution time.
I believe I can reduce this with caching but I have not even the slightest clue how that works.
Here is my code:
/**
* Export events from spreadsheet to calendar
*/
function exportEvents() {
var sheet = SpreadsheetApp.getActiveSheet();
var headerRows = 1; // Number of rows of header info (to skip)
var range = sheet.getDataRange();
var data = range.getValues();
var calId = "trhcom7eiadkcn39mg9d0hfceg#group.calendar.google.com";
var cal = CalendarApp.getCalendarById(calId);
for (i in data) {
if (i < headerRows) continue; // Skip header row(s)
var row = data[i];
var date = new Date(row[9]);
if (!(isValidDate(date))) continue; // Skip rows without a date
var title = row[19]+" - "+row[3]+" - "+row[1]+" - "+row[2];
var id = row[31];
// Check if event already exists, delete it if it does
try {
var event = cal.getEventSeriesById(id);
event.deleteEventSeries();
row[31] = ''; // Remove event ID
}
catch (e) {
// do nothing - we just want to avoid the exception when event doesn't exist
}
var newEvent = cal.createAllDayEvent(title, date).addEmailReminder(4320).addEmailReminder(60).addSmsReminder(4320).addSmsReminder(60).getId();
row[31] = newEvent; // Update the data array with event ID
}
i=0;
for (i in data) {
if (i < headerRows) continue; // Skip header row(s)
var row = data[i];
var date = new Date(row[13]);
if (!(isValidDate(date))) continue; // Skip rows without a date
var title = "Expected Pay Date: "+row[19];
var id = row[32];
// Check if event already exists, delete it if it does
try {
var event = cal.getEventSeriesById(id);
event.deleteEventSeries();
row[32] = ''; // Remove event ID
}
catch (e) {
// do nothing - we just want to avoid the exception when event doesn't exist
}
date.setDate(date.getDate() + 12);
var newEvent = cal.createAllDayEvent(title, date).addEmailReminder(4320).addEmailReminder(60).addSmsReminder(4320).addSmsReminder(60).getId();
row[32] = newEvent; // Update the data array with event ID
}
// Record all event IDs to spreadsheet
range.setValues(data);
};
I'm trying to use the information from this page but I don't even know where to begin.
Is the cache stored locally or on the server? How do I access it? What is a key and where do I find it? What url do I use? How will this end up increasing my speed?
I feel like this is simple but I just don't grasp the concept.
Update: After doing some research I'm not sure a cache can help me since it's not getting data that's taking a long time but rather creating it.
Maybe instead I should be trying to figure out a way to simply write all the events to the calendar at once at the end of the loop but I wouldn't know how to do that either.
Your question contains a lot of question but you already answered a couple of them yourself... ;-) it is indeed not a matter of reading sheet data (which you already do the right way using getDataRange().getValues() ) but a problem with the event creation that takes a long time...
Unfortunately there is no way to speed that up, the only thing we can do is proceed by reduced size batch and let the script run automatically every 10 minutes or so until all the events are created.
Nothing really complicated, here is a script that shows the process :
function createEvents() {
// check if the script runs for the first time or not,
// if so, create the trigger and PropertiesService.getScriptProperties() the script will use
// a start index to know were from it has to continue
if(PropertiesService.getScriptProperties().getKeys().length==0){
PropertiesService.getScriptProperties().setProperties({'startRow':0 });
ScriptApp.newTrigger('createEvents').timeBased().everyMinutes(10).create();
}
// initialize all variables when we start
var startRow = Number(PropertiesService.getScriptProperties().getProperty('startRow'));
var sheet = SpreadsheetApp.getActiveSheet();
var headerRows = 1; // Number of rows of header info (to skip)
var range = sheet.getDataRange();
var data = range.getValues();
var calId = "trhcom7eiadkcn39mg9d0hfceg#group.calendar.google.com";
var cal = CalendarApp.getCalendarById(calId);
var counter = 0
for (var i=tstartRow ; i < data.length ; i++) {
counter++ ;
if(counter == 30){ break }
if (i < headerRows) continue; // Skip header row(s)
var row = data[i];
...
... continue your own code
}
// update the spreadsheet
// if i== data.length then kill the trigger and eventually send yourself a message
// to tell you that the script has finished successfully .
// killing the current trigger goes like this :
var trigger = ScriptApp.getProjectTriggers()[0];
ScriptApp.deleteTrigger(trigger);
Good luck.

Find Index of Column(s) after it has been Moved

We are using DHTMLX Grid. Need some help, please.
I have a table and each columns (has filter/dropdown) are allocated an id eg. fac, date, sel, loc, tag ... etc
We have hard coded the index of columns to set and get the cookie elsewhere.
function doInitGrid(){
mygrid.setColumnIds("fac,date,sel,loc,tag"); //set ids
mygrid.attachEvent("onFilterStart",function(ind,data)
{
setCookie("Tray_fac_filter",mygrid.getFilterElement(0).value,365); //column index 0
setCookie("Tray_loc_filter",mygrid.getFilterElement(3).value,365);//column index 3
setCookie("Tray_tag_filter",mygrid.getFilterElement(4).value,365); //column index 4
mygrid.getFilterElement(0).value = getCookie("Tray_fac_filter")
mygrid.getFilterElement(3).value = getCookie("Tray_dep_filter")
mygrid.getFilterElement(4).value = getCookie("Tray_prg_filter")
});
}
But when the columns are moved, the problem arises as the index of the column changes yet it is set in setCookie /getCoookie
DHTMLX allows to get the index of the id using --
var colInd = grid.getColIndexById(id);
eg: var colInd = grid.getColIndexById(date); // outputs 1.
After moving the date column to the end -- fac, sel, loc, tag, date // it will output 4.
However, we have about 14 columns that can be moved/rearranged and I could use the
var colInd = grid.getColIndexById(id); 15 times
var facInd = grid.getColIndexById("fac");
var dateInd = grid.getColIndexById("date");
var selInd = grid.getColIndexById("sel");
var locInd = grid.getColIndexById("loc";
var tagInd = grid.getColIndexById("tag");
and put those variables in the set/get cookie. I was thinking if there was a better way.
To understand the code better, I have put the minimised version of the code in fiddle.
http://jsfiddle.net/19eggs/s5myW/2/
You've got the best answer I think. Do it in a loop and it's easier:
var cookie_prefix = "Fray_filter_";
var cookie_dur = 365;
var num_cols = dhx_grid.getColumnCount();
// filter vals to cookies
for (var col_idx=0; col_idx<num_cols; col_idx++) {
var filter = mygrid.getFilterElement(col_idx)
if (filter) { // not all columns may have a filter
var col_id = dhx_grid.getColumnId(col_idx);
var cookie_name = cookie_prefix+col_id;
setCookie(cookie_name, filter.value, cookie_dur);
}
}
// cookies to filter vals
for (var col_idx=0; col_idx<num_cols; col_idx++) {
var col_id = dhx_grid.getColumnId(col_idx);
var filter_val = getCookie(cookie_prefix+col_id);
var filter = mygrid.getFilterElement(col_idx)
filter.value = filter_val;
}
You can use dhtmlxgrid native event to assign the correct id everytime a column is moved.
The event is called onAfterCMove, you can check the documentation here. onAfterCMove Event
You would do something like:
mygrid.attachEvent('onAfterCMove',function(cInd,posInd){
//Your processing here to change the cookies; where cInd is the index of the column moved
//and posInd, is the position where it Was moved
}):

Categories

Resources