I have several Google forms which are reused frequently with around 300 - 400 form submission each time.
Until now, I have two ways to reset form, by hand:
Deleting the form while keeping the form editor opened.
Select the rows that contain form submissions, right click and choose "Delete ... rows".
With script, I've have no luck finding such method like form.reset().
The method .DeleteRows(fromRow, to Row) just delete the rows, but doesn't affect the counters.
So, how can I use Google apps script to automate the "reset form" task? Or, how to mimic the second way in script?
Thank you!
Google Forms has created a new form editor (released just a few weeks ago). This new form editor has an option for "Delete all responses." So one option would be to re-create the form in the new form editor. (From a new spreadsheet, click Tools > Create a form).
Note: This new form editor isn't available to Google Apps users yet.
I have a working solution, that uses deleteRows(). I'm not sure why that didn't work for you, it would be interesting to see your code.
The new Forms product (February 2013) is quite different than the legacy forms - this solution will work on legacy forms only.
Anyway, here's what I have, with before & after screenshots. The tidy() function can be called by passing in the number of rows to be removed, or you can call it from a menu as demonstrated here. A more complete version of this script is available as a gist.
/**
* Standard onOpen provided in script template.
*/
function onOpen() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{name : "Read Data",functionName : "readRows"},
{name : "Tidy Form Responses", functionName : "tidy"}];
sheet.addMenu("Script Center Menu", entries);
};
/**
* Prompt user for the number of form response rows to remove, then delete them.
* Assumes that the form responses are in the active sheet, that there is one
* row of 'headers' followed by responses (in row 2).
*
* #param {number} thisMany (Optional) The number of rows to remove. If not
* specified, user will be prompted for input.
*/
function tidy(thisMany) {
if (tidy.arguments.length == 0) {
thisMany = Browser.inputBox("How many responses should be tidied?");
}
var sheet = SpreadsheetApp.getActiveSheet();
sheet.deleteRows(2, thisMany);
}
Before
During
After
Related
Current function: Using apps script, I delete a few tabs then add a few tabs. So new a new sheet is like 'Sheet 467'. The number is getting big very quickly even though there are only 8 sheets in the file.
Desired function: I'd like to reset the sheet counter so the next sheet is 9 (number of sheets currently in the file +1) rather than 468 (number of sheets there have ever been in the file +1).
I tried to google this but I think the search terms are too generic. I couldn't find it anywhere.
Explanation
Unfortunately, this is the default behaviour of how sheets are created. You can request it as a feature but I don't think this will ever be implemented.
However, I would like to propose the following two workarounds:
User inserts a new sheet:
Take advantage of the onChange trigger and especially the event
object property changeType. When you add a new sheet, the latter
takes the value of INSERT_GRID.
This allows you to trigger a piece
of code when a new sheet is added by the user.
The following script will check whether you created a new sheet. If you did, it will rename the last created sheet as Sheet plus the total number of sheets.
Script inserts a new sheet:
If the new sheets are created by a script the onChange trigger can't be used. Then use the second solution I propose.
Workarounds:
Solution when the user inserts a new sheet:
function nameNewSheet(e) {
if (e.changeType == 'INSERT_GRID') {
const sheets = SpreadsheetApp.getActive().getSheets();
sheets[sheets.length-1].setName(`Sheet${sheets.length}`)
}
}
In order to use this solution you have to create an installable onChange trigger for the nameNewSheet function. Please read this answer on how to do that.
Solution when the script inserts a new sheet:
If the script itself adds new sheets, then onChange won't be triggered. But you can incorporate the following two lines into your existing script that inserts new sheets in order to rename the last created sheet.
function myFunction() {
//
// the code of the script that adds a new sheet
//
const sheets = SpreadsheetApp.getActive().getSheets();
sheets[sheets.length-1].setName(`Sheet${sheets.length}`);
}
Illustration:
I have a spreadsheet on Google sheets with 2 sheets, Sheet1 and Sheet2. On Sheet1 I have 2 columns (ID1 and Result1), in which are filled. On Sheet2 I have 2 columns (ID2 and Result2), in which ID2 is filled and Result2 is filled with the word "empty".
Sheet1 in my real spreadsheet is getting data from Google Forms, so every time Sheet1 receives a value on ID1 and Result1, it should search for the ID1 value on Sheet2's ID2 and when finding, paste Result1 value in Result2 in the row it's was found.
Result2 would then only be updated when there's new data on Sheet1 submitted from the form.
I created this editable form and spreadsheet to make it easier to understand (I also added a note explaining it in there). You can see the form here and the spreadsheet here.
In cell B2 of your second sheet I entered the following:
=VLOOKUP(A2,Sheet1!A:B,2,FALSE)
I guess VLOOKUP on Google-Sheets does allow reference across worksheets?
FYI, when entering the formula, after enter the first argument which is your look up criteria, you can click your sheet1 and highlight column A and B, then go back to your sheet2 (with the second argument automatically filled by the system) to finish the formula with the third (result range column position within the look up range) and fourth argument (TRUE for approximate match and FALSE for exact match) and hit Enter to exit the formula.
After looking through the current answers and comments I think I understand what you are looking for.
Answer:
You can do this in Google Apps Script by creating a function that is bound to your Google Form which collects the latest response and does the data processing, and making it run on Form submission.
Steps to take:
Firstly, on your Form, you will need to create a bound script. From the menu in the top right of the Form edit page, click ⋮ > Script editor, which will open a new script page.
From there you can make a script which will automatically do this for you, and make an Installable Trigger which runs when you need.
Code:
After opening the Script Editor, you will see a ready-to-edit function that looks like this:
function myFunction() {
}
Replace the entire script with the following code:
function onSubmit(e) {
var responses = FormApp.getActiveForm().getResponses();
var response = responses[responses.length - 1].getItemResponses();
var connectedSheet = SpreadsheetApp.openById('<your-sheet-id>').getSheets();
var sheet2 = connectedSheet[1];
var result2Column = sheet2.getRange('A1:A').getValues();
for (var i = 0; i < result2Column.length; i++) {
if (response[1] == result2Column[0]) {
sheet2.getRange('B' + (i +1)).setValue(response[0]);
}
}
}
Make sure to replace <your-sheet-id> with the unique ID of your Google Sheeet - you can find this in the URL of the sheet between the d/ and /edit like so:
https://docs.google.com/spreadsheets/d/<your-sheet-id>/edit
Run the script by pressing the play button (►) and authorise the application to run.
Then go to Edit -> Current Project's Triggers and set up a new installable trigger with the following settings:
Choose which function to run: onSubmit
Choose which deployment should run: Head
Select event source: From form
Select event type On form submit
Explanation:
This script will run each time a new form submission is made automatically - it will take the ID from the form response and search Sheet2 for it. If it's found, then the response given for result will be put in Sheet2 also, next to the corresponding ID.
References:
Google Apps Script Installable Triggers
do it simply like this:
=ARRAYFORMULA(IFERROR(VLOOKUP(A2:A, Sheet1!A:B, 2, 0)))
or reset by checkbox:
I've got a Google form that I've augmented with an apps script that is triggered with the form "on open" event. The script (see below) updates the form description with a value from the source sheet and it works as expected.
The problem arises when the form is submitted and then a new "question" is generated. Because the form updates the sheet, I'd expect the new question to show an updated value in the description. But it shows the previous (outdated) value.
I can force the value to be updated by "running" the script in the script editor.
Is there a work around to prevent the value being cached by the form?
Script:
function DisplayBalanceInDescription() {
var form = FormApp.getActiveForm();
var ss = SpreadsheetApp.openById(form.getDestinationId()).getSheetByName('sheet 1');
var data = ss.getDataRange().getValues();
var balance = data[0][6]; // first row, 7th column
form.setDescription("Current account balance is $" + balance);
}
Some extra details:
The value is cached even when the form is viewed in a different browser for the first time, so the browser is not caching the value
the target sheet for the form is copied to a different sheet via a query function
the cell that holds the "balance" is a calculation based on the "copied" sheet (above)
opening/viewing the sheet does not seem to be required as simply running the script results in the value being updated (sheet not open when run)
I've tried adding a cell.setFormula("=QUERY('foo bar')) to my script function, thinking I needed to trigger the query but this had no effect on caching, despite running without any syntax errors
(edit based on comments) manually editing the spreadsheet and then exiting (so it saves) also results in the form caching the old value, so it appears that the form app is performing its own caching and not the sheets app - I think the call to read the sheet data (.getValues() or thereabouts) is where the caching is occurring...
The title is the question.
I'm working on a spreadsheet in Google Drive where we got a central sheet named Name registry with names of the people who ever visited some place where notebooks are left at for them to write their name down on. The sheet calculates various stats, too.
The sheets for different places will be named as whatever the specific notebooks were named. The name registry saves us writing and fixing by referencing the cell of a particular name.
I'm sure there won't be anywhere like 255 sheets (which should be a lifted limit in the new Sheets), but I came across an idea how to have it automated:
1) Optionally a new sheet is added and the spreadsheet notices it.
2) Once either
2.1) the new sheet is renamed, the `Name registry` will auto-name one of the free columns as the renamed sheet; or
2.2) an old sheet is renamed, the sheet's old column is renamed in the `Name registry`
I did check on things, but the documentation is, frankly, brainlessly organized, so the only option is to use Google on its own resources.
For example, https://developers.google.com/apps-script/understanding_events implies it can't be done anyhow.
You should know that there is, indeed, a trigger that fires when inserting one sheet (I'm guessing that's what you call "table" in your question).
Re-check the docs for the spread sheet Change event, this event only fires if you use an installed trigger. To use it (copied from the docs)
Open or a create a new spreadsheet.
Click the Unsaved spreadsheet dialog box and change the name.
Choose Tools > Script Editor and write the function you want to run.
Choose Resources > Current project's triggers. You see a panel with the message No triggers set up. Click here to add one now.
Click the link.
Under Run, select the function you want executed by the trigger (for example myFunction).
Under Events, select From Spreadsheet
From the next drop-down list, select On Change.
Click Save.
Please notice that this trigger is not the same as the simple onEdit one.
Also, bear in mind that your callback function must receive the event parameter:
function myFunction(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.getSheetByName("Sheet1").getRange("A1").setValue(e.changeType);
}
You'll see that there are different types of changes: EDIT, INSERT_ROW, INSERT_COLUMN, REMOVE_ROW, REMOVE_COLUMN, INSERT_GRID, REMOVE_GRID, OTHER. You're looking for INSERT_GRID for inserting new sheet and OTHER for renaming it.
Having said that, the problem here will be detecting the rename of one sheet as its an OTHER type, so you may easily trigger the function when you don't really want to do it (for example changing background color, also fires the onChange event with OTHER edit type). My advice is that you don't try to detect the createion/change but create a custom menu entries for handling the creation and renaming of sheets:
function onOpen()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [ {name: "Create sheet", functionName: "createNewSheet"},
{name: "Rename current Sheet", functionName: "renameCurrentSheet"}
];
ss.addMenu("Custom operations", menuEntries);
}
function createNewSheet()
{
// create the new sheet
// also execute the logic you want when new sheet is created
}
function renameCurrentSheet()
{
// rename current sheet
// also execute the logic you want when sheet is renamed
}
Hope I put some light to your problem.
I'm a high school teacher in L.A. trying to create a course registration system using Apps Script. I need the Google Form I'm using for this registration to:
Question 1) Update the choices available in subsequent multiple choice questions on new pages based on a student's current response choices.
Question 2) Eliminate choices from the form when a multiple choice option has reached it's "cap".
Question 1 Example)
A student registers for “tie-tying” in workshop 1, and gets taken to a new page. The Script edits the available choices on that new page based on the student’s first choice, and removes “tie-tying” from the list of possible choices on that new page, so “etiquette” is their only remaining option.
Question 2 Example)
Students can either register for “tie-tying” or “etiquette”, both responses are initially available in the Google Form. 30 students take the survey, all 30 register for the “tie-tying” workshop. The Apps Script references the response spreadsheet, realizes the “tie-tying” workshop is full, then removes it from the Google Form's list of possible choices. Student 31 goes to register, and their only option is “etiquette”.
If my question has already been asked and answered (believe me, I did search!) I'd appreciate the redirection.
I believe we can achieve your second objective without too much difficulty and modify the form, based on the current state of response.
The approach is to
Create the form and associate it with a response spreadsheet
In that response spreadsheet, create a script with a function (updateForm for instance)
Bind that function with the onFormSubmit event, see Using Container-Specific Installable Triggers.
Analyse the response in the updateForm function and modify your form using the Form Service
For instance
function updateForm(e) {
if (e.values[1] == 'Yes') {
Logger.log('Yes');
var existingForm = FormApp.openById('1jYHXD0TBYoKoRUI1mhY4j....yLWGE2vAm_Ux7Twk61c');
Logger.log(existingForm);
var item = existingForm.addMultipleChoiceItem();
item.setTitle('Do you prefer cats or dogs?')
.setChoices([
item.createChoice('Cats'),
item.createChoice('Dogs')
])
.showOtherOption(true);
}
}
When it comes to achieving the goal in your first question, its more delicate, as the form will not submit mid way. What is possible is to go to different pages based on different responses to a Multiple Choice question, your use case may fit this method, although its not very dynamic.
Further its possible to use html Service to create completely dynamic experience.
Let me know if you need further information.
You are not able to create this type of dynamic form using the Google Forms Service, because there is no interaction between the service and scripts during form entry, except upon Form Submission. In the case of a multi-page form, a script has no way to know that a student has completed one page and gone on to another.
You could achieve this using the HtmlService or UiService, though. In either case, you'd rely on the client-side form interacting through server-side scripts to get updated lists of course options, then modifying the next 'page'. It will be complex.
The other answer to this question will keep adding a multichoice select each time for the form is submitted. Using similar approach of:
Create the form and associate it with a response spreadsheet
In that response spreadsheet, create a script with a function (updateForm for instance)
Bind that function with the onFormSubmit event, see Using Container-Specific Installable Triggers.
Analyse the response in the updateForm function and modify your form using the Form Service
I've used the following code to modify a list select which could be easiliy modified for a multiple choice.
function updateForm(){
var form = FormApp.openById('YOUR_FORM_ID'); // Base form
// need to read what dates are available and which are taken
var doc = SpreadsheetApp.getActiveSpreadsheet();
var dates = doc.getRange("dates!A1:A10").getValues(); //available options
var taken_dates = doc.getRange("responses!F2:F51").getValues(); //just getting first 50 responses
// joining the taken dates into one string instead of an array to compare easier
var taken_dates_string = taken_dates.join("|");
var choice = [];
// loop through our available dates
for (d in dates){
// test if date still available
if (dates[d][0] != "" && taken_dates_string.indexOf(dates[d][0]) === -1){
choice.push(dates[d][0]); // if so we add to temp array
}
}
var formItems = form.getItems(FormApp.ItemType.LIST); // our form list items
// assumption that first select list is the one you want to change
// and we just rewrite all the options to ones that are free
formItems[0].asListItem().setChoiceValues(choice);
}