Possible speed up of gappscript - google-apps-script

I have a script that captures information from a panel. Once the info has been entered, user clicks a Submit button and the data entered is stored in a spreadsheet that will later be used to replace some fields of a template that I have on a cell on that spreadsheet. This process is very time consuming, like around a minute to perform all this. What I was wondering is if is possible that instead of storing the data in a cell on the spreadsheet if I can use the parameters info, and replace it on the template I have. Any ideas on how to do this?
function a(e) {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var timecell = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Templates").getRange("A2");
var timebval = timecell.setValue(e.parameter.Times);
var t1 = timecell.getValue();
var mincell = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Templates").getRange("A3");
var minbval = mincell.setValue(e.parameter.Minutes);
var t2 = mincell.getValue();
var namecell = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Templates").getRange("A4");
var nabval = namecell.setValue(e.parameter.Name);
var t3 = namecell.getValue();
var email = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Templates").getRange("A5");
var emaval = email.setValue(e.parameter.email);
var t4 = email.getValue();
var address = "albdominguez25#gmail.com";
var advancedArgs = {bcc:t4};
var emailSubject = "Test";
var emailTemplate =SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Templates").getRange("A1").getValue() ;
emailTemplate = emailTemplate.replace("TIME",t1).replace("MIN",t2).replace("EXP",t3);
MailApp.sendEmail(address, emailSubject, emailTemplate, advancedArgs);
Browser.msgBox("Your Email has been sent!");

Instead of storing something to a spreadsheet and reading back from it for each value, just directly use the value in the replace method as shown below.
function a(e) {
var address = e.parameter.email;
var advancedArgs = {bcc:t4};
var emailSubject = "Test";
var emailTemplate = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Templates").getRange("A1").getValue() ;
emailTemplate = emailTemplate.replace("TIME", e.parameter.Times).replace("MIN", e.parameter.Minutes).replace("EXP", e.parameter.Name);
MailApp.sendEmail(address, emailSubject, emailTemplate, advancedArgs);
Browser.msgBox("Your Email has been sent!");
}
If you must store these values in your spreadsheet also for later reference, it is faster to do one setValues instead of calling setValue several times. After you use the parameter info to send the email, just call this to set all the values at once:
var range = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Templates").getRange("A2:A5");
range.setValues([[e.parameter.Times], [e.parameter.Minutes], [e.parameter.Name], [e.parameter.email]]);

Related

App Script not updating Doc to include existing data on Form edits

I've been working on the process of auto populating a form based on a Google form submission. All of my progress to date has been what I learned on this site. Thanks for all the help!
I got everything to work exactly how it needed to run, assuming that the form is filled out in its entirety. The technician fills out the google form and submits. After he/she submits, the script automatically generates a custom document with all the data.
After some runtime, the technicians asked that we make the Google Form editable after submission. I did some research and found that you can make a form editable and wrote some script to auto generate a custom URL for each form.
Now I'm running into an issue with the auto Doc when partial forms are submitted. If the technician fills out the 1'st half of the form and submits, a Doc is auto generated with only the 1st half data, which I expected. Now if they go back and fill out the 2nd half of the form, only the 2nd portion of the Doc is filled out. I thought it would pull the entire row of data from the Sheet, but this isn't the case.
After some thought, I think I need to rewrite the code to not auto generate the Doc when a form is submitted, because more and more forms will be filled out in pieces, and completed at a later date.
Here's is what I would like to do. Any insight or helpful links would be greatly appreciated! Thanks so much!
All of the form data currently goes to the Sheet, Tab A. I would have a second Tab B on the sheet named "Data to Doc". I would manually comb through Tab A and copy and past the data to Tab B that I would like to create Doc's for. On Sheet B, There would be a button that I can click that would run the script to convert the sheet data to a Doc file.
I found a video of what I think I want to do, but I cant seem to make my code work the way they did.
https://www.youtube.com/watch?v=r9uU_KwGgzQ
Below is my current code.
var docTemplate = "fdgfdg";
var docName = "Technician Report";
var folder = DriveApp.getFolderById('hjhjghhgjgh');
function onFormSubmit(e) {
var replaceTextToImage = function(body, searchText, fileId) {
var width = 300; // Please set this.
var blob = DriveApp.getFileById(fileId).getBlob();
var r = body.findText(searchText).getElement();
r.asText().setText("");
var img = r.getParent().asParagraph().insertInlineImage(0, blob);
var w = img.getWidth();
var h = img.getHeight();
img.setWidth(width);
img.setHeight(width * h / w);
}
//Get information from form and set as variables
var Technician = e.values[1];
var Customer_Name = e.values[2];
var Date = e.values[3];
var Facility_Location = e.values[4];
var WO_Project_No = e.values[5];
var PO_No = e.values[6];
var Tag_No = e.values[7];
var Site_Contact = e.values[8];
var Repair_Scope = e.values[9];
var Valve_Serial_No = e.values[10];
var Valve_Model = e.values[11];
var Valve_Condition = e.values[12];
var Valve_Action = e.values[13];
var Act_Serial_No = e.values[14];
var Act_Model = e.values[15];
var Act_Condition = e.values[16];
var Act_Action = e.values[17];
var Cont_Serial_No = e.values[18];
var Cont_Model = e.values[19];
var Cont_Condition = e.values[20];
var Cont_Action = e.values[21];
var Recommended_Actions = e.values[30];
var Call_Notes = e.values[32];
var Picture1_Notes = e.values[23];
var Picture2_Notes = e.values[25];
var Picture3_Notes = e.values[27];
var Picture1_Image = e.values[22].split("=")[1];
var Picture2_Image = e.values[24].split("=")[1];
var Picture3_Image = e.values[26].split("=")[1];
// Get document template, copy it as a new temp doc, and save the Doc’s id
var copyId = DriveApp.getFileById(docTemplate)
.makeCopy(docName+' for '+Customer_Name+' '+Tag_No, folder)
.getId();
// Open the temporary document
var copyDoc = DocumentApp.openById(copyId);
// Get the document’s body section
var copyBody = copyDoc.getBody();
copyBody.replaceText('A1', Technician);
copyBody.replaceText('A2', Customer_Name);
copyBody.replaceText('A3', Date);
copyBody.replaceText('A4', Facility_Location);
copyBody.replaceText('A5', WO_Project_No);
copyBody.replaceText('A6', PO_No);
copyBody.replaceText('A7', Tag_No);
copyBody.replaceText('A8', Site_Contact);
copyBody.replaceText('A9', Repair_Scope);
copyBody.replaceText('B1', Valve_Serial_No);
copyBody.replaceText('B2', Valve_Model);
copyBody.replaceText('B3', Valve_Condition);
copyBody.replaceText('B4', Valve_Action);
copyBody.replaceText('B5', Act_Serial_No);
copyBody.replaceText('B6', Act_Model);
copyBody.replaceText('B7', Act_Condition);
copyBody.replaceText('B8', Act_Action);
copyBody.replaceText('B9', Cont_Serial_No);
copyBody.replaceText('C1', Cont_Model);
copyBody.replaceText('C2', Cont_Condition);
copyBody.replaceText('C3', Cont_Action);
copyBody.replaceText('C4', Recommended_Actions);
copyBody.replaceText('C5', Call_Notes);
copyBody.replaceText('C8', Picture1_Notes);
copyBody.replaceText('D1', Picture2_Notes);
copyBody.replaceText('D3', Picture3_Notes);
replaceTextToImage(copyBody, 'C7', Picture1_Image);
replaceTextToImage(copyBody, 'C9', Picture2_Image);
replaceTextToImage(copyBody, 'D2', Picture3_Image);
copyDoc.saveAndClose();
Assuming you have a script bound to your Form with an onSubmit trigger, you can retrieve the length of the latest response and comprare it to the total amount of questions:
function myFunction(e) {
var length = FormApp.getActiveForm().getItems().length;
if(e.response.getItemResponses().length == length){
//proceed to generate custom document
}else{
Logger.log("Form is not complete");
}
}
UPDATE: Alternative solution create doc if checkbox has been checked
In this case, you do not want to run the function when the form is submitted, but when an edit of the checkbox has been made.
So, replace the onSubmit trigger through an onEdit trigger.
This will alow you to access the event objects e.range, e.oldValue and e.value.
You can implement an if statement that runs the code only if an edit in the checkbox column has been made and if the old value was false (unchecked) and the new one istrue` (checked).
Unfortunately you cannot retrieve your form response onEdit with e.values, instead, you can verify in which row the checkbox has been checked and retrieve the values of this row with sheet.getRange(row, 1, 1, sheet.getLastColumn()).getValues();
To implement the change, create a column with checkboxes (lets say in column 29, since the columns before are ocupied with responses) and mofify your code as following:
function onEdit(e){
if(e.range.getColumn() == 29 || e.oldValue == false && e.value == true){
var row = e.range.getRow();
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var values = sheet.getRange(row, 1, 1, sheet.getLastColumn()).getValues()[0];
var replaceTextToImage = function(body, searchText, fileId) {
var width = 300; // Please set this.
var blob = DriveApp.getFileById(fileId).getBlob();
var r = body.findText(searchText).getElement();
r.asText().setText("");
var img = r.getParent().asParagraph().insertInlineImage(0, blob);
var w = img.getWidth();
var h = img.getHeight();
img.setWidth(width);
img.setHeight(width * h / w);
}
//Get information from form and set as variables
var Technician = values[1];
var Customer_Name = values[2];
...
...
copyDoc.saveAndClose();
}
}

Why can't I return a list of specific email addresses from a Calendar invite with App Script?

I'm trying to return a list of specific email addresses of all event attendees on a GSuite calendar with a script and I can only return either an ID number or a list that appears only as "EventGuest, EventGuest, EventGuest...."
Is it possible to return the email address itself? Thanks in advance for any guidance here!
This is the basic script that I'm working with:
function listCalendarAttendees(){
var calendar = CalendarApp.getCalendarById('AnyCalendarTEST#gmail.com');
var sheet = SpreadsheetApp.openById('TEST ID').getSheetByName('Bobs Calendar');
var startTime = new Date();
var endTime = new Date(startTime.getTime()+(1000*60*60*24*7));
var events = calendar.getEvents(startTime, endTime);
Logger.log('Number '+events.length);
for(var x=0;x<events.length;x++){
var event = events[x];
var messages = event.getTitle();
var eventStart = event.getStartTime();
var emails = event.getCreators().toString();
var descriptions = event.getDescription();
var guestListLength = event.getGuestList().length;
var guestListNames =event.getGuestList().toString();
sheet.appendRow([messages,eventStart,emails,descriptions,guestListLength,guestListNames]);
}
}
You want to retrieve the emails of all guests in the event.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Modification points:
getGuestList() returns the object of EventGuest[]. In this case, the email can be retrieved by getEmail() from EventGuest.
When appendRow() is used in the for loop, the process cost will be high. So in this modification, setValues() is used instead.
Modified script:
When your script is modified, it becomes as follows.
From:
for(var x=0;x<events.length;x++){
var event = events[x];
var messages = event.getTitle();
var eventStart = event.getStartTime();
var emails = event.getCreators().toString();
var descriptions = event.getDescription();
var guestListLength = event.getGuestList().length;
var guestListNames =event.getGuestList().toString();
sheet.appendRow([messages,eventStart,emails,descriptions,guestListLength,guestListNames]);
}
To:
var values = []; // Added
for(var x=0;x<events.length;x++){
var event = events[x];
var messages = event.getTitle();
var eventStart = event.getStartTime();
var emails = event.getCreators().toString();
var descriptions = event.getDescription();
var guestListLength = event.getGuestList().length;
var guestListNames = event.getGuestList().map(function(e) {return e.getEmail()}).join(","); // Modified
values.push([messages,eventStart,emails,descriptions,guestListLength,guestListNames]); // Modified
}
sheet.getRange(sheet.getLastRow() + 1, 1, values.length, values[0].length).setValues(values); // Added
In this modification, the emails are separated by , and it is put to a cell.
References:
getGuestList()
Class EventGuest
getEmail()
If I misunderstood your question and this was not the direction you want, I apologize.
Event Guest is an object. Here are it's methods:
getEmail()
getAdditionalGuests()
getGuestStatus()
getName()

If column has value do ______

How to search a column on a sheet for a value, and if the value is there continue code? I am trying to make an update system to check to see if their email is on a sheet. If their email is on a sheet then it will check if their system version is up to date. If the system is not to up to date it will then copy their data, delete their current sheet, create a copy from an example then put in their data. If the user does not have the system it will create one for them. It seems to continuously skip Checking their email.
My code uses URLs and IDs but I have taken them out for obvious reasons. Below is an example of the beginning of my code.
function readlog()
var user = Session.getEffectiveUser().getUsername();
var email = Session.getEffectiveUser().getEmail();
var ss = SpreadsheetApp.openByUrl(URL_);
SpreadsheetApp.setActiveSpreadsheet(ss);
var sheet = SpreadsheetApp.getActiveSpreadsheet()
var sheet = sheet.getSheetByName('8th grade')
var active = sheet.getRange('8th grade!b3:b').getValues()
if (email == active) {
//do something
}
Here is a full version of my code:
function readlog(){
var user = Session.getEffectiveUser().getUsername();
var email = Session.getEffectiveUser().getEmail();
var timezone= "CST";
var date = Utilities.formatDate(new Date(),timezone, "E MMM dd,yyyy HH:mm:ss");
var ss = SpreadsheetApp.openByUrl(URL_);
SpreadsheetApp.setActiveSpreadsheet(ss);
var sheet = SpreadsheetApp.getActiveSpreadsheet()
var sheet = sheet.getSheetByName('8th grade')
var active = sheet.getRange('8th grade!b3:b').getValues()
if (email == active) {
var container = DriveApp.getFoldersByName('Reading Log')
var fid = DriveApp.getFoldersByName('Reading Log').getId()
var version = sheet.getRange('8th grade!a1')
var update = container.getDescription();
if (update == version) {
var ui = SpreadsheetApp.getUi();
var response = ui.alert('You already have a reading log, and you have the most recent update!', ui.ButtonSet.OK);
} else {
var contents = container.getFiles();
var files = DriveApp.getFolderById(fid).searchFiles(
'title contains "Reading log"');
while (files.hasNext()) {
var file = files.next();
var logtoupdate = file.getUrl;
}
var ss = SpreadsheetApp.openByUrl(logtoupdate);
SpreadsheetApp.setActiveSpreadsheet(ss);
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var logrange = sheet.getRange('8th Grade!e5:o').getValues()
SpreadsheetApp.getActiveSpreadsheet().deleteActiveSheet();
var master = DriveApp.getFileById(URL_);
var read = master.makeCopy("Reading Log",container);
var target = read.getRange('8th Grade e5:o').setValues(logrange);
var range = sheet.getRange("A1:B2");
range.setValues([[50, 100]]);
var ss = SpreadsheetApp.openByUrl(URL_);
SpreadsheetApp.setActiveSpreadsheet(ss);
var sheet = SpreadsheetApp.getActiveSpreadsheet()
}
} else {
var ss = SpreadsheetApp.openByUrl(URL_);
SpreadsheetApp.setActiveSpreadsheet(ss);
var sheet = SpreadsheetApp.getActiveSpreadsheet()
var targetFolder = DriveApp.createFolder("Reading Log");
var eighth = DriveApp.getFileById(URL_);
var share = eighth.makeCopy("(Name): 8th Grade Reading Log",targetFolder);
var master = DriveApp.getFileById(URL_);
var read = master.makeCopy("Reading Log",targetFolder);
var folder = sheet.getRange('8th grade!a1');
targetFolder.setDescription(folder);
var idsheet = read.getUrl();
var idshare = share.getUrl();
var idfolder = targetFolder.getUrl()
sheet.appendRow([user,email,date,idfolder,idsheet,idshare]);
var ui = SpreadsheetApp.getUi();
var response = ui.alert('Your Reading log is created. There is now a folder in your google drive called Reading log. Have Fun Reading!', ui.ButtonSet.OK);
}
}
Short answer
Instead of email == active use active.join('|').split('|').indexOf(email)
Explanation
getValues() return a two dimensional array.
join('|') convert this array into an string using | as separator.
split('|') convert the above string into an (one dimension) array.
indexOf(email) returns the index of the email value on the above array. If the email value is not found, -1 is returned.
On a comparison -1 is parsed as false.

How can I keep the leading zero's in my pdf generated documents from google spreadsheet?

I have created a google test form and test form response. In the response spreadsheet, I have written below script to capture the inputted values from the from > generate a pdf/doc > email the pdf & > save a copy of the doc in a target folder. The issue I'm running into is that the section labeled as passcode could start with a "0" or multiple "0's", which I have formatted in the script for the spreadsheet. However when it generates the PDF/DOC, the zero's are removed. Is there any way to keep the leading 0's when converted in the PDF/DOC?
function formatNumber(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var cell = sheet.getRange("D2:D");
// Always show 4 digits
cell.setNumberFormat("0000");
}
var docTemplate = "11laarf0ThJ4mANX4KzHZCblVwRgphlf-bCblaUMl4oc";
var docName = "Test Form";
function onFormSubmit(e) {
var email_address = "jimmy111#gmail.com";
var Time_Stamp = e.namedValues["Timestamp"];
var full_name = e.namedValues["Name"];
var phone = e.namedValues["Phone Number"];
var passcode = e.namedValues["Passcode"];
var price = e.namedValues["Price"];
var copyId = DocsList.getFileById(docTemplate)
.makeCopy(full_name+' '+docName)
.getId();
var copyDoc = DocumentApp.openById(copyId);
var copyBody = copyDoc.getActiveSection();
copyBody.replaceText('keyDate', Time_Stamp);
copyBody.replaceText('keyName', full_name);
copyBody.replaceText('keyPhone', phone);
copyBody.replaceText('keyCode', passcode);
copyBody.replaceText('keyPrice', price);
copyDoc.saveAndClose();
var pdf = DocsList.getFileById(copyId).getAs("application/pdf");
var subject = "CIR Testform for "+ full_name + "";
var body = "Here is the registration form for "+ full_name +"";
MailApp.sendEmail(email_address, subject, body, {htmlBody: body, attachments: pdf});
var targetFolder = DocsList.getFolderById('0B-LfisIjjXtvLW82X3o1UzNyY1U');
var file = DocsList.getFileById(copyId);
file.addToFolder(targetFolder);
file.removeFromFolder(DocsList.getRootFolder());
}
function assignEditUrls() {
var form = FormApp.openById('16zVePLm61yRsSZMaoDI5MWIeB-vGHZGEoR9J7uJ0CP8');
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Form Responses 1');
var data = sheet.getDataRange().getValues();
var urlCol = 6; // column number where URL's should be populated; A = 1, B = 2 etc
var responses = form.getResponses();
var timestamps = [], urls = [], resultUrls = [];
for (var i = 0; i < responses.length; i++) {
timestamps.push(responses[i].getTimestamp().setMilliseconds(0));
urls.push(responses[i].getEditResponseUrl());
}
for (var j = 1; j < data.length; j++) {
resultUrls.push([data[j][0]?urls[timestamps.indexOf(data[j] [0].setMilliseconds(0))]:'']);
}
sheet.getRange(2, urlCol, resultUrls.length).setValues(resultUrls);
}
I've got a workaround to this issue, it's less than ideal but it's working.
Using the Pre-Filled form feature, enter a period in each response that requires the leading zero.
So if your user had entered "0123456", the recorded response will appear as "0.123456" you can then use substring to remove the first two charecters.
e.g.
var phone = e.namedValues["Phone Number"].substring(2);
This will then give you the leading zero. The only issue is accidental deletion of the period from the pre-filled form, although you can set a validation which checks for it, and then an error asking the user to enter it if they've removed it.
Not pretty, but we're up and running with that now at least.

Mail Merge Script with inputbox

I am trying to change one word from a template I have in a spreadsheet, for the value that the user inputs through the inputbox. When I run my script it doesnt change the word, any ideas??
function sendEmails() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var time = Browser.inputBox("Enter TIME");
var address = "albdominguez25#gmail.com";
ss.setActiveSheet(ss.getSheetByName("Templates"));
var emailTemplate = ss.getRange("B1").getValue();
//here I am trying to replace the word "TIME" on my template to the value entered on the inputBox
emailTemplate.replace("TIME",time);
var emailSubject = "Tutorial: Simple Mail Merge";
MailApp.sendEmail(address, emailSubject, emailTemplate);
}
Albert,
I believe the source of your replace is not assigning the result of the emailTemplate.replace to a variable.
I also did a little bit to the SpreadsheetApp call. Depends on what you are after of course.
Jim
function sendEmails() {
var emailTemplate = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Templates").getRange("B1").getValue();
var time = Browser.inputBox("Enter TIME");
var address = "jcampbell#neonova.net";
emailTemplate = emailTemplate.replace("TIME",time);
var emailSubject = "Tutorial: Simple Mail Merge";
MailApp.sendEmail(address, emailSubject, emailTemplate);
}