how to add to an array in appscript without resetting the array - google-apps-script

I'm trying to add to an array when a dropdown button is pressed but app-script seems to redeclare the array everytime an event is triggered. is there a way to stop this?
the code in question looks like this:
let by = [];
function addToArray (name = 'hello') {
by.push(name.replace(/\s[\s\S]*/, '').toLowerCase());
console.log(by.join(', '))
}
// ...
// function called every dropdown buttonclick
function handelAdd () {
const response = DocumentApp.getUi().prompt('Name:'); // prompt that gets name of item added
if (response.getSelectedButton() == DocumentApp.getUi().Button.OK) {
add(response.getResponseText());
}
}

I am going to assume that the following line in the question, here:
add(response.getResponseText()); // incorrect?
Should actually be this:
addToArray(response.getResponseText()); // correct?
Whenever your script finishes execution, any state it had saved (for example the value of the array in let by = [];) is lost.
This is to be expected. The script has finished. It exits. The document is still open, but the script has completed its work. It will run again the next time there is a relevant button click event.
To save state between multiple runs of the script, you can use Properties Services. This allows you to store a variable associated with the specific document (and user of that document). You can save your list to this storage, and retrieve it when you need to add a new item to it.
But you also need to store your array of data as a string in this storage - so in my example below I will convert the [...] array to a JSON string representation of the array, using JSON.stringify(). And I will convert back from JSON to an array using JSON.parse():
let by = [];
let byList = 'BY_LIST';
function addToArray (name = 'hello') {
let userProps = PropertiesService.getUserProperties();
// retrieve stored list and convert back from JSON to array:
by = JSON.parse(userProps.getProperty(byList));
console.log(by);
by.push(name.replace(/\s[\s\S]*/, '').toLowerCase());
userProps.setProperty(byList, JSON.stringify(by));
console.log(by.join(', '))
}
// ...
// function called every dropdown buttonclick
function handelAdd() {
const response = DocumentApp.getUi().prompt('Name:'); // prompt that gets name of item added
if (response.getSelectedButton() == DocumentApp.getUi().Button.OK) {
addToArray(response.getResponseText());
}
}
// used for my testing:
//function testMe() {
// handelAdd();
//}
function onOpen(e) {
// whenever the doc is re-opened, set the list to be
// empty, and store it in userProperties as a JSON string:
let userProps = PropertiesService.getUserProperties();
userProps.setProperty(byList, JSON.stringify( [] ));
}
The onOpen() trigger ensures that each time the document is opened, we start with a new empty list.

Related

Delete events from calendar that were previously created via

I have created event with this script:
function createCalendarEvent() {
let communityCalendar = CalendarApp.getCalendarById("calendar_ID");
let sheet = SpreadsheetApp.getActiveSheet();
let schedule = sheet.getDataRange().getValues();
schedule.splice(0, 2);
schedule.forEach(function(entry){
communityCalendar.createEvent(
entry[2],
entry[0],
entry[1],
{
guests: entry[3],
sendInvites: true
}
);
});
}
Now if I have made a mistake and would like to delete those events, how do I do that?
Thought it's easy as changing
communityCalendar.createEvent
to
communityCalendar.deleteEvent
But it won't work (obviously)
TypeError: communityCalendar.deleteEvent is not a function
(anonimowy) # Kod.gs:7
createCalendarEvent # Kod.gs:6
Much thanks,
Bartosz
As mentioned in Cooper's comment, you need to first retrieve an Event object before calling deleteEvent() on it. The safest way would be to store the IDs on another sheet. Luckily, createEvent() returns the Event object after you create it so you can get its ID right away. As an example based on your code:
const createdEvents = [] //blank array that will contain the list of created event IDs
schedule.forEach(function(entry){
//push the event IDs to the array
createdEvents.push(communityCalendar.createEvent(
entry[2],
entry[0],
entry[1],
{
guests: entry[3],
sendInvites: true
}
).getId()
//calling communityCalendar.createEvent(...).getId() will return the event ID
//as a string like "eq4p0j231j3qj412ddrmhmgg34#google.com"
)
});
At this point you can paste the list of IDs in a separate sheet. Then if you need to delete the events you can get the IDs from the sheet and use getEventByID() to process them
function deleteEvents(){
let communityCalendar = CalendarApp.getCalendarById("calendar_ID");
let sheet SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Events List");
let list = sheet.getRange("range where you pasted the IDs").getValues();
list.forEach(function(entry){
communityCalendar.getEventByID(entry).deleteEvent()
});
}
Alternatively, you could try to "reverse" your original function by using the original spreadsheet data to search for the events with getEvents(). This would probably work if you're the only one managing this calendar, but if there are other events that were not created by your script that also match the search criteria they would be deleted as well.
It would look something like this:
function deleteEvents() {
let communityCalendar = CalendarApp.getCalendarById("calendar_ID");
let sheet = SpreadsheetApp.getActiveSheet();
let schedule = sheet.getDataRange().getValues();
schedule.splice(0, 2);
schedule.forEach(function(entry){
communityCalendar.getEvents(
entry[0],//getEvents() takes starttime and endtime as well so we can reuse them
entry[1],
{
search: entry[2],
//from your code we know that entry[2] is the title so we can use it as a search query
}
)[0].deleteEvent(); //getEvents() returns an array so just take the first position and delete it
});
}
There's room for improvement but I hope this gives you a general idea of how it works. I would recommend to just store the IDs to avoid mistakes.

Trouble Passing Object back to html in Google Apps Script

I have the code side creating an array and am now trying to pass it back to the HTML Service. I am stringifiying the object and when I run the logger I see that it's sending the correct information over. However, when it get's back to the html side I'm just seeing "undefined" or empty arrays no matter what I try. Any help would be appreciated, code is below:
Here it the html side.
function editFunction(){
//I start by passing an object over to the code side, based on user input
var userChoice = document.getElementById("userChoice").value;
google.script.run
.editJob(userChoice);
//Code side gathers some information based on user input formats into arrays
//and sends back...theoretically
<?var editData = editJob();?>
var returnedValues =JSON.parse(<?=editData?>);//object parsed
var arrayOfValues = [];
for (var n in returnedValues) {
var thisValue = returnedValues[n];
arrayOfValues.push(thisValue);//object reformatted as an array
}
document.getElementById("instructions").value = arrayOfValues[1];
//desired out put part of the array`
}
This is the code side:
function editJob(userChoice){
var ss = SpreadsheetApp.openById('1emoXWjdvVmudPVb-ZvFbvnP-np_hPExvQdY-2tOcgi').getSheetByName("Sheet1");
var jobReference = [];
var job;
//uses user input to gather necessary information and puts into arrays
for (var i = 1; job!=""; i++){
job = ss.getRange(i,27).getValue();
jobReference.push(job);
};
for(var n=1; n<jobReference.length;n++){
if(jobReference[n]==userChoice){
break;
}
};
var returnEdit = [];
for (var int =1; int<28;int++){//update int< for number of column
var value = ss.getRange(n+1,int).getValue();
returnEdit.push(value);
};//final array now formed
var returnStringified = JSON.stringify(returnEdit);
Logger.log(returnStringified);
return returnStringified //stringified results theoretically sent over
}
While I think your question has already been answered, I just wanted to point out that server side function can NOT send any data by itself.
Client side code has to call the server side function which can return a bunch of values clubbed together as one parameter. If client should have more data, then it is up to the client side code to call another server side function and get the required data.
In short server side will answer only what client side asks for. Server side code on its own can NOT send any data unsolicited.
Your function will end after google.script.run.editJob(userChoice);
You need a second function to run on success and preferably one to run on error as well. The function names for the sample below are successFunction() and errorFunction():
google.script.run.withSuccessHandler(successFunction).withFailureHandler(errorFunction).serverSideFunc();
serverSideFunction() is the name of the function to run on the server side.
Create a new function to run on success and pass the data back to the function. Roughly, wothout debugging but to give a basic idea, it becomes:
function editFunction(){
//I start by passing an object over to the code side, based on user input
var userChoice = document.getElementById("userChoice").value;
google.script.run
.editJob(userChoice);
.withSuccessHandler(
}
function handleTheData(returnedValues) {
var arrayOfValues = [];
for (var n in returnedValues) {
var thisValue = returnedValues[n];
arrayOfValues.push(thisValue);//object reformatted as an array
}
document.getElementById("instructions").value = arrayOfValues[1];
//desired out put part of the array`
}
See the documentation for google.script.run.

Fill multiple rows in google spreadsheet via google script

I am currently working on a semester project for my university in which we want to log data from an Arduino to a Google Sheet.
I was following the numerous tutorials and examples that I could find on Google and it worked so far really, really well. My Arduino is able to upload data to said spreadsheet.
Unfortunately all those examples always only deal with one row to be filled. For our project we would like to fill 2 or 3 lines simultaneously.
I will shortly show what I have done so far and maybe you can help me solve my (probably easy) problem.
I created a google spreadsheet in which I want to log my data
I used the script from a tutorial that should fill one row.
By typing the following line in my browserhttps://script.google.com/macros/s/<gscript id>/exec?tempData=datahereI am now able to fill row one with my data in enter in the end of the url.
But how do I progress now, when I want to fill two or three rows of the table? I say that the author of the code already implemented an option to fill the third row, yet I can't find out what to input in my url then to fill it with data.
All my attempts to write something like
https://script.google.com/macros/s/<gscript id>/exec?tempData=datahere&tempData1=value2
just ended in writing
datahere&tempData1=value2
in my first row, not filling datahere into the first and value2 in to the second row.
How can I provide and write multiple rows of data?
The code in this script is:
/*
GET request query:
https://script.google.com/macros/s/<gscript id>/exec?tempData=data_here
*/
/* Using spreadsheet API */
function doGet(e) {
Logger.log( JSON.stringify(e) ); // view parameters
var result = 'Ok'; // assume success
if (e.parameter == undefined) {
result = 'No Parameters';
}
else {
var id = '<ssheet id>'; // Spreadsheet ID
var sheet = SpreadsheetApp.openById(id).getActiveSheet();
var newRow = sheet.getLastRow() + 1;
var rowData = [];
//var waktu = new Date();
rowData[0] = new Date(); // Timestamp in column A
for (var param in e.parameter) {
Logger.log('In for loop, param='+param);
var value = stripQuotes(e.parameter[param]);
//Logger.log(param + ':' + e.parameter[param]);
switch (param) {
case 'tempData': //Parameter
rowData[1] = value; //Value in column B
break;
case 'tempData1':
rowData[2] = value; //Value in column C
break;
default:
result = "unsupported parameter";
}
}
Logger.log(JSON.stringify(rowData));
// Write new row below
var newRange = sheet.getRange(newRow, 1, 1, rowData.length);
newRange.setValues([rowData]);
}
// Return result of operation
return ContentService.createTextOutput(result);
}
/**
* Remove leading and trailing single or double quotes
*/
function stripQuotes( value ) {
return value.replace(/^["']|['"]$/g, "");
}
I would suggest the following:
Create a 2d array of your data you wish to write to the spreadsheet. If your client on Arduino were using JavaScript this might look like :
var data = [
["row1value1", "row1value2"],
["row2value1", "row2value2"]
];
Convert this to JSON, again in JavaScript this might look like:
var json = JSON.stringify(data);
This gives you a string representation of your array.
Now make your request using this data. I would suggest you should look at using doPost instead of doGet, as you are sending data to the spreadsheet that updates state. However, for the purposes of getting something working, your URL would look like:
https://script.google.com/<.....>/exec?myarray=<stringified JSON>
In Apps Script, in your doGet (again, consider using doPost instead), you could then use:
// Get the JSON representation of the array:
var json = e.parameter.myarray;
// Convert back to 2d array
var data = JSON.parse(json);
Now you can write this to a Range in Sheets using setValues, e.g. assuming a rectangular 2d array:
sheet.getRange(1, 1, data.length, data[0].length).setValues(data);
Hope this helps

e.values in google forms skips empty answers, is there a workaround?

I have a Google form, which writes response data to a spreadsheet containing a script that is supposed mail the form-filler with his/her answers to the form.
I had success with e.values before, and the mail generated just fine. Now there seems to be some problems, as empty answers are skipped, meaning that e.values[9] for example becomes actually column 9, instead of 10, as it used to be (in the spring of 2014, at least). So if there are one or more fields left empty the following answers move backward in the array one or more steps, depending on the number of questions left empty. Behaviour like this wreaks havoc in carefully planned scripts!
I also tried to use namedValues, but it can't tolerate empty fields either!
Does anybody know of a work around? I'd appreciate ideas.
That open issue has been marked Working as intended. So no help there.
Here's a work-around. Also available in this gist.
Example
function onFormSubmit(e) {
fixFormEvent( e );
...
}
fixFormEvent( e )
/**
* Force blank reponses into event object's values property, so that the value's index
* correctly reflects the question order. (With "new Sheets" + "new Forms", blank responses
* are skipped in the event object.
*
* see http://stackoverflow.com/a/26975968/1677912
*
* #param {event} e Event received as a Spreadsheet Form object. The event's value
* property will be modified by this function.
* #return {event} The same event, for chaining
*/
function fixFormEvent( e ) {
var ss = SpreadsheetApp.getActive();
var formUrl = ss.getFormUrl(); // Use form attached to sheet
var form = FormApp.openByUrl(formUrl);
var items = form.getItems();
var resp = [e.namedValues["Timestamp"]];
for (var i=0; i<items.length; i++) {
switch (items[i].getType()) {
case FormApp.ItemType.IMAGE:
case FormApp.ItemType.PAGE_BREAK:
case FormApp.ItemType.SECTION_HEADER:
// Item without a response - skip it
break;
case FormApp.ItemType.CHECKBOX:
case FormApp.ItemType.DATE:
case FormApp.ItemType.DATETIME:
case FormApp.ItemType.DURATION:
case FormApp.ItemType.GRID:
case FormApp.ItemType.LIST:
case FormApp.ItemType.MULTIPLE_CHOICE:
case FormApp.ItemType.PARAGRAPH_TEXT:
case FormApp.ItemType.SCALE:
case FormApp.ItemType.TEXT:
case FormApp.ItemType.TIME:
// If item has a response, append it to array. If not, append blank.
var itemTitle = items[i].getTitle();
var type = items[i].getType();
if (itemTitle === "") throw new Error( "Untitled item" );
var itemResp = [];
if (itemTitle in e.namedValues) {
itemResp = e.namedValues[itemTitle];
}
resp.push( itemResp );
break;
default:
Logger.log( "Unknown item type, index=" + items[i].getIndex() );
break;
}
}
e.values = resp;
return e; // For chaining
}
Can confirm this behaviour, and there is an open issue (at the time of this answer) for this.
As for a work around, could you process the namedValues object to work out which values are missing?
Instead of e.values use e.range.getValues().flat()
e.range.getValues() will return an Array holding an Array with the response values logged in the spreadsheet)
Array.prototype.flat() will convert the previous bidimensional Array in a "unidimensional" Array.
Resources
https://developers.google.com/apps-script/guides/triggers/events#form-submit
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat

Error when trying to store an array in ScriptDb

I have an array of objects that is created by my script and I am trying to copy that array into a new array and then store it in scriptDb using the following function:
function copyAndStore (currentArray) {
var db = ScriptDb.getMyDb();
var copyArray = [];
for (var i in currentArray) {
copyArray.push(currentArray[i]);
}
var id = db.save(copyArray);
return id;
}
It copies everything properly but when it gets to var id = db.save(copyArray); I get the error: Invalid argument. Expected a javascript map object.
Does ScriptDb have issues with storing arrays? Thanks in advance for the help.
As #Thomas said, you can save an array in a map object.
You don't need to perform a copy operation before putting an object into the ScriptDB, either. You could save your array by simply db.save({myArray}), and remember the ID.
Here's some minimalist code to demonstrate. I'm showing two ways to retrieve your saved array - one by ID, which seems to be the way you were planning to, but also a second way using a "key" value for a query. If you expect to retrieve the contents of ScriptDB in a later run of your code, this approach eliminates the need to somehow remember the ID of the stored array.
function saveArray (currentArray) {
var db = ScriptDb.getMyDb();
return db.save({type: "savedArray", data:currentArray}).getId();
}
function loadArrayById (id) {
var db = ScriptDb.getMyDb();
return db.load(id).data;
}
function loadArrayByType () {
var db = ScriptDb.getMyDb();
var result = db.query({type: "savedArray"});
if (result.hasNext()) {
return result.next().data;
}
else {
return [];
}
}
function test() {
var arr = ['this','is','a','test'];
var savedId = saveArray( arr );
var loaded1 = loadArrayById( savedId );
var loaded2 = loadArrayByType();
debugger; // pause if running debugger
}
Here's what you'll see at the debugger pause:
Note that by using the map tag data to pull the array from the saved object, both loaded1 and loaded2 are identical to the source array arr.
ScriptDb only stores map objects. You could however store a map that contains an array!
You can use arrays to save several objects in a single call using db.saveBatch.