I've got a code in Google Apps Script to automatically populate a Google Form. It works perfectly, but there's one big problem. When I run the script multiple times, the Forms copies itself. So, instead of overwriting the previous run, it adds a whole new (identical) Form. For example: after running the code one time, it shows 4 sections. But when I run it two times, it shows 8 sections, and so on.
Does anybody know how to solve this?
Here's the used code:
function rosterMaker() {
//spreadsheet id of the rosters
var SHEET_ID = FormApp.getActiveForm().getDestinationId();
var ss = SpreadsheetApp.openById(SHEET_ID)
var form = FormApp.getActiveForm();
var sheets = ss.getSheets().filter(function(sheet) {return sheet.getName().match(/Roster/gi);});
//add multiple choice item
var classSelect = form.addMultipleChoiceItem();
classSelect.setTitle('Choose a class');
var classChoices = [];
for(var i = 0; i < sheets.length; i++) {
var className = sheets[i].getName();
var classSection = form.addPageBreakItem()
.setTitle(className)
.setGoToPage(FormApp.PageNavigationType.SUBMIT);
var students = getStudents(sheets[i]);
var studentSelect = form.addCheckboxItem()
.setTitle(className + 'absent')
.setHelpText('Select the students who are absent from this class');
var studentChoices = [];
for(var j = 0; j < students.length; j++) {
studentChoices.push(studentSelect.createChoice(students[j]));
}
studentSelect.setChoices(studentChoices);
classChoices.push(classSelect.createChoice(className, classSection));
}
classSelect.setChoices(classChoices);
}
function getStudents(sheet) {
var studentValues = sheet.getDataRange().getValues();
var students = [];
for(var i = 1; i < studentValues.length; i++) {
students.push(studentValues[i].join(' '));
}
return students;
}
What you need to do is remove all the items you populated in your form before populating it again. Best place to add it on should be before adding anything to the form.
Based on your script, you can add it after var form = FormApp.getActiveForm();
See my sample code below on how it works.
var formID = <FORM_ID>;
var fData = FormApp.openById(formID);
function clearForm(){
// function that clears all items in your form
var items = fData.getItems();
// always delete while there is remaining item
while(items.length > 0){
fData.deleteItem(items.pop());
}
}
function populateForm() {
// call clearForm to prevent duplicating populated questions
clearForm();
// section to populate your form
// ....
}
If you have any issues/problems on making the code work, feel free to comment below.
Related
The following script will create a form with 4 sections. The first section of the form lists three options (dropdown) and depending on which one you choose, it will jump to the relevant section to request more information about that particular option (more dropdowns).
I am populating the first section with the names of three sheets (sql_db_info, oracle_db_info, imanis_db_info)
I am populating the three different dependant sub sections using the rows in the first two columns of each sheet.
All of this works.
The problem I have is what I need to do to update the different sections if I change any values in the columns of the existing sheets or if I add new sheets.
If I add more sheets (mongo_db_info, casandra_db_info) and their associated dependent sub sections (using the values in the new sheets), it appends to the existing form (adding more sections).
I tried to delete and recreate the sections each time but that screws up the collection of the responses in the relevant sheet. New columns are created and it spreads out of control.
So....I need to be able to just update the relevant sections by adding new entries and removing deleted entries or by overwriting the dropdown entries without recreating the sections.
So, the looping has to work in case I add new sheets (new database type with new dependent sections) but recreating the Form each time wont cut it.
So. Additional sheets, will add to the additional first section (sql, oracle, imanis, mongo, cassandra etc).
And the cell values in the first two columns of those sheets will provide the dropdowns for the subsequent two sections for each option chosen in the first section (sql,oracl,imanis etc).
How can I update the relevant dropdowns when I add new sheets or add/delete values from the columns in the sheets?
In the below code, the dbMaker function works on the first run.
What do I need to do to get it to just update dropdowns on subsequent runs?
var ssID = "url id is put here";
var formID = "url id is put here"
var ss = SpreadsheetApp.openById(ssID);
var form = FormApp.openById((formID));
//function clearAll(form){
// var form = FormApp.openById((formID));
// var items=form.getItems();
// Logger.log(items);
// //if(items < 1 ) {
// var d=0;//deleted items counter
// for (var i=0; i<items.length; i++) {
// form.deleteItem(i-d++);
//
// }
//}
// dbMaker();
//}
function dbMaker() {
//var message = 'The current time is ' + new Date().toString();
//Logger.log(message);
var sheets = ss.getSheets().filter(function(sheet) {return sheet.getName().match(/db_info/gi);});
var dbSelect = form.addListItem().setTitle('DB Type').setRequired(true);
var dbChoices = [];
for(var i = 0; i < sheets.length; i++) {
var dbName = sheets[i].getName();
var dbSection = form.addPageBreakItem().setTitle(dbName).setGoToPage(FormApp.PageNavigationType.SUBMIT);
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
var nativetech = getnativetech(sheets[i]);
var nativetech_Select = form.addListItem().setTitle('Native Technology' + ' Choice').setHelpText('Select the correct DB type').setRequired(true);
var nativetech_Choices = [];
for(var j = 0; j < nativetech.length; j++) {
nativetech_Choices.push(nativetech_Select.createChoice(nativetech[j]));
Logger.log('nativetech choices', nativetech[j]);
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
var integratedtech = getintegratedtech(sheets[i]);
var integratedtech_Select = form.addListItem().setTitle('Intregrated with company' + ' Choice').setHelpText('Select the correct option').setRequired(true);
var integratedtech_Choices = [];
for(var k = 0; k < integratedtech.length; k++) {
integratedtech_Choices.push(integratedtech_Select.createChoice(integratedtech[k]));
Logger.log('integratedtech choices', integratedtech[k]);
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
nativetech_Select.setChoices(nativetech_Choices).setRequired(true);
integratedtech_Select.setChoices(integratedtech_Choices).setRequired(true)
dbChoices.push(dbSelect.createChoice(dbName, dbSection));
}
dbSelect.setChoices(dbChoices).setRequired(true);
}
function getnativetech(sheet) {
var result1 = ""
var nativetech_Values = sheet.getDataRange().getValues();
var result1 = nativetech_Values.reduce(function(ar, e) {
if (e[0]) ar.push(e[0])
return ar;
}, []);
var nativetech = [];
for(var i = 1; i < result1.length; i++) {
Logger.log('result1',result1[i]);
nativetech.push(nativetech_Values[i][0]);
}
return nativetech;
}
function getintegratedtech(sheet) {
var result2 = ""
var integratedtech_Values = sheet.getDataRange().getValues();
var result2 = integratedtech_Values.reduce(function(ar, e) {
if (e[1]) ar.push(e[1])
return ar;
}, []);
var integratedtech = [];
for(var i = 1; i < result2.length; i++) {
Logger.log('result2',result2[i]);
integratedtech.push(integratedtech_Values[i][1]);
}
return integratedtech;
}
I'm relatively new to Google Scripts for Sheets/Forms, and have edited this answer to create a script that updates the options on a dropdown question whenever it is triggered. It works wonderfully, but with one small glitch. My code as below:
var idColumn = 1;
function getSheet() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ProjectList");
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var lr = rows.getLastRow();
var docId = sheet.getRange(2,idColumn,lr,1).getValues(); //gets the data from the last row in selected column
fillFormData(docId);
}
//fills form with document ID
function fillFormData(docId) {
var formId = 'form ID goes here'
var form = FormApp.openById(formId);
var items = form.getItems();
var item = items[1];
//loop variables
var thisValue = "";
var arrayOfItems = [];
var newItem = "";
for (var i=0;i<docId.length;i++) {
thisValue =docId[i][0];
Logger.log('thisValue: ' + thisValue);
newItem = item.createChoice(thisValue);
arrayOfItems.push(newItem);
};
item.setChoices(arrayOfItems)
}
My hope was that, by updating the question using setChoices(), responses would be placed in the same column in the response sheet. Instead, each trigger of this script creates another column in the response sheet named "Project Number", overwriting one of the columns that were there before. Is this an expected behaviour, or does anyone know of a way to ensure that my responses go into the same column? Is there a way to direct responses to this question to a specific column in the response sheet?
Edit: As per the suggestions below, I've tried adjusting the macro to use .asListItems() with no success. The interesting behaviour here is that the new, duplicate column seems to overwrite one of my other columns rather than create a new one.
Here's my ProjectList, just a single column:
Project Number
Project A
Project B
Project C
Project D
Project E
The form consists of 35 questions, of a variety of types, split among 3 sections.
I think you need to explicitly set the question you're trying to add the choices to, please see below example of working script to add new items to form.
function getSheet() {
var sheet = SpreadsheetApp.openById('sheet id here');
}
var formId = "form id here";
var question = [{formFieldTitle:"question title here", worksheetName:"linked form sheet name here"}]
function newChoices(item, sheetName) {
var data = (SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName(sheetName)
.getDataRange()
.getValues());
var choices = [];
for (var i = 1; i < data.length; i += 1){
choices.push(item.createChoice(data[i][0]));
}
item.setChoices(choices);
}
function addNewChoices() {
var form = FormApp.openById(formId);
var items = form.getItems();
for (var i = 0; i < items.length; i += 1) {
for (var j = 0; j < question.length; j += 1) {
var item = items[i];
if (item.getTitle() === question[j].formFieldTitle) {
addNewChoices(item.asCheckboxItem(), question[j].worksheetName);
break;
}
}
}
}
Variable "question" should have the question header and the sheet name from which your form items are being added.
var question = [{formFieldTitle:"question title here", worksheetName:"sheet name here"}]
Also, make sure to change "asCheckboxItem" if your question is not a checkbox in the line below, see Interface Item documentation to determine what to use.
addNewChoices(item.asCheckboxItem(), question[j].worksheetName);
Instead of using method createChoice() and setChoices(), you should use setChoiceValues() so it doesn't create a new question. By using setChoiceValues(), you are effectively updating the choices.
See the code below:
function optWriteItem_CHECKBOX_(paramItem, paramItemData) {
try {
var thisItem = paramItem.asCheckboxItem();
var listChoices;
var n, i;
n = paramItemData.length;
if(paramItemData.indexOf('__OTHER__') > 5) {
listChoices = paramItemData.slice(5, n-1);
thisItem.showOtherOption(true);
} else {
listChoices = paramItemData.slice(5, n);
thisItem.showOtherOption(false);
}
thisItem.setTitle(paramItemData[0]);
if(paramItemData[3].toLowerCase() == 'yes') thisItem.setRequired(true);
else thisItem.setRequired(false);
thisItem.setHelpText(paramItemData[4]);
if(listChoices.length == 0) listChoices = [ 'Option 1' ];
thisItem.setChoiceValues(listChoices);
} catch(err) {
Logger.log('optWriteAsType/opt=CHECKBOX : ' + err.message);
console.error("optWriteItem_CHECKBOX_()", err);
return {s:false, m:err.message};
}
return {s:true};
}
Source-code
I am trying to create a Google Apps Script that will list all the Google Classroom courses that are active and archived, along with the ID, NAME, SECTION, and COURSESTATE. Everything works except that I have no idea how to fix the .getRange so that it will put all the information in the Google Spreadsheet. The error I get is "Incorrect range height, was 4 but should be 10". If I put the .getRange as simply "A1:D1", it will overwrite what is there for each Class until the script finishes, so I know the rest of the script works, but I can't figure out how to put the range so that all the Classes can be listed. What I have up to now is this:
function listCourses() {
var response = Classroom.Courses.list();
var courses = response.courses;
if (courses && courses.length > 0) {
for (i = 0; i < courses.length; i++) {
var course = courses[i];
var ids = course.id;
var title = course.name;
var sec = course.section;
var state = course.courseState;
var arr1 = [];
arr1.push(ids,title,sec,state);
var arr2 = [];
while(arr1.length) arr2.push(arr1.splice(0,1));
var s = SpreadsheetApp.getActiveSpreadsheet();
var sh = s.getSheetByName('LISTS');
for (var x=0; x<arr2.length; x++){
var newRow = sh.getLastRow() + 1;
// Here in the .getRange is where it gives me the error. A new row should be added for each Class until all the Classes (with the information mentioned above) are listed.
sh.getRange(1, 1, newRow, arr2[0].length).setValues(arr2);
}
}}}
Try something like this:
I don't use Classroom API much and I only have one class in it but this works for it.
function listCourses() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('LISTS');
var response = Classroom.Courses.list();
var courses = response.courses;
var arr=[];//You could put column headers in here
for (i = 0; i < courses.length; i++) {
var course = courses[i];
var ids = course.id;
var title = course.name;
var sec = course.section;
var state = course.courseState;
arr.push([title,sec,state]); //you could also go sh.appendRow([title,sec,state]); here if you wish and avoid the use of the two dimensional array all together as suggested in the other answer.
}
sh.getRange(1, 1, arr.length, arr[0].length).setValues(arr);
}
Jason. You could bypass the range issue by using append row. This will however mean manipulating the data so that each class's details is on one row. I prefer this approach because it is let prone to errors. Also it automatically finds the next empty row in the sheet, so you don't need to get the last row.
function appendRow() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
// Try is added to catch failures on automated scripts
try {
sheet.appendRow(['cell1','cell2','cell3'])
} catch (e){
Logger.log(e)
// Could send an email here to alert script failure
}
// In a loop
var data = [['row 1 cell 1','row 1 cell 2'],['row 2 cell 1','row 2 cell 2 ']]
data.forEach(function(item){
sheet.appendRow(item)
})
}
This question is similar to the last one I presented, but this time, I need to change the options in MULTIPLE google forms with the same title: WHAT IS YOUR NAME (in every single form, the question title is the same, but I have to change the options to hundreds of forms under this question). All the forms must change based on one column in a Google Spreadsheet. The Code below almost works, and I get no errors, but for some reason, it will only change one form instead of changing all of them in the folder. Any help would be appreciated.
function updateLists() {
var files = DriveApp.getFolderById("ID HERE").getFiles()
while (files.hasNext()) {
var file = files.next();
var form = FormApp.openById(file.getId());
continue;
}
var items = form.getItems();
for (var i = 0; i < items.length; i += 1){
var item = items[i]
if (item.getTitle() === "WHAT IS YOUR NAME"){
var agentList = item.asListItem()
}
}
var ss = SpreadsheetApp.getActive().getSheetByName("Sheet1");
var agentValues = ss.getRange(2, 1, ss.getMaxRows() - 1).getValues();
var agentNames = [];
for(var i = 0; i < agentValues.length; i++)
if(agentValues[i][0] != "")
agentNames[i] = agentValues[i][0];
agentList.setChoiceValues(agentNames);
}
Consider logging custom messages to find out why it's not looking at multiple forms. Likely because .setChoiceValues() and some other lines needed aren't inside the while loop. agentNames could get created once before the while too.
That continue statement isn't really doing anything either. The form var will only contain the last one looked at. As is, all the form files before the last one are each being placed in the var form then replaced with the next one found.
In accordance with what Bryan P mentioned above, I changed the script slightly and it finally worked. The modified and working script is below for those who want to copy and paste. Just a quick note: make sure you have nothing else but forms in the folder or an error will be produced.
function updateLists() {
var files = DriveApp.getFolderById("FOLDER ID HERE").getFiles()
while (files.hasNext()) {
var file = files.next();
var form = FormApp.openById(file.getId())
var items = form.getItems();
for (var i = 0; i < items.length; i += 1){
var item = items[i]
if (item.getTitle() === "QUESTION TITLE HERE"){
var agentList = item.asListItem()
}
}
var ss = SpreadsheetApp.getActive().getSheetByName("SHEET NAME HERE");
var agentValues = ss.getRange(2, 1, ss.getMaxRows() - 1).getValues();
var agentNames = [];
for(var i = 0; i < agentValues.length; i++)
if(agentValues[i][0] != "")
agentNames[i] = agentValues[i][0];
agentList.setChoiceValues(agentNames);
}
}
I have a FlexTable that contains checkBoxes in the all cells of first column and other data in the other cells. I need to store FlexTable's row when checkBox is true to subsequently put it in document with DocumentApp.create('Doc').getBody().appendTable(storedRows), and I have no idea how to realise this function.
Maybe it impossible when using FlexTable?
Anyway thankyou in advance.
if you need to read the value of a checkBox this checkBox has to have a unique name so you can get it using e.parameter.widgetName. What I usueally do when building in a loop is to generate a name in which I include the row number so I have a direct relation with data.
I rewrote your code with this modification and a few other ones.... please have a look and tell us if you need more details.
// Global variables
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getActiveSheet();
var data = sh.getDataRange().getValues();
var app = UiApp.getActiveApplication();
// Menu additions
function onOpen() {
var menuEntries = [{name: 'Поиск вакансий', functionName: 'ui'}];
ss.addMenu('Отчеты',menuEntries);
}
// UI
function ui() {
var app = UiApp.createApplication().setHeight(400).setWidth(800).setTitle('Поиск вакансий');
var panel = app.add(app.createHorizontalPanel());
var input = panel.createTextBox().setName('input').setId('input').setFocus(true);
var button = panel.createButton('Сформировать документ');
panel.add(input).add(button);
var handler = app.createServerHandler('search');
input.addChangeHandler(handler);
handler.addCallbackElement(input);
var click = app.createServerHandler('click');
button.addClickHandler(click);
click.addCallbackElement(button);// this is useless, the button has no data to transmit ?
var table = app.createFlexTable().setId('table').setWidth(785).setBorderWidth(1).setCellPadding(1)
var tpanel = app.createScrollPanel(table).setPixelSize(800, 383).setAlwaysShowScrollBars(true);
table
.setWidget(0,0,app.createCheckBox().setValue(true).setEnabled(false))
.setWidget(0,1,app.createLabel('Вакансия'))
.setWidget(0,2,app.createLabel('График'))
.setWidget(0,3,app.createLabel('Время'))
.setWidget(0,4,app.createLabel('Условия'))
.setWidget(0,5,app.createLabel('Зарплата'))
.setWidget(0,6,app.createLabel('Оплата'))
.setWidget(0,7,app.createLabel('Организация'))
.setWidget(0,8,app.createLabel('Телефон'));
app.add(tpanel);
ss.show(app);
}
// Search
function search(e) {
var table = app.getElementById('table');
var query = e.parameter.input.toLowerCase();
var hidden = app.createHidden().setId('hidden').setName('hidden');
app.add(hidden);// set a name too but I don't use it right now
var check = app.createServerHandler('check');
check.addCallbackElement(table);
var r = 1;
for (var row = 0; row < data.length; row++) {
if(data[row].toString().toLowerCase().match(query) == query && query!= ''){ // you don't need to check each cell, you can use match on the stringified row instead.
table.setWidget(r,0,app.createCheckBox().addValueChangeHandler(check).setName('check'+row));// create the checkBow once every row if condition is true and give it a name
for (var c = 1; c < data[row].length; ++c) {
table.setText(r,c,data[row][c].toString());
}
++r
continue;
}
}
return app;
}
// Storing checked rows
function check(e) {
var checkedArray = [];
for(var n=0; n < data.length;++n){
Logger.log('check'+n+' = '+e.parameter['check'+n]);// shows also 'undefined' for items not found by input query, that's normal
if(e.parameter['check'+n]=='true'){
checkedArray.push(data[n]);
}
}
Logger.log('checkedArray = '+checkedArray);// see results
}
function click(e) {
// no action right now
}