Create Multiple Choice Question with data from Google Sheet - google-apps-script

I am creating a database for my study program's inventory which can be accessed for anyone who want to request using the item.
For weeks, I am stuck with making the multiple choice question from the data in my Google Sheet Database.
These codes are the closest that I can do...Instead of creating all the choices, the program only puts different statements inside 1 choice... Could anyone help me making the proper multiple choice please?
function myFunction() {
var ss = SpreadsheetApp.openById('1lSB55vGZeuaJC0OS3zjpuRHEDCXpUTDYC7MPUVr0tMU');
var form = FormApp.openById('1sH-qxqiiyh3Qqq28KuYoaNftnY3RUe4FhGq6qck2eMg');
var ss_mediaandchemical = ss.getSheetByName('Stock - Media & Chemical').getRange('K2:K').getValues();
var form_mediaandchemical_question = form.getItemById('447046025').asMultipleChoiceItem();
for (var i in ss_mediaandchemical){
form_mediaandchemical_question.createChoice(ss_mediaandchemical[i]).isCorrectAnswer;
form_mediaandchemical_question.setChoiceValues(ss_mediaandchemical[i]);
}
}

Issues:
setChoiceValues(values) sets all the provided values in the array as choices of the item, removing the choices that were previously set.
Since you are iterating through the different choices, you are replacing each choice you set with the next value in the source array.
Also, getValues() returns a 2D array, and you want to provide a simple array. You can use flat() to fix that. Also, the source data contains several empty cells, I'd suggest removing them from the array (for example, using .filter(String).
Finally, createChoice doesn't add the choice to the item, and it should be used in combination with setChoices. It doesn't make sense to use it if you're then using setChoiceValues.
Solution:
Retrieve a simple array with values, removing empty values, and set the choices directly using setChoiceValues, without iterating through the array:
var ss_mediaandchemical = ss.getSheetByName('Stock - Media & Chemical').getRange('K2:K').getValues().flat().filter(String);
var form_mediaandchemical_question = form.getItemById('447046025').asMultipleChoiceItem();
form_mediaandchemical_question.setChoiceValues(ss_mediaandchemical);
If you want to create the choices via createChoice (it could allow more customization, like setting a choice as correct or adding a navigation item), you can do this instead:
var ss_mediaandchemical = ss.getSheetByName('Stock - Media & Chemical').getRange('K2:K').getValues().flat().filter(String);
var form_mediaandchemical_question = form.getItemById('447046025').asMultipleChoiceItem();
var choices = ss_mediaandchemical.map(value => form_mediaandchemical_question.createChoice(value));
form_mediaandchemical_question.setChoices(choices);

Related

Range.SetValues() does not insert data on one sheet, on the other works. What is the reason?

I have a GoogleSheet with basically two sheets, which are very similar in terms of data collected.
I need to calculate same values for both sheets, but source data is in different columns.
Therefore I created three files in AppsScript:
Common.gs - with common function definitions
sheet1.gs
sheet2.gs - both sheet1 and sheet2 have only definitions of proper ranges in particular columns and one function to run script, which essentially calls functions defined in Common.gs, like so in sheet1.gs:
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("sheet1")
var createdColumn = sheet.getRange("E2:E").getValues()
var ackColumn = sheet.getRange("G2:G").getValues()
var resColumn = sheet.getRange("I2:I").getValues()
var timeToAckColumn = sheet.getRange(2,14,ackColumn.length,1)
var timeToResColumn = sheet.getRange(2,15,resColumn.length,1)
var yearAndWeekRange = sheet.getRange(2,16,createdColumn.length,2)
function calculateMetricsSheet1() {
calculateTimeDiff(createdColumn, ackColumn, timeToAckColumn)
calculateTimeDiff(ackColumn, resColumn, timeToResColumn)
calculateWeek(createdColumn, yearAndWeekRange)
}
example function implementation (they are basically very similar with minor differences):
function calculateWeek(createdColumn, yearAndWeekRange) {
var arrData = []
for(var i=0;i<createdColumn.length;i++) {
if(createdColumn[i][0].toString()=="") {
arrData.push(["",""])
continue
}
var createdDate = new Date(createdColumn[i][0])
var year = createdDate.getFullYear()
var week = Utilities.formatDate(createdDate, "GMT+1", "w")
arrData.push([year, week])
}
yearAndWeekRange.setValues(arrData)
}
the sheet2.gs is basically different column definitions, the functions called within calculateMetricsSheet2() are the same.
So what is the problem?
The script works perfectly fine for sheet2.gs, but for sheet1.gs it does collect proper data, calculates proper data, but the data does not appear in proper columns after Range.setValues() call.
No exceptions or errors appear in the console.
Documentation does not provide any kind of information what could be the problem.
I have really ran out of ideas what could be the cause of the issue.
Does anyone have any idea what is going on?
edit: It may be useful to put emphasis on the fact that each script runs function calling 3 other functions -> all of them end with Range.setValues({values}). And for one sheet all of them work, and for the other - none.
That's the reason I assume there is something wrong with the sheet itself, maybe some permissions/protection? But I couldn't find anything :(
edit2: I modified my code to iterate through the sheet 10 rows at a time, because I thought maybe when I get a whole column, something bad happens with data and breaks setValues() function.
Unfortunately - even if my code iterated 1 row at a time, it still did not work on sheet1, but worked on sheet2. So not a data problem.
The code you show always puts values in yearAndWeekRange which is always in the 'sheet1' sheet. To put the data into another sheet, you need to change the target range appropriately.
The dimensions of the range must match the dimensions of the array you put there. Use this pattern:
yearAndWeekRange.offset(0, 0, arrData.length, arrData[0].length).setValues(arrData);
I found out what is the problem.
Two scripts were pretty identical, even with naming of variables - ie ackColumn, resColumn etc.
Those were stored as a global variables, so even if I was running script1.gs, it used global variables from script2.gs, effectively writing proper data to wrong sheet.
separating global variables names fixed the issue.
Perhaps a rookie mistake, but I missed the fact, that if I have a variable defined outside any function, it becomes global and could be overwritten from other file

How to use Google Apps Script to bring in Spreadsheet values efficiently

I currently use the following code to bring in many values from a spreadsheet
myValues["TitleText"] = ss.getRange('B9').getValue();
I then display the values in a index.html form using the same Google Apps Script like
<div id="title"><?= data.TitleText ?></div>
I was wondering if I should use something else. Maybe like an ArrayFormula?
I guess it looks to me like I am taking many trips back to the sheet to get all of the values that I need. It would be nice to get all values in one visit thus speeding up the loading and processing of the form.
If a one trip approach is possible; what would it look like?
If the data is in an array, there is no "built-in" way to associate one value with another value. With an object, you can associate the "key" with the "value". There are ways to do the same thing with arrays, but it's a lot "trickier". Whether you should use an array or an object, depends on the "bigger picture". If you want to associate a title with a value, and the position of the title and value could be changing in the spreadsheet, then it MIGHT make sense to compile the data in an object before sending it to your HTML. But then there is the issue of performance. It can be better to construct the HTML in the server code, and then send the HTML string back to the client, and not just the data.
If you have lots of scriptlets, you might try having just one printing scriptlet, create the HTML string in the ".gs" server code, and then send the HTML back instead of sending just the data back.
If you compile the data in some special format, send the data, then you need to unscramble the data and construct the HTML, that might be more processing than just building the HTML from the start in the server code.
The getRange() method has four different parameter configurations. You are using the a1Notation variation. I would use this variation:
getRange(starting row, starting column, number of Rows, number of Columns)
Then you can use code like this:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('the sheet name');
var allData = sheet
.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn())
.getValues();
If you only want to get the values in column B, starting in row 2, you can do this:
var colB_Data = sheet
.getRange(2, 2, sheet.getLastRow())
.getValues();
The getValues() method returns a two dimensional array. But if you are only getting one column of data, all the inner arrays only have one element. If you need a one dimensional array, you can (in this case) convert the two dimensional array to a one dimensional array with:
colB_Data = colB_Data.toString().split(",");

Changing data in a Google spreadsheet

I am changing a few cells of data in a Google spreadsheet. I would like to write back the data into the spreadsheet. The problem i have is that the sheet has say 5 columns, when i add new data into one of the cells i would like to backup the old data in the next free cell in that row. When i do this i am not able to write back the data. This is my code.
var sheet = SpreadsheetApp.openByUrl("URL");
var d = sheet.getSheetByName('Form Responses').getDataRange().getValues();
var head = sheet.getSheetByName('Form Responses').getRange(1,1,1,sheet.getLastColumn()).getValues()[0];
d[29][2] = "A";
d[29][3] = "B";
d[29][4] = "C";
d[29][5] = "D";
sheet.getSheetByName('Form Responses').getRange(1,1,d.length,d[0].length).setValues(d);
This is the error i get. Incorrect range width, was 6 but should be 5
I have also tried modifying the the write back statement as
sheet.getSheetByName('Form Responses').getRange(29,1,1,d[29].length).setValues(d[29]);
In this case i get Cannot convert Array to Object[][].
What is the correct way to write back the data. Preferably i would like to write back data of that particular row only, instead of the whole sheet data.
The issue here is that you modify only one "row" in the array so that in the end it is not "symetric" anymore (ie every rows don't have the same length) and that causes the error you get.
possible solutions :
There are probably more than one good way to avoid that error, you could create a second array with new data (same height) and join the arrays before writing back to the sheet or, as in the example below, add a cell (or many cells) to every row in the array in a loop before assigning them a new value. Depending on the size of the array one could be better and/or easier than the other to implement... here is a simplified example that should work :
for(var n=0;n<d.length;n++){
d[n].push('');
Logger.log('row '+n+' = '+d[n]+'\n');// see the result
}
EDIT following comment :
OR you can write a single row if you change only one, just change the code to
sheet.getSheetByName('Form Responses').getRange(29,1,1,d[29].length).setValues([d[29]])
which is not far from what you tried but you were simply missing the pair of brackets to get a 2D array as required by setValues()

Switch rows and columns in EmbeddedChart

I am using Google Docs and Google Apps Script to make some auto generated report for our sprint.
I would like to add a Chart to my Sheet, and everything works fine with the following code:
var lChartBuilder = SpreadsheetApp.getActiveSheet().newChart();
lChartBuilder.addRange(SpreadsheetApp.getActive().getRange("Tasks!C39:S40"));
lChartBuilder.setOption("title", "Task Burndown");
var lChart = lChartBuilder.asLineChart().build();
SpreadsheetApp.getActiveSheet().insertChart(lChart);
But my series are organized horizontally, not vertically. In the editor I have seen the option "Switch rows / columns" and the other option "Use column C for labels"
I have tried many options (like lChartBuilder.setOption("reverseCategories", true); or lChartBuilder.setOption("isStacked", true);), but they seem all related to the last tab, I fear, not the Start tab.
So, is there a way (other than transposing my data) to do that or must I fire the chart editor manually to fix this each time I generate it?
Bonus question: how do I then set (through Google Apps Script as well) that the first row/column is a header and serves as legend?
For the bonus question, I have your bonus answer.
Use this option when creating/modifying your graph:
setOption('useFirstColumnAsDomain','true')
For an embedded chart, you will need to transpose your data, because the embedded chart uses spreadsheet data, and does not support transposition itself. However, you can handle this programmatically, and the transpose operation itself is simple.
In the code below, I'm assuming existence of a sheet named "Scratch" that will be used to store the working copy of the transposed data. You can create and then hide this sheet, so that it's not in the way.
// Uses 2D Arrays Library, see https://sites.google.com/site/scriptsexamples/custom-methods/2d-arrays-library
function buildChart() {
var taskSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Tasks");
var lChartBuilder = taskSheet.newChart(); taskSheet.removeChart(chart)
var srcData = taskSheet.getRange("Tasks!C39:S40").getValues();
// Transpose the table (using 2D Array Library)
var scratchData = ArrayLib.transpose(srcData);
var numRows = scratchData.length;
var numCols = scratchData[0].length; // assume all rows are same width
// Write scratch values to scratch sheet.
var scratchSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Scratch");
scratchSheet.getRange(1, 1, numRows, numCols).setValues( scratchData );
SpreadsheetApp.flush();
lChartBuilder.addRange(scratchSheet.getDataRange());
lChartBuilder.setOption("title", "Task Burndown");
var lChart = lChartBuilder.asLineChart().setPosition(1, 1, 1, 1).build();
taskSheet.insertChart(lChart);
};
For the bonus... Having the data formatted this way ensures the first row/column is a header and serves as legend.
Now, you've still got some problems to solve.
This code creates a new chart every time it runs, but you probably want to modify it instead. (Instead, you'll want to update the transposed data, and modify the chart to pick up the new range, if it changed.)
The X axis doesn't show all the task names - you may be able to control that.
You say you did not want to transpose your data. Unfortunately, there just isn't any way around that with the current incarnation of Charts - you've got to massage your data yourself. Inserting a widget with a different charting library could work, but widget support for Sheets has been deprecated. Fortunately, I had similar code already, so it was less work than it looks like. You should look at some of the other filtering capabilities in ArrayLib, you can do a lot with it.

How do I create a Range class programatically in my google spreadsheet script?

So I have this google script that I want to use to create charts in my spreadsheet. I'm basically programatically creating content (with the use of spreadsheet data) that I then want to plot. The way I used to do it is by filling one of the sheets with all the data and then using that data to plot, but I was hoping to skip that step and feed the javascript arrays directly into my addRange method.
So I've got a script that creates a new chart:
// insert the scenario chart
var scenarioChartBuilder = sheet.newChart();
scenarioChartBuilder.setPosition(5, 6, 5, 5)
.setChartType(Charts.ChartType.AREA)
.addRange(rangeObject);
sheet.insertChart(scenarioChartBuilder.build());
The problem is; how do I make "rangeObject", given that I only have javascript arrays, and don't want to use actual spreadsheet data? Or is there another way of plotting data that isn't actually in a spreadsheet?
Range data is actually just a multidimensional array.
So a rangeObject could just be defined like;
var rangeobject = [[data, data, data],[data, data, data]];
The first array represents the row and the second array the column data.
programmatically you could get the data like;
var dataFirstRowSecondColumn = rangedata[0][1]; //0 indexed array!
So, to add a range is just to pass a multidimensional array (with content data).
But beware ;-) When adding to a chart i would think that you would have to mind that each column would only contain on kind of data to be valid.
In code you could directly use my first example.