0

I have mashed together a Google Apps script to create an event in the calendar based on values in my Google sheet. The script should work in the following conditions:

  1. Whenever a new row is added (achived by a trigger - the 1st function)
  2. Only apply to the last row on the spreadsheet
  3. Only create an event if the "id" cell (28) is empty

It should then create the calendar event and populate cell 28 with the event ID.

The new rows are created when a client fills out a form on my website. I'm using Ninja Forms in WordPress that has a Google Sheets plugin. So the filled out form is automatically added to the sheet, then this function fires.

Everything is almost working. When I test manually (eg I delete the id cell in the last row or I create a new row or copy an existing row) it works perfectly. The event is created only if there is no event ID in cell 28 already, and it successfully fills out cell 28 with the ID. Great!

However, when the row is created using the WordPress form I get two calendar events. IE - it's like the function runs twice. Each event is identical and both are created at the same time.

I'm guessing this has something to do with how the form integrates with the sheet. It is somehow triggering my function twice. I have tried using Utilities.sleep at different points in the function with different values to see if maybe waiting between strps helps, but to no avail.

Can anyone think of a way I can stop this from occurring? Is there some kind of check I might be able to build into my function? Or have I missed something obvious? I would really appreciate any suggestions. Here is my code:

function initializeTrigger(){ // run once only to create the trigger
  var sheet = SpreadsheetApp.getActive();
 ScriptApp.newTrigger("NewCalEvent")
   .forSpreadsheet(sheet)
   .onChange()
   .create();
}

function NewCalEvent() {

  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName('Bookings2');
  var row = sheet.getLastRow();
  var calId = "xxxyyy@group.calendar.google.com";
  var cal = CalendarApp.getCalendarById(calId);
  var date = sheet.getRange(row, 1).getValue();
  var title = sheet.getRange(row, 26).getValue();
  var tz = sheet.getRange(row, 23).getValue();
  var tstart = new Date(sheet.getRange(row, 32).getValue());
  var tstop = new Date(sheet.getRange(row, 33).getValue());
  var loc = sheet.getRange(row, 2).getValue();
  var desc = sheet.getRange(row, 27).getValue();
  var guests = sheet.getRange(row, 29).getValue();
  var id = sheet.getRange(row, 28).getValue();
if (id == ""){    
  var newEvent = cal.createEvent(title, tstart, tstop, {description:desc,location:loc,guests:guests,sendInvites:true}).getId();
  sheet.getRange(row, 28).setValue(newEvent)
  }
}
Marios
  • 26,333
  • 8
  • 32
  • 52
Luke
  • 173
  • 1
  • 6
  • 1
    Try using the event objects changeType to specify a unique change type also perhaps your getting a spurious extra trigger, try testing for it. – Cooper Oct 20 '20 at 22:50
  • Thanks @Cooper for you reply. Sorry - I'm pretty new to this. I think I'm learning js as I go! I thoguht I did specify the change type to be .create() - but maybe I'm missing what you're saying. Would you mind elaborating? Cheers. – Luke Oct 20 '20 at 23:01
  • 1
    Look at the [trigger event object](https://developers.google.com/apps-script/guides/triggers/events#change) it contains a parameter called changeType – Cooper Oct 20 '20 at 23:03
  • 1
    On the script execution page you should be able to see an entry for each script exectution. This could help you to know exactly if the form is triggering twice your script of if other thing is happening. – Rubén Oct 20 '20 at 23:29
  • Thanks @Cooper. I tried your suggestion by adding: if (e.changeType == "INSERT_ROW"). Unfortunately, I don't think the form uses "Insert Row". As it isn't triggering :( – Luke Oct 20 '20 at 23:31
  • Thanks @Rubén that's a good idea too! I checked and can definitely see both executions and triggers. But not a lot of detail. Should I be looking for something in particular? https://imgur.com/wxK6G3X – Luke Oct 20 '20 at 23:37
  • @Ruben so it looks like the form is triggering the script twice at exactly the same time. Maybe it does 2 edits at once? I'm very confused... – Luke Oct 20 '20 at 23:48
  • 1
    Take a look at this: https://stackoverflow.com/questions/54834837/how-can-i-be-getting-multiple-unwanted-event-blocks-from-the-same-onformsubmit-t – Cooper Oct 21 '20 at 00:00
  • Thanks @Cooper. That seems to be a similar problem to waht I am experiencing. But I have to admit, with my very limited understanding of js, I don't understand the solution at all... By simple replacement (with zero understanding of what I'm doing) would mine look something like: if(id && !id[1]){return;} ? – Luke Oct 21 '20 at 00:14
  • I'm sorry, I don't know how your form works. I think that you should add more details about it. Also you should log the on change event object. – Rubén Oct 21 '20 at 01:10
  • Thanks @Rubén. I'm not sure what more to add. It is basically a WordPress form that visitors can fill in. The plugin enables me to map filds in the form with cells in Sheets - to it workds very similarly to a Google Form. Once the user submits the form, a new row is populate in the sheet with the form fields. – Luke Oct 21 '20 at 01:17
  • So far it looks that you are blamimg Google Apps Script while the problem might the the WordPress Form. I think that you should add more infor about the plugin and how you set it up. – Rubén Oct 21 '20 at 01:59
  • Definitely not blaming Google Apps Script! I hope it didn't come across that way. I'm mostly blaming myself for my lack of knowledge! There is very little I can add about the plugin and very little control I have over it. It connects to your Google account using OAuth2. It pulls a list of sheets and you tell it which sheet you want to post the form results to. It then literally pulls all the fields from the sheet and you just choose which filed you want to map to which fileds in the form. I have o idea how it actually pushes the data unfortunately. – Luke Oct 21 '20 at 02:49
  • AFAIK there are several plugins that send form data to Google Sheets... one simple solution might be to use another plugin, another simple solution is instead of using an on change trigger use a time-driven trigger – Rubén Oct 21 '20 at 03:07
  • 1
    Learning Google Apps Script is greatly facilitated by learning javascript first. Also a reasonable of knowledge of html and css is very useful. – Cooper Oct 21 '20 at 04:00
  • Thanks for getting back to me @Cooper. I tend to agree - but necessity requires I learn on the go! I'll try to work out what I need to do, but if you have any suggestions, that would be appreciated. – Luke Oct 21 '20 at 07:19

1 Answers1

1

Possible Solution:

Add a check to see if the calendar event has already been created for this time.

More Information:

As it is not clear exactly where this trigger duplication is coming from, a way to circumvent the event being created twice is to search the calendar for the specified time frame for events which have the description you intend to enter, and then wrap the event creation code inside the check so it only runs if the event doesn't yet exist.

Code Snippet:

// ...
var id = sheet.getRange(row, 28).getValue();
var events = cal.getEvents(tstart, tstop, {search: desc})
      .map(function(e) {
        try {
          return e.getDescription();
        }
        catch {
          return "";
        }
      });

if (!events.includes(desc)) {
  if (id == ""){
    var newEvent = cal.createEvent(title, tstart, tstop,
      {description:desc,location:loc,guests:guests,sendInvites:true}).getId();
    sheet.getRange(row, 28).setValue(newEvent)
  }
}

This way, if the script is run twice, then as the event already exists the second time, it will not be created again.

NB: This checks for all Calendar event exists within the time frame tstart to tstop that have a description of desc. If you happen to have other events within this time frame with the same description, this script may not behave as expected.

References:

Nimantha
  • 6,405
  • 6
  • 28
  • 69
Rafa Guillermo
  • 14,474
  • 3
  • 18
  • 54
  • Yes! You legend. That worked perfectly! Thanks so much! I was going to give up and change the trigger to time-based (per minute) because that seemed to get around the problem, but I was potentially in a pickle if more than one person filled out the form in the space of a minute. This is a much cleamer and better solution. Thank you so much for taking the time to wrtie it out and assist me. – Luke Oct 21 '20 at 08:27
  • Just for my own education, I changed your code a bit to use the title - getName() - and it seems to work still. Is there any reason I would use description instead? – Luke Oct 21 '20 at 08:29
  • 1
    @Luke No reason, I just chose description as it's more likely to be unique (I don't know your data set so could be something like "Weekly Team Meeting" as a genertic title or something akin to this) – Rafa Guillermo Oct 21 '20 at 08:30
  • 1
    Ah that makes a lot of sense. In my specific case, the title includes the name of the client, and a specific time, so I think I'm OK to keep using that. But your answer is definitely the best for general use. Thanks again so much for the consise, clear and helpful answer. You have saved me much frustration. – Luke Oct 21 '20 at 08:33
  • Hi @rafa-guillermo sorry to bring this back up again. I just noticed that - even though the script is doing the job perfectly - I get errors logged for the times the event is suppressed. Is there a way to supress these errors? See here for an example: https://imgur.com/2CNhAHa – Luke Oct 26 '20 at 00:26
  • @Luke You can define a function instead of a direct `e => e.getName()` inside the mapping and use a try/catch. I've updated my answer. – Rafa Guillermo Oct 26 '20 at 08:45