Dynamic Removal of Multiple Form Options in Google Forms - google-apps-script

I have a Google form where individuals are to enter in their employer preference and then a time for that employer. I currently have a script that is supposed to function where the form is updated to eliminate options after certain thresholds are reached. However, even when using the form with one option, it does not function correctly.
So, I have two questions here:
1) How do I get this type of action to occur in the form without utilizing the provided Google apps formLimiter and Choice Eliminator?
Here is the script code I was using:
function availableSlots()
{
var form = FormApp.openByUrl('https://docs.google.com/forms/d/e/1FAIpQLSdLRI6BmDXdCCygpHMr7o8MtnYLEVdrnumoHJfW-j_uTZCNiA/viewform?usp=sf_link');
var slots = SpreadsheetApp
.getActiveSpreadsheet()
.getRange("Sheet2!A7:C10")
.getValues();
var choice = [];
{
if (slots[s][0] != "" && slots[s][2] > 0) {
choice.push(slots[s][0]);
}
}
var formItems = form.getItems(FormApp.ItemType.LIST);
formItems[0].asListItem().setChoiceValues(choice);
}
2) Is there a way for the code to check for not only if the employer has been met by the max amount of respondents - because of all times being chosen already - and relay a message to the respondent that this employer is booked, but also behave in a way where if one time is taken for one employer, only that time is taken away if that employer was selected?

Related

How to reduce the latency between two script calls in Google Apps Script

In a self-developed add-on for Google Sheets, the functionality has been added that a sound file will be played from a JavaScript audio player in the sidebar, depending on the selection in the table. For the code itself see here.
When a line is selected in the table the corresponding sound file is played in the sidebar. Every time the next line is selected it takes around 2 seconds before the script will start to run and load the sound file into the sidebar. As the basic idea of the script is to quickly listen through long lists of sound files, it is crucial to reduce the waiting time as fare as possible.
A reproducible example is accessible here; Add-ons > 'play audio' (Google account necessary). To reproduce the error, the sheet has to be opened two times (e.g. in two browsers).
In order to reduce the latency you might try to reduce interval on your poll function as suggested by Cooper on a comment to the question and to change the getRecord function.
poll
At this time the interval is 2 seconds. Please bear in mind that reducing the interval too much might cause an error and also might have an important impact on the consume of the daily usage quotas. See https://developers.google.com/apps-script/guides/services/quotas
getRecord
Every time it runs it make multiple calls to Google Apps Script which are slow so you should look for a way to reduce the number of Google Apps Script calls. In order to do this you could store the spreadsheet table data in the client side code and only read it again if the data was changed.
NOTE: The Properties Service has a 50,000 daily usage quota for consumer accounts.
One way to quickly implement the above is to limit the getRecord function to read the current cell and add a button to reload the data from the table.
Function taken from the script bounded to the demo spreadsheet linked in the question.
function getRecord() {
var scriptProperties = PropertiesService.getScriptProperties();
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var headers = data[0];
var rowNum = sheet.getActiveCell().getRow(); // Get currently selected row
var oldRowNum = scriptProperties.getProperty("selectedRow"); // Get previously selected row
if(rowNum == oldRowNum) { // Check if the was a row selection change
// Function returns the string "unchanged"
return "unchanged";
}
scriptProperties.setProperty("selectedRow", rowNum); // Update row index
if (rowNum > data.length) return [];
var record = [];
for (var col=0;col<headers.length;col++) {
var cellval = data[rowNum-1][col];
if (typeof cellval == "object") {
cellval = Utilities.formatDate(cellval, Session.getScriptTimeZone() , "M/d/yyyy");
}
record.push({ heading: headers[col],cellval:cellval });
}
return record;
}
Related
Problems when using a Google spreadsheet add-on by multiple users

Google Sheets: Action Based on Birthday

I'm trying to send myself either an email or copy the row to a new sheet when it's someone's birthday or hire date anniversary. Copying the line to a new sheet would allow me to use zapier to notify me of the update. Either would work. The sheet uses a form to collect data.
I've built a few scripts but nothing that had to do with dates. I'm just struggling with this one and have tried a few examples I could find with no luck.
Here is this sheet. It's view only so just let me know if you need more access.
I understand that you want to replicate your form responses Sheet in another Sheet (let's call it Zapier Sheet) automatically each time that a new form response is added. You can achieve that goal developing an Apps Script code that runs at each form response. In that case you can use a code similar to this one:
function so62400514() {
var formSheet = SpreadsheetApp.openById(
'{FORM SHEET ID}').getSheets()[0];
var zapierSheet = SpreadsheetApp.openById(
'{ZAPIER SHEET ID}').getSheets()[0];
var formData = formSheet.getRange(1, 1, formSheet.getLastRow(), formSheet
.getLastColumn()).getValues();
var zapierData = zapierSheet.getRange(1, 1, zapierSheet.getLastRow(),
formSheet.getLastColumn()).getValues();
var recorded = false;
for (var fr = 0; fr < formData.length; fr++) {
for (var zr = 0; zr < zapierData.length; zr++) {
if (formData[fr].toLocaleString() == zapierData[zr].toLocaleString()) {
recorded = true;
}
}
if (recorded == false) {
zapierSheet.appendRow(formData[fr]);
} else {
recorded = false;
}
}
}
This code will first open both sheets (using SpreadsheetApp.openById() and Spreadsheet.getSheets()) to select the data with Sheet.getRange (setting boundaries with Sheet.getLastRow() and Sheet.getLastColumn()) and reading it using Range.getValues(). After that operation the data will get iterated using the property Array.length as the perimeter. The iteration compares each row from the form Sheet to every row of the zapier sheet (to accomplish that, I first parsed the row as a string with Date.toLocaleString()). If the form row is found in the zapier sheet, the boolean recorded will flag to true. After every row on the zapier sheet gets compared to the form row, the code will write it down on the zapier sheet based on the boolean flag.
As explained in the previous paragraph, this code will take the form sheet rows not present in the zapier sheet; and paste them on the zapier sheet. I used this approach to prevent missing any row (as it could happen when simultaneous users answer the form all at once). To make this fire automatically you'll need to set up an installable trigger with these settings:
As an example, let's say that we have these form responses:
And our initial sample zapier sheet looks like this one below. Please, notice how several past rows are missing;
After running the script (as it will do automatically) this would be the result:
I suggest running the script manually for an initial setup. If the timestamps diverge, please check if both spreadsheets share time zones. Don't hesitate to ask me further questions to clarify my answer.

Can I use Google Apps Script to make a Google Form display randomized text from a Google Sheet?

Say I have a list of 1000 animals in a Google Sheet (e.g., dog, cat, cow, ..., giraffe). I'll like the Google Form to randomly pick one of these animals every time a respondent opens the Form.
E.g., Have you ever seen a __________ ?
Here, the blank would be different for every respondent (unless they were lucky enough to randomly get matching animals).
I currently have the code to randomly select an animal from the Google Sheet, but I can't figure out how to randomly select an animal for each respondent, since the onOpen() function cannot trigger for every respondent, but only when the owner opens the Form.
function onOpen(e){
var animals = worksheet.getRange(2, 1, worksheet.getLastRow()-1, 1)
.getValues()
.map(function(o){ return o[0]})
.filter(function(o){return o !== ""});
//Logger.log(animals)
// get random animal
var animal = animals[Math.floor(Math.random()*animals.length)];
Logger.log(animal);
var id = getBlockIdFromTitle()
Logger.log(id)
if (id !== -1){
updateLink(id, animal)
}
}
Any advice on how to change my code or do a completely different approach to achieve the same results will be appreciated. Thanks!
Instead of onOpen trigger, use the installable onFormSubmit trigger
This will allow you to update your form question after a respondent has submitted the form.
Sample:
function onFormSubmit(e){
var animals = worksheet.getRange(2, 1, worksheet.getLastRow()-1, 1)
.getValues()
.map(function(o){ return o[0]})
.filter(function(o){return o !== ""});
//Logger.log(animals)
// get random animal
var animal = animals[Math.floor(Math.random()*animals.length)];
FormApp.openById("XXX").getItems()[0].asTextItem().setTitle("Have you ever seen a " + animal + "?");
}
}
Mind:
Since the question will only be updated on form submit, the respondents that will open the form before the preceding respondent finishes submitting will not see a different version of the form.
However, currently there is no other option to change the question contents dynamically for each respondent.
If it is helpful for your - there options to shuffle question order and answer options to different respondents.

Converting "Create Multiple Choice Form Question from spreadsheet" script to "Create Checkbox etc." script - error?

Question: What is causing the error in this script when replacing "asMultipleChoiceItem()" with "asCheckboxItem"? And is there an obvious way to correct it?
Short version: I am trying to implement a checkbox version of the solution (found here), by replacing "item.asMultipleChoiceItem()" with "item.asCheckboxItem()"; however, I'm encountering an error on debugging "Invalid conversion for item type: MULTIPLE_CHOICE." (debug image here). I'm having trouble troubleshooting to identify/understand, and therefore figure out how to correct, the error.
My code:
function DeptUpdate() {
// User settings
var OPTIONS_SPREADSHEET_ID = "1q21HxRkwXxVtiw7D5fuO-w0JCQtZRd-A35gRtmJUwKk";
var OPTIONS_SHEET_NAME = "Present";
var OPTIONS_RANGE = "A:A"; // We have the options, listed in column A
var itemNumber = 1; // which question on form does this apply to
//
var options2DArr = SpreadsheetApp.openById(OPTIONS_SPREADSHEET_ID).getSheetByName(OPTIONS_SHEET_NAME).getRange(OPTIONS_RANGE).getValues();
var options = options2DArr.reduce(function(a, e) {
if (e[0] != "") {
return a.concat(e[0]);
}
return a;
}, []);
var form = FormApp.openById("1JHZoCdJrsRIltMwKqWGZizRQuy-2Ak2-XET83s04goc");
var item = form.getItems()[itemNumber - 1];
item.asCheckboxItem()
.setTitle("SELECT NAME")
.setChoiceValues(options)
.showOtherOption(true);
}
Long version:
Goal: Google script in Google sheets, on trigger, updates targeted form's checklist options to reflect the items listed in the defined range (excluding blanks).
Purpose/Context: This is one part of a series of forms and spreadsheet that allow me to track arrivals, hall passes out/in, and departures from a study hall in which any 10-20 students from a pool of 120 possible may attend any given day. Spreadsheet is linked to forms to provide a "head's up display" of which students are present, and which are signed out to other locations (this all works fine). Restricting Hall Pass Out and Departure form answer choices (student names) to only those check in as "present" drastically cuts down on time and user errors in the logging system. Currently works with multiple choice, but students frequently arrive/leave in groups. Checkbox (multiple response) would further expedite the tracking process. Spreadsheet is otherwise set up to process multiple response entries; just need the form to appropriately update.
Process/Attempts: I've read of others who adjusted similar (different purpose) scripts to change from dropdown/multiple choice to checkbox without issue ("I just change this and it worked, great!" is the extent of what I've read), but as soon as I change to checkbox, I get the attached error for both the showOtherOption field, and (if that is removed), the setChoiceValues field. I'm thinking it could possibly be an issue with the checkbox item reading the array differently than the multiple choice item does? However, I haven't be able to find anything in the documentation or Q/A posts on a significant difference between the two functions parameters. At this point, I'm just a little flummoxed on what might be causing the issue.
Background: I have no formal (or significant informal) coding training, but have tweaked and adapted a variety of codes for about a decade. I understand the basic processes/concepts of code and programming logic, but lack a substantial/cohesive vocabulary.
I'm including a link to a dummy copy of the spreadsheet and the form, in case that's helpful.
Spreadsheet
Form
Thank you in advance for any insights!
Brandy
The problem is that you have a ListItem but try to convert it to a CheckboxItem
This is not directly possible. There is a feature request on Google's Public Issue Tracker for this feature. I recommend you to give it a "star" to increase visibility.
In the mean time, if you want to convert an item type, you need to do it manually by creating a new item, passing it the title and choice from the old one and deleting the old item:
Sample
function DeptUpdate() {
// User settings
var OPTIONS_SPREADSHEET_ID = "1wHE6b5ZuAKJTM4N7t6nlB5SdU9h24ueuxon4jnH_0sE";
var OPTIONS_SHEET_NAME = "Present";
var OPTIONS_RANGE = "A:A"; // We have the options, listed in column A
var itemNumber = 1; // which question on form does this apply to
//
var options2DArr = SpreadsheetApp.openById(OPTIONS_SPREADSHEET_ID).getSheetByName(OPTIONS_SHEET_NAME).getRange(OPTIONS_RANGE).getValues();
var options = options2DArr.reduce(function(a, e) {
if (e[0] != "") {
return a.concat(e[0]);
}
return a;
}, []);
var form = FormApp.getActiveForm();
var listItem = form.getItems()[itemNumber - 1];
listItem
.asListItem()
.setTitle("SELECT NAME")
.setChoiceValues(options)
var title = listItem.getTitle();
var choices = listItem.asListItem().getChoices();
var checkboxItem = form.addCheckboxItem();
checkboxItem.setTitle(title)
.setChoices(choices)
.showOtherOption(true);
form.deleteItem(listItem);
}

Allowing others to add Google tasks

I am looking for a way for employees to send me an email or add information to a spreadsheet that will then add tasks to my task list. Ideally, the script would capture the task list, task, due date, and any notes.
I have already successfully implemented five scripts (five task lists) that allow my employees to add tasks to specific tasklists, following this script shown below. This works OK but does not have the capacity to add due dates or notes:
Automated email to task list API
I recently came across references to scripts that monitors task lists, and then posts them to a spread sheet, including task, due dates, notes, etc. It strikes me that a spreadsheet might be a better way to do this though it does not have the convenience of email:
Task list to spreadsheet API
I wonder if the REVERSE can be done. I envision a spreadsheet that I could give my employees access to, with two worksheets (NEW and PROCESSED) with columns:
TASKLIST TASK DUE DATE NOTES
and the script would run through this every hour or two. Anything in NEW would be processed and added to my task list, then moved to the end of PROCESSED.
Does anyone know of something like that out there? Alternatively, perhaps there are ways to change the email script so that it moves anything in the body of the email into the NOTES section of the task. I am a raw newbie at this BTW. Thanks.
you should replace
var newTask = Tasks.newTask().setTitle(title);
by
var newTask = Tasks.newTask().setTitle(title).setDue(date).setNotes(notes);
I'm also stuck in the way
I can from a spreadsheet :
- Create a new tasklist
- Create a new task in a dedicated tasklist (with due date and notes)
I can from the Gtasks :
- Check if the task is completed and mark it as completed in the spreadsheet
- Check if the task still exists in the spreadsheet and remove it if necessary
I'm still looking for a way to make a task completed in GTasks when it's closed in spreadsheet
All the functionality exists for you to accomplish this, but I don't know if there is a pre-built script out there that does what you want. You may want to look into use a Google Form that saves data to the spreadsheet, and then create a trigger for form submit that scoops up the data and creates a new task using it.
Is this [part] of what you're looking for?
https://developers.google.com/apps-script/articles/google_apis_reading_list
It syncs a Spreadsheet based task list with a your regular Task List, and if you mark the task done in gmail, it records that back in the spreadsheet.
// Fetch the list of URLs to keep synchronized
var articleUrls = SpreadsheetApp.getActiveSheet().getRange("A2:A");
for (var rowNum = 0; rowNum < articleUrls.getNumRows(); rowNum++) {
// Limit our range to a single cell containing a URL
var oneUrlCell = articleUrls.offset(rowNum, 0, 1, 1);
if (oneUrlCell.getComment() === "") {
// This is a new URL that needs to be shortened/inserted
var urlText = oneUrlCell.getValue();
if (urlText !== "") {
// Shorten the URL
Logger.log("Adding task for url: " + urlText);
var toShorten = UrlShortener.newUrl().setLongUrl(urlText);
var shortened = UrlShortener.Url.insert(toShorten);
// Insert the shortened URL into our reading list
var taskToInsert = Tasks.newTask().setTitle(shortened.getId());
taskToInsert.setNotes(urlText);
var newTask = Tasks.Tasks.insert(taskToInsert, readingListId);
// Save the new ID as our comment.
oneUrlCell.setComment(newTask.getId());
}
} else {
// This URL has already been inserted, update the status
var existingTask = Tasks.Tasks.get(readingListId, oneUrlCell.getComment());
if (existingTask.getStatus() === "completed") {
var absRowNum = oneUrlCell.getRow();
var completedCell = sheet.getRange(absRowNum, 2);
completedCell.setValue("Yes");
}
}
Should be part of the solution, no?
I'm looking to make something a bit bigger myself.