Script using the createCourses function using data from a sheet - google-apps-script

I am creating a script using the createCourses function. The template code provided on google development support is where this began. Now I want to manipulate the template to pull data from a spreadsheet. Debuggin is giving me an error - "Invalid JSON payload received. Unknown name "name" at 'course': Proto field is not repeating, cannot start list." The data appears to be pulled but I can't figure out how to repeat the create function for all my courses.
Here is my code... I have replaced the sheets 'ID' on purpose!
function createCourses() {
var course;
course = Classroom.newCourse();
var ss = SpreadsheetApp.openById('ID');
course.name = ss.getRange("A2:A").getValues();
course.ownerId = ss.getRange("H2:H").getValues();
//course.id = "Bio10";
course = Classroom.Courses.create(course);
Logger.log('%s (%s)', course.name, course.id);
var list = Classroom.Courses.create();
Logger.log(create);
}

You don't say but I assume you are getting the error on the line var list = Classroom.Courses.create(); you're not passing any parameters to the .create() method.
To repeat the function you would just need to pull the range of data from the spreadsheet and use a for loop to iterate through the data and create your courses.

Related

Can I duplicate a trigger with my new spreadsheet?

I have a new client template that I want to keep from being filled out prior to being duplicated. I've set a trigger to run a duplication and renaming function for the file when the file is opened.
function newRecord(){
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var scheck = sheet.getName();
if (scheck=='#New Client Record'){
var file = DriveApp.getFilesByName('#New Client Record').next();
file.setName('New Client')
file.makeCopy('#New Client Record');
}
}
This checks to see that the file is the correct file before proceeding, changes the name of the template and then duplicates the file and renames it the old template name. I configured it in this way so that when we start filling out the data right away while we're talking on the phone with a client, we aren't sullying our original template which remains pristine. It works nicely except that the trigger from file #1 doesn't transfer to file #2 which takes on the new identity of the original template.
So my question is - can I duplicate the trigger as well? Or is there script that can open the duplicate file and close the template file to protect my form?
EDIT: I threw a bit more at this last night and tried to add an install trigger with limited effect (I probably sound like a boomer talking about smoking the drugs with this - I'm not a programmer and have only a rudimentary vocabulary for script). I added the following function, which is transferring, but does not seem consistent in its functionality (excusing the pun).
function createSpreadsheetOpenTrigger() {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger('newRecord')
.forSpreadsheet(ss)
.onOpen()
.create();
}
Thanks for any assistance any of you can provide on this. I've stumbled my way this far on my own with the archives, but I've finally gotten myself stuck.
How to get a form to create a new sheet with values from a template
Here is a quick example of how to carry this out. What you need to see this example in action:
A template sheet that looks like this:
A form that will have 3 "Short answers" that are required.
Client Name
Phone
Email
A standalone script (Drive Web app > New > More > Google Apps Script).
A folder into which all the newly created sheets will go.
At the top of the script (outside any function) you can define 3 global variables with the appropriate IDs:
const FORM_ID = ...
const TEMPLATE_ID = ...
const CLIENT_FOLDER_ID = ...
First, to set up an onFormSubmit trigger, you can do this by running the following function only once.
function createTrigger(){
const form = FormApp.openById(FORM_ID)
ScriptApp.newTrigger("formSubmitReceiver")
.forForm(form)
.onFormSubmit()
.create()
}
After which comes the main function that will:
Receive the formSubmit event and create a range of values from it.
In this example, it will generate a followUpDate that will be 7 days from the submission of the form.
Create a copy of the template file.
Fill the range B1:B4 with the relevant info.
Resulting in a new spreadsheet:
function formSubmitReceiver(e){
const itemResponses = e.response.getItemResponses()
const values = itemResponses.map( itemResponse => [itemResponse.getResponse()] )
const followUpDate = new Date()
followUpDate.setDate(followUpDate.getDate() + 7)
values.push([followUpDate])
const newFile = DriveApp.getFileById(TEMPLATE_ID).makeCopy()
const parentFolder = DriveApp.getFolderById(CLIENT_FOLDER_ID)
newFile.moveTo(parentFolder)
newFile.setName(values[0][0])
const newId = newFile.getId()
const newSpreadsheet = SpreadsheetApp.openById(newId)
const sheet = newSpreadsheet.getSheetByName("Sheet1");
const range = sheet.getRange("B1:B4")
range.setValues(values)
}
Depending on how complex you make your form, and what type of questions you choose (i.e multiple choice etc) this can get more complicated but hopefully it will give you a good idea of how to get something like this working. The simplest way is just to use "Short Answers" as this will just return a string. Also remember to make the questions "Required" if you don't want to handle empty values. Again, this all depends on how exactly you want to implement it and the complexity of the information involved!
References and Further Reading
FormResponse
getItemResponses()
ItemResponse
getFileById(id)
makeCopy()

Efficient Way of sending Spreadsheet over email using GAS function?

I am creating an addon for Google Sheets that my local High School's volunteer clubs can use to keep track of their member's volunteer hours. Most of the code is done and works very nicely, and I am currently working on a system that will send a member a spreadsheet listing all of the volunteer events that they have logged. I have GAS create a separate spreadsheet, and then send an email with that separate spreadsheet attached in PDF. When the email is received, the PDF is empty except for a singular empty cell at the top left of the page.
I am pretty new to GAS but have been able to grasp the content pretty easily. I have only tried one method of sending the Spreadsheet and that is by using the .getAs(MimeType.PDF). When I changed the "PDF" to "GOOGLE_SHEETS," GAS returned the error: "Blob object must have non-null data for this operation." I am not entirely sure what a Blob object is, and have not found any website or video that has fully explained it, so I am not sure how to go about troubleshooting that error.
I think I'm having a problem grabbing the file because it either sends an empty PDF or it returns an error claiming it needs "non-null data."
function TigerMail()
{
var Drive = DriveApp;
var app = SpreadsheetApp;
var LOOKUP = app.getActiveSpreadsheet().getSheetByName("Student
Lookup");
var Name = LOOKUP.getRange("E1").getValue();
Name = Name + "'s Hours";
//app.openById(Name+"'s Hours");
var HOURS = app.create(Name);
var ESheet = HOURS.getSheets()[0];
var ROW = LOOKUP.getLastRow();
var arr = LOOKUP.getRange("D1:J"+ROW).getValues();
var cell = ESheet.getRange("A1:G"+ROW);
cell.setValues(arr);
////////////////////////////////////////////////////
var LOOKUP = app.getActiveSpreadsheet().getSheetByName("Student
Lookup");
var cell = LOOKUP.getRange("D1");
var Addr = cell.getValue();
var ROW = LOOKUP.getLastRow();
var file = Drive.getFilesByName(Name);
var file = file.next();
var FORMAT = file.getAs(MimeType.GOOGLE_SHEETS);
TigerMail.sendEmail(Addr, "Hours", "Attached is a list of all of the
events you have volunteered at:", {attachments: [FORMAT]} );
}
the final four lines are where the errors are occurring at. I believe I am misunderstanding how the .next() and .getFilesByName() work.
(above the comment line: creating a spreadsheet of hours)
(below the comment line: grabbing the spreadsheet and attaching it to an email)
Here is the link to the Google Sheet:
https://docs.google.com/spreadsheets/d/1qlUfTWaj-VyBD2M45F63BtHaqF0UOVkwi04XwZFJ4vg/edit?usp=sharing
In your script, new Spreadsheet is created and put values.
You want to sent an email by attaching the file which was converted from the created Spreadsheet to PDF format.
If my understanding is correct, how about this modification? Please think of this as just one of several answers.
Modification points:
About Drive.getFilesByName(Name), unfortunately, there is no method of getFilesByName() in Drive.
I think that when you want to use the created Spreadsheet, HOURS of var HOURS = app.create(Name) can be used.
About var FORMAT = file.getAs(MimeType.GOOGLE_SHEETS), in the case of Google Docs, when the blob is retrieved, the blob is automatically converted to PDF format. This can be also used for your situation.
In order to save the values put to the created Spreadsheet, it uses SpreadsheetApp.flush().
When above points are reflected to your script, it becomes as follows.
Modified script:
Please modify as follows.
From:
var file = Drive.getFilesByName(Name);
var file = file.next();
var FORMAT = file.getAs(MimeType.GOOGLE_SHEETS);
To:
SpreadsheetApp.flush();
var FORMAT = HOURS.getBlob();
Note:
In your script, it seems that var ROW = LOOKUP.getLastRow() is not used.
References:
flush()
getBlob()
If I misunderstood your question and this was not the result you want, I apologize.

How to trigger Google Apps script function based on insert row via api

I have a Google Sheet with 5 columns (First Name, Address, SKU, Quote, Status).
I have an apps script function (createQuote) which looks at the above variable's values from google sheet row and create a google document quote replacing the variables to values.
I use Zapier to insert row into my above google sheet.
What am struggling with-:
I need a way to trigger my createQuote function right when a new row is inserted via zapier (Google Sheet API call).
I tried playing with triggers but couldn't make it, any help is appreciated.
thank you
here is the code for my function-
function quoteCreator(){
docTemplate = "googledocidgoeshere"
docName = "Proposal"
var sheet = SpreadsheetApp.getActive().getSheetByName("Main")
var values = sheet.getDataRange().getValues()
var full_name = values[1][0]
var copyId = DriveApp.getFileById(docTemplate).makeCopy(docName+" for "+full_name).getId()
// Open the temporary document
var copyDoc = DocumentApp.openById(copyId);
// Get the document’s body section
var copyBody = copyDoc.getActiveSection();
// Replace place holder keys/tags,
copyBody.replaceText("keyFullName", full_name);
copyDoc.saveAndClose();
// Convert temporary document to PDF by using the getAs blob conversion
var pdf = DriveApp.getFileById(copyId).getAs("application/pdf");
// put the link of created quote in the quote column
var url = DocumentApp.openById(copyId).getUrl()
var last = sheet.getRange(2, 7, 1, 1).setValue(url)
}
Note-: I haven't put the loop yet in above, i'll do that once it starts working as per my requirements.
Changes made via Sheets API or Apps Script do not fire onEdit triggers. I give two workarounds for this.
Web app
Have whatever process updates the sheet also send a GET or POST request to your script, deployed as a web application. As an example, a GET version might access https://script.google.com/.../exec?run=quoteCreator
function doGet(e) {
if (e.parameter.run == "quoteCreator") {
quoteCreator();
return ContentService.createTextOutput("Quote updated");
}
else {
return ContentService.createTextOutput("Unrecognized command");
}
}
The web application should be published in a way that makes it possible for your other process to do the above; usually this means "everyone, even anonymous". If security is an issue, adding a token parameter may help, e.g., the URL would have &token=myToken where myToken is a string that the webapp will check using e.parameter.token.
GET method is used for illustration here, you may find that POST makes more sense for this operation.
Important: when execution is triggered by a GET or POST request, the methods getActive... are not available. You'll need to open any spreadsheets you need using their Id or URL (see openById, openByUrl).
Timed trigger
Have a function running on time intervals (say, every 5 minutes) that checks the number of rows in the sheet and fires quoteCreator if needed. The function checkNewRows stores the number of nonempty rows in Script Properties, so changes can be detected.
function checkNewRows() {
var sp = PropertiesService.getScriptProperties();
var oldRows = sp.getProperty("rows") || 0;
var newRows = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Main").getLastRow();
if (newRows > oldRows) {
sp.setProperty("rows", newRows);
quoteCreator();
}
}

getCalendarById where the ID is a String

On a Google Spreadsheet there are several calendar IDs. Each is in Column B, and is in the format of "SuperLongStringGoesHere#group.calendar.google.com".
Things work when I manually enter the calendar ID instead of using a variable (the variable is calRngVal). When I use a variable, it returns null. How do I call getCalendarById using a variable?
var calRng = calids.getRange(a, 2);
var calRngVal = calRng.getDisplayValue();
var cal = CalendarApp.getCalendarById(calRngVal);
var ad = cal.getName();
Thanks in advance!
Here is a picture of the debugger:
You don't need to do anything special to access a calendar using a string value stored in a sheet. Both your approach and that mentioned in Simon's answer should work.
There must be something else going wrong. Are you sure you have permission to access all of the calendars listed in the sheet? If you don't have permission, getCalendarById will return null.
Wrap the code after your getCalendarById call in a try/catch block so that one "bad" calendar won't crash the script.
EG:
var calRng = calids.getRange(a, 2);
var calRngVal = calRng.getDisplayValue();
var cal = CalendarApp.getCalendarById(calRngVal);
try{
var ad = cal.getName();
//other code here
}catch(e){
Logger.log('Exception processing '+calRngVal+', row '+a+'. Message: '+e);
}
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch
Also note that looping and fetching the ranges one by one is a slow way to do this, you could use getDataRange() to return all the data in the sheet in a two dimensional array, then just loop over the array. However I would fix the "null" problem before optimizing.
Are you sure the code is selecting a cell with a calendar id value in it? Try this:
var calRng = calids.getRange("A1:B");
var calRngVal = calRng.getValues();
var cal = CalendarApp.getCalendarById(calRngVal[1][1]);
var ad = cal.getName();
If it doesn't work, try using the logger sometime:
Logger.log(calRngVal);

Google html service to sheets

I'm not a big fan of google forms so I made the form for my user input in the html service. I found a way to push the data out of the form and into google sheets using all of my variables in the html file like this:
<textarea type="text" name="Special Instructions" id="instructions"></textarea>
...
var instructions = document.getElementById("instructions").value;
...
google.script.run
.formsubmit (instructions,...)
google.script.host.close()}
in combination with the following in the code file:
function formsubmit(instructions,...)
var ss = SpreadsheetApp.getActive().getSheetByName("Sheet1");
ss.getRange(ss.getLastRow(),7,1,1).setValue(instructions);
...
The problem is, not only is the code very slow to output results, but if I have more than 37 or so variables defined, it glitches out and rather than closing the dialog box and recording the values in a spreadsheet, it opens a blank web page.
I know there has to be better (and more efficient) way, but I'm afraid I don't know it.
On the "client side", put all of your variables into a JSON object or an array, the stringify it, and send that string to the server.
var objectOfData;
variableOne = "one";
variable2 = "two";
objectOfData = {};
objectOfData['varOne'] = variableOne;//Create a new element in the object
objectOfData['var2'] = variable2;//key name is in the brackets
objectOfData = JSON.stringify(objectOfData);//Convert object to string
google.script.run
.formsubmit(objectOfData);
And then convert the object as a string back to a real object:
function formsubmit(o) {
var arrayOfValues,k,myData,outerArray;
myData = JSON.parse(o);//Convert string back to object
var ss = SpreadsheetApp.getActive().getSheetByName("Sheet1");
arrayOfValues = [];
for (k in myData) {//Loop through every property in the object
thisValue = myData[k];//
Logger.log('thisValue: ' + thisValue);//VIEW the LOGS to see print out
arrayOfValues.push(thisValue);
}
outerArray = [];
outerArray.push(arrayOfValues);
ss.getRange(ss.getLastRow() + 1,7,1,arrayOfValues.length).setValue(outerArray);
...
Note that the last parameter of getRange('start row', start column, number of rows, number of columns) uses the length of the inner array named arrayOfValues. This insures that the parameter value will always be correct regardless of how the array is constructed.