I have created a google spreadsheet to automatically convert into a google form, so i don't have to manually enter all the questions into the google form.
I am writing google app script and managed to get all the questions.I am trying to divide the form in to sections depending on the first column of the sheet. So if the first column is "1" questions corresponding to it should be on the first section and if it is "2" it should create another section.And so on.
How can i do that? what will be the code? I have attached the google sheet as here Google spreadsheet
function myFunction()
{
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
var range = ss.getDataRange();
var data = range.getValues();
var numberRows = range.getNumRows();
var numberColumns = range.getNumColumns();
var firstRow = 1;
var form = FormApp.openById('1hIQCLT_JGLcvjz44vXTvP5ziia6NnwCqWBxYT4h2uCk');
var items = form.getItems();
var ilength = items.length;
for (var i=0; i<items.length; i++)
{
form.deleteItem(0);
}
for(var i=0;i<numberRows;i++)
{
Logger.log(data);
var questionType = data[i][0];
if (questionType=='')
{
continue;
}
//choose the type of question from the first column of the spreadsheet
else if(questionType=='1')
{
var rowLength = data[i].length;
var currentRow = firstRow+i;
var currentRangeValues = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1').getRange(currentRow,1,1,rowLength).getValues();
var getSheetRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1').getDataRange();
var numberOfColumnsSheet = getSheetRange.getNumColumns();
var numberOfOptionsInCurrentRow = numberOfColumnsSheet;
var lastColumnInRange = String.fromCharCode(64 + (numberOfOptionsInCurrentRow));
var range_string = 'C' + currentRow + ":" + lastColumnInRange + currentRow;
var optionsArray = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1').getRange(range_string).getValues();
var choicesForQuestion =[];
for (var j=0;j<optionsArray[0].length;j++)
{
choicesForQuestion.push(optionsArray[0][j]);
}
form.addMultipleChoiceItem().setTitle(data[i][1]).setHelpText("").setChoiceValues(choicesForQuestion).setRequired(true);
}
else
{
continue;
}
}
form.addParagraphTextItem()
.setTitle('Please specify and attach relevant documents'); // add the text question at the last
form.addPageBreakItem().setTitle('Identity - Asset Management').setHelpText("")();
}
googleSheet
If you want to use the same exact format for the next section you can get away with a simple counter. I have written a successful script variant, but it depends on what you really want.
Some of the changes I would do
for (i = 0; i < items.length; i++) {
form.deleteItem(items[i])
}
instead of the current form.deleteItem(0);. Otherwise I see that you grab all the data, however you do not utilize it. Calling the spreadsheet app each time you want the options causes it to run a lot slower. More on that for loop: move the Logger.log(data); outside of the loop. There is no reason for you to keep logging the full data range each time you go to the next row of the data. Or change it to Logger.log(data[i]); which would make more sense.
You already do a
if (questionType=='') {
continue;
}
to skip over the empty lines, so not really sure what that last else is meant for. The loop will fall through to the next option on its own anyway.
Now the way your set up would work is that your questions in the spreadsheet must be in order. That is you cannot have
Section 1
Section 2
Section 1
as that will create 3 sections instead of 2. However let's move along with the assumption that the spreadsheet would only be set up in a way where you will only have a sequence like
Section 1
Section 1
Section 2
In that case you should utilize your data and questionType by adding a counter var sectionCount = 0 somewhere before the loop. Then inside of your for loop you do a simple
else if (questionType != sectionCount) {
form.addSectionHeaderItem().setTitle('Section ' + questionType)
sectionCount++
}
this will create the section (provided that the numbers are always increasing by 1 in Column A). Then in the same for loop you do not need any more if statements and can just use
items = data[i].slice(2, data[i].length + 1)
items = items.filter(chkEmpty)
form.addMultipleChoiceItem().setTitle(data[i][1]).setChoiceValues(items)
where
function chkEmpty(val){
return val != ''
}
function myFunction()
{
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
var ss2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet2');
var range = ss.getDataRange();
var data = range.getValues();
var numberRows = range.getNumRows();
var numberColumns = range.getNumColumns();
var firstRow = 1;
var form = FormApp.openById('1xlXDZB5jhbUWpWHxxJwY-ut5oYkh4OfIQSTGsnwGTW4');
var sectionCount = 0
// deletes the previous changes
var items = form.getItems();
var ilength = items.length;
for (i = 0; i < items.length; i++)
{
form.deleteItem(items[i])
}
for(var i=0;i<numberRows;i++)
{
var questionType = data[i][0];
if (questionType=='')
{
continue;
}
else if (questionType != sectionCount )
{
if (sectionCount != 0 )
{
// form.addParagraphTextItem()
// .setTitle('Please specify and attach relevant documents'); // add the text question at the last
// write the description here using SectionCount
}
sectionCount++ // add new section to the form
form.addSectionHeaderItem().setTitle('Section ' + questionType).setHelpText(""); // add section header and title
}
items = data[i].slice(2, data[i].length + 1)
items = items.filter(chkEmpty)
form.addMultipleChoiceItem().setTitle(data[i]
[1]).setChoiceValues(items).setRequired(true);
if ( i == (numberRows-1)){
// form.addParagraphTextItem()
// .setTitle('Please specify and attach relevant documents');
}
}
function chkEmpty(val)
{
return val != ''
}
Logger.log(data);
}
Related
I'm trying to use a for loop to change values and to add notes to the ones that I change. since I'm doing it through arrays and not each row individually, my question is, is it possible to get where there isnt a note on the cell to be nothing in the array?
It's through the google scripts system, I've tried looking everywhere but can't find anything on the subject, hoping there's a wizard here.
edit: snippet of script
function CheckHours() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Copy of PNC Roster');
var lastrow = sheet.getLastRow();
var response;
var hoursList = [];
var notesList = [];
var bmidRange = sheet.getRange("L:L").getValues();
var notesRange = sheet.getRange("J:J").getNotes();
for (var i = 0; i < lastrow; i++) {
if (bmidRange[i] > 1) {
//response = UrlFetchApp.fetch('InsertWebsite/?bm=' + bmidRange[i] + '&fr=7');
var seconds = 59378;
var hours = seconds/60/60;
hoursList.push([hours]);
var note = "Activity Check performed: "+ new Date() + "\n\n" + notesRange[i];
notesList.push([note]);
Logger.log(hours);
} else if(bmidRange[i] == "") {
notesList.push('INSERT BATTLEMETRICS ID');
} else {
hoursList.push(bmidRange[i]);
notesList.push(notesRange[i]);
}
}
sheet.getRange("J:J").offset(0, 0, hoursList.length).setNotes(notesList);
sheet.getRange("J:J").offset(0, 0, hoursList.length).setValues(hoursList);
}
What I'm trying to do when I'm pushing the cell values is that they get an appropriate note along with them, but because the variable notesRange is not the same amount as getValues gets, then where there is no notes, instead having a null value in the array basically, is this possible??
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 have a spreadsheet displaying information on two monitors. The file has anywhere from 5 to 30 tabs. I have been trying to get the following script with a time-driven trigger to move from one sheet tab to the next every minute, however I have two problems:
1) - I need the loop to skip four tabs (they have fixed names) and I currently can't figure out a workable solution, and
2) - the time driven trigger of 1 minute is not doing anything, not working.
Here are two scripts I've been testing and tweeking to see if I find the one that works:
Script 1:
function MoveNext() {
var spreadsheet = SpreadsheetApp.getActive();
var nextSheetIndex = spreadsheet.getActiveSheet().getIndex() + 1;
if (nextSheetIndex > spreadsheet.getSheets().length) { nextSheetIndex = 1; }
spreadsheet.setActiveSheet(spreadsheet.getSheets()[nextSheetIndex - 1],true);
And Script 2: this one aims at skipping the four tabs I don't want to loop:
var ss = SpreadsheetApp.getActive();
var sheets = ss.getSheets();
for (i = 0; i < sheets.length; i++) {
switch (sheets[i].getSheetName()) {
case "T1":
case "T0":
case "Summary Panel":
case "Flight Info":
case "Template":
break;
default:
var nextSheetIndex = ss.getActiveSheet().getIndex() + 1;
if (nextSheetIndex > ss.getSheets().length) {
nextSheetIndex = 1;
}
ss.setActiveSheet(ss.getSheets()[nextSheetIndex - 1], true);
You'll have to add your timer code yourself, but here is an example of how to move to the next "approved" sheet.
function MoveNext() {
var sheetsToSkip = ["Sheet3", "Sheet4", "Sheet7"];
var thisBook = SpreadsheetApp.getActive();
var sheetCount = thisBook.getNumSheets();
var thisSheet = SpreadsheetApp.getActiveSheet();
var thisSheetName = thisSheet.getName();
var thisSheetIndex = thisSheet.getIndex() - 1; //subtract one to get the array index
var allSheets = thisBook.getSheets();
var i = thisSheetIndex;
var notDone = true;
while (notDone) {
if (i == (sheetCount-1)) {
i = 0;
} else {
i++;
}
Logger.log('next sheet index is ' + i);
var nextName = allSheets[i].getName();
if (sheetsToSkip.indexOf(nextName) == -1) {
var nextSheet = allSheets[i];
Logger.log('next active sheet should be ' + nextSheet.getName());
thisBook.setActiveSheet(nextSheet);
notDone = false;
}
}
}
My spreadsheet is composed of a main sheet that is populated using a form plus several other sheets for the people who work with the responses submitted through the form. A script delegates the form responses to these other sheets depending on the type of item described in the response.
The problem is, when Person A deletes an item from their respective sheet, it doesn't delete in the main sheet.
My idea is that when you type a set password into the corresponding cell in row 'Q' in Person A's sheet, it matches the item by timestamp to the original form submission and deletes both the version of the item in Person A's sheet as well as the main sheet. However, I can't figure out what to set the range to to get it to point to the row in the array. Everything I have tried has sent back "undefined" in the debugger and won't delete anything. I think the problem is that I don't know how to get the row from the array that I have made. See my code below:
function onEdit() {//copies edited items from individual selector sheets back onto main spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var actSheet = ss.getActiveSheet();
var responseSheet = ss.getSheetByName("Item Request");
var actCell = actSheet.getActiveCell();
var actRow = actCell.getRow();
var actVal = actCell.getValue();
var actLoc = actCell.getA1Notation();
var last = actSheet.getLastRow();
var respLast = responseSheet.getLastRow();
var dataA = responseSheet.getRange(1, 1, respLast, 1).getValues(); //compiles an array of data found in column A through last row in response sheet
var tstamp1 = actSheet.getRange(actCell.getRow(), 1);
var tsVal1 = tstamp1.getValue();
var colEdit = actCell.getColumn();
//===========THIS IS WHERE I'M STUCK=======================
if ((actVal == "p#ssword") && (colEdit == 17)) {
for (i = 1; i < dataA.length; i++) {
if (dataA[i][0].toString == tsVal1.toString()) {
responseSheet.deleteRow(i + 1);
actSheet.deleteRow(actRow);
break;
}
}
}
else if (colEdit == 15) { //checks the array to see if the edit was made to the "O" column
for (i = 1; i < dataA.length; i++) {//checking for timestamp match and copies entry
if (dataA[i][0].toString() == tsVal1.toString()) {
var toEdit = responseSheet.getRange(i + 1, 16);
toEdit.setValue(actVal);
}
}
}
else if (colEdit == 16) { // checks the array to see if the edit was made in the "P" column
for (i = 1; i < dataA.length; i++) {//checking for timestamp match and copies entry
if (dataA[i][0].toString() == tsVal1.toString()) {
var toEdit = responseSheet.getRange(i + 1, 17);
toEdit.setValue(actVal);
}
}
}
else {return;}
}//end onEdit
I don't believe these are proper commands delRow.deleteRow();actCell.deleteRow(); Take a look at the documentation;
Okay I rewrote that function for you a bit but I'm stilling wondering about a couple of lines.
function onEdit(e)
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var actSheet = ss.getActiveSheet();
var responseSheet = ss.getSheetByName("Item Request");
var actCell = actSheet.getActiveCell();
var actRow = actCell.getRow();
var actVal = actCell.getValue();
var colEdit = actCell.getColumn();
var respLast = responseSheet.getLastRow();
var dataA = responseSheet.getRange(1, 1, respLast, 1).getValues();
var tstamp1 = actSheet.getRange(actRow, 1);
var tsVal1 = tstamp1.getValue();
for(var i=0;i<dataA.length;i++)
{
if(new Date(dataA[i][0]).valueOf()==new Date(tsVal1).valueOf())
{
if (actVal=="p#ssword" && colEdit==17)
{
responseSheet.deleteRow(i + 1);
actSheet.deleteRow(actRow);
}
else if(colEdit==15)
{
var toEdit = responseSheet.getRange(i + 1, 16);//?
toEdit.setValue(actVal);//?
}
else if (colEdit == 16)
{
var toEdit = responseSheet.getRange(i + 1, 17);//?
toEdit.setValue(actVal);//?
}
}
}
}
Can you explain the function of the lines with question marked comments?
Column C has an ID in it that pertains to several rows, but only the first row has an ID in it.
I need to copy that ID value to the blank cells beneath, until I hit a cell that has another value in it.
I have tried adapting this script but it hits a timeout error.
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var last = sheet.getLastRow();//how many times should I do this?
for (i = 5528; i < last; i++) {
var test = sheet.getRange(i, 1,1,1);
//Logger.log(test);
//looks to see if the cell is empty
if (test.isBlank()) {
var rewind = sheet.getRange(i-1, 1, 1, 1).getValues();//gets values from the row above
sheet.getRange(i, 1, 1, 1).setValues(rewind);//sets the current range to the row above
}
}
}
i is set to a big number because every time it times out I have to start over!
I have read that it would be better to bring in the column in an array, work on it, then put it back out to save a lot of time.
I have tried to adapt this but can't get past the variable.
Am I on the right track? I would like to pretty up a solution for the future where I can pass a column or range and do the same thing.
Here is my failing attempt:
function FillDown2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet2");
var tracts = sheet.getRange("C15:C").getValues();
var allTractList = [];
var title;
for (var row = 0, var len = tracts.length; row < len; row++) {
if (tracts[row][0] != '') {
//response = UrlFetchApp.fetch(tracts[row]);
//doc = Xml.parse(response.getContentText(),true);
title = tracts[row][0];
//newValues.push([title]);
allTractList.push([title]);
Logger.log(title);
} else allTractList.push([title]);
}
//Logger.log('newValues ' + newValues);
Logger.log('allTractList ' + allTractList);
// SET NEW COLUMN VALUES ALL AT ONCE!
sheet.getRange("B15").offset(0, 0, allTractList.length).setValues(allTractList);
return allTractList;
}
Holy Smokes! I did it!
Not sure about why error happened but I had made some changes and got it to work!
Hope this is helpful to others:
function FillDown2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet2");
var tracts = sheet.getRange("C15:C").getValues();
var allTractList = [];
var title;
var len = tracts.length;
for (var row = 0; row < len; row++) {
if (tracts[row] != '') {
//response = UrlFetchApp.fetch(tracts[row]);
//doc = Xml.parse(response.getContentText(),true);
title = tracts[row];
//newValues.push([title]);
allTractList.push([title]);
Logger.log(title);
} else allTractList.push([title]);
}
//Logger.log('newValues ' + newValues);
Logger.log('allTractList ' + allTractList);
// SET NEW COLUMN VALUES ALL AT ONCE!
sheet.getRange("B15").offset(0, 0, allTractList.length).setValues(allTractList);
return allTractList;
}
Credit to Bryan here: