Every Friday our students submit an electronic log of what they did in an extra-curricular activity using a Google Form. We want one PDF created each time a script is run. The problem is it creates a new PDF each time there is a new record in a sheet. We started to look at using a pivot table before creating the PDF, can't figure that part out. Any help would be much appreciated.
Kind regards,
var CLASSLIST = [];
var MAINLIST = [];
const MAINTAIN_GS = "1tFXqO0oKjm37P09Mi2jfYT-RML-G1dBr03qHPUzsT7w";
const MAINTAIN_SN = "Class & Teacher Email";
const STUDENT_FORM_SN="Form Responses 1";
const TEMPFOLDER_ID = "12Wzl_v_o-aJ_38P9YSshyQPTtGVXmsyH";
const TEMPLATE_DOC_ID = "1D0PBYPunBa63d0eC9b6IrzjAHmWzZVVJJM1zhhUVY3c";
function onOpen() {
loadMainDetails();
// Utilities.sleep(10000)
/*
var ui = SpreadsheetApp.getUi();
ui.createMenu('Send Email')
.addItem(CLASSLIST[0] ,'mainScript1')
.addItem(CLASSLIST[1] ,'mainScript2')
.addItem(CLASSLIST[2] ,'mainScript3')
.addItem(CLASSLIST[3] ,'mainScript4')
.addItem(CLASSLIST[4] ,'mainScript5')
.addItem(CLASSLIST[5] ,'mainScript6')
.addItem(CLASSLIST[6] ,'mainScript7')
.addItem(CLASSLIST[7] ,'mainScript8')
.addItem(CLASSLIST[8] ,'mainScript9')
.addItem(CLASSLIST[9] ,'mainScript10')
.addItem(CLASSLIST[10] ,'mainScript11')
.addItem(CLASSLIST[11] ,'mainScript12')
.addToUi(); */
var ui = SpreadsheetApp.getUi();
ui.createMenu('Send Email')
.addItem(CLASSLIST[0] ,'mainScript7I')
.addItem(CLASSLIST[1] ,'mainScript7P')
.addItem(CLASSLIST[2] ,'mainScript7S')
.addItem(CLASSLIST[3] ,'mainScript7W')
.addItem(CLASSLIST[4] ,'mainScript8I')
.addItem(CLASSLIST[5] ,'mainScript8P')
.addItem(CLASSLIST[6] ,'mainScript8S')
.addItem(CLASSLIST[7] ,'mainScript8W')
.addItem(CLASSLIST[8] ,'mainScript9I')
.addItem(CLASSLIST[9] ,'mainScript9P')
.addItem(CLASSLIST[10] ,'mainScript9S')
.addItem(CLASSLIST[11] ,'mainScript9W')
.addItem(CLASSLIST[12] ,'mainScript10A')
.addItem(CLASSLIST[13] ,'mainScript10B')
.addItem(CLASSLIST[14] ,'mainScript10C')
.addItem(CLASSLIST[15] ,'mainScript10D')
.addItem(CLASSLIST[16] ,'mainScript10E')
.addItem(CLASSLIST[17] ,'mainScript11A')
.addItem(CLASSLIST[18] ,'mainScript11B')
.addItem(CLASSLIST[19] ,'mainScript11C')
.addItem(CLASSLIST[20] ,'mainScript11D')
.addItem(CLASSLIST[21] ,'mainScript11E')
.addItem(CLASSLIST[22] ,'mainScript12A')
.addItem(CLASSLIST[23] ,'mainScript12B')
.addItem(CLASSLIST[24] ,'mainScript12C')
.addItem(CLASSLIST[25] ,'mainScript12D')
.addItem(CLASSLIST[26] ,'mainScript12E')
.addItem(CLASSLIST[27] ,'mainScript13A')
.addItem(CLASSLIST[28] ,'mainScript13B')
.addItem(CLASSLIST[29] ,'mainScript13C')
.addItem(CLASSLIST[30] ,'mainScript13D')
.addItem(CLASSLIST[31] ,'mainScript13E')
.addItem("All", 'mainScriptAll')
.addToUi();
// menuEntries.push({name: "Generate PDF", functionName: "mainScript0"});
// ss.addMenu("Email Teacher", menuEntries);
/* ss.addMenu("Send Email", menuEntries);
menuEntries.push({name: CLASSLIST[0], functionName: "mainScript7I"});
menuEntries.push({name: CLASSLIST[1], functionName: "mainScript7P"});
menuEntries.push({name: CLASSLIST[2], functionName: "mainScript7S"});
menuEntries.push({name: CLASSLIST[3], functionName: "mainScript7W"});
menuEntries.push({name: CLASSLIST[4], functionName: "mainScript8I"});
menuEntries.push({name: CLASSLIST[5], functionName: "mainScript8P"});
menuEntries.push({name: CLASSLIST[6], functionName: "mainScript8S"});
menuEntries.push({name: CLASSLIST[7], functionName: "mainScript8W"});
menuEntries.push({name: CLASSLIST[8], functionName: "mainScript9I"});
menuEntries.push({name: CLASSLIST[9], functionName: "mainScript9S"});
menuEntries.push({name: CLASSLIST[10], functionName: "mainScript9P"});
menuEntries.push({name: CLASSLIST[11], functionName: "mainScript9W"});
*/
}
function mainScript7I() {
mainScriptByClass("A7", "C7","B7");
}
function mainScript7P() {
mainScriptByClass("A8","C8","B8");
}
function mainScript7S() {
mainScriptByClass("A9","C9","B9");
}
function mainScript7W() {
mainScriptByClass("A10","C10","B10");
}
function mainScript8I() {
mainScriptByClass("A11", "C11","B11");
}
function mainScript8P() {
mainScriptByClass("A12", "C12","B12");
}
function mainScript8S() {
mainScriptByClass("A13", "C13","B13");
}
function mainScript8W() {
mainScriptByClass("A14", "C14","B14");
}
function mainScript9I() {
mainScriptByClass("A15", "C15","B15");
}
function mainScript9S() {
mainScriptByClass("A16", "C16","B16");
}
function mainScript9P() {
mainScriptByClass("A17", "C17","B17");
}
function mainScript9W() {
mainScriptByClass("A18", "C18","B18");
}
function mainScript10A() {
mainScriptByClass("A19", "C19","B19");
}
function mainScript10B() {
mainScriptByClass("A20", "C20","B20");
}
function mainScript10C() {
mainScriptByClass("A21", "C21","B21");
}
function mainScript10D() {
mainScriptByClass("A22", "C22","B22");
}
function mainScript10E() {
mainScriptByClass("A23", "C23","B23");
}
function mainScript11A() {
mainScriptByClass("A24", "C24","B24");
}
function mainScript11B() {
mainScriptByClass("A25", "C25","B25");
}
function mainScript11C() {
mainScriptByClass("A26", "C26","B26");
}
function mainScript11D() {
mainScriptByClass("A27", "C27","B27");
}
function mainScript11E() {
mainScriptByClass("A28", "C28","B28");
}
function mainScript12A() {
mainScriptByClass("A29", "C29","B29");
}
function mainScript12B() {
mainScriptByClass("A30", "C30","B30");
}
function mainScript12C() {
mainScriptByClass("A31", "C31","B31");
}
function mainScript12D() {
mainScriptByClass("A32", "C32","B32");
}
function mainScript12E() {
mainScriptByClass("A33", "C33","B33");
}
function mainScript13A() {
mainScriptByClass("A34", "C34","B34");
}
function mainScript13B() {
mainScriptByClass("A35", "C35","B35");
}
function mainScript13C() {
mainScriptByClass("A36", "C36","B36");
}
function mainScript13D() {
mainScriptByClass("A37", "C37","B37");
}
function mainScript13E() {
mainScriptByClass("A38", "C38","B38");
}
function mainScriptAll() {
mainScript7I();
mainScript7P();
mainScript7S();
mainScript7W();
mainScript8I();
mainScript8P();
mainScript8S();
mainScript8W();
mainScript9I();
mainScript9P();
mainScript9S();
mainScript9W();
mainScript10A();
mainScript10B();
mainScript10C();
mainScript10D();
mainScript10E();
mainScript11A();
mainScript11B();
mainScript11C();
mainScript11D();
mainScript11E();
mainScript12A();
mainScript12B();
mainScript12C();
mainScript12D();
mainScript12E();
mainScript13A();
mainScript13B();
mainScript13C();
mainScript13D();
mainScript13E();
}
function mainScriptByClass(vClassCol, vEmailCol, vFolderCol) {
if (vClassName != "ALL" ){
/* var TrueMaintain = SpreadsheetApp.openById(MAINTAIN_GS).getSheetByName(MAINTAIN_SN);
var vClassName = TrueMaintain.getRange(vClassCol).getValue();
var emailID = TrueMaintain.getRange(vEmailCol).getValue();
var urlID = TrueMaintain.getRange(vFolderCol).getValue(); */
var vClassName = SpreadsheetApp.openById(MAINTAIN_GS).getSheetByName(MAINTAIN_SN).getRange(vClassCol).getValue();
var emailID = SpreadsheetApp.openById(MAINTAIN_GS).getSheetByName(MAINTAIN_SN).getRange(vEmailCol).getValue();
Logger.log("emailID " + emailID)
var urlID = SpreadsheetApp.openById(MAINTAIN_GS).getSheetByName(MAINTAIN_SN).getRange(vFolderCol).getValue();
generatePDFByClass(vClassName);
Logger.log("URLID " + urlID);
//generatePDFByClass(vClassName);
var linkFromID = "https://drive.google.com/drive/folders/" + urlID ;
// GmailApp.sendEmail(emailID, "PDF Files for this week", "Hello these are the PDF results of the forms your student filled out:" + inkFromID);
GmailApp.sendEmail(emailID, "PDF Files for this week", "Hello these are the PDF results of the forms your students filled out:" + linkFromID);
} else {
mainScript();
}
}
function loadMainDetails () {
var vClassName, vURL, vFTEmail;
/* const ssMainFileID = "1SR2NFYOHZ2h2JANUoTsIZvFhLJ7RQc2ob--dU4IPNxk";
const sMainFileSheet = "Class & Teacher Email"; */
var file = SpreadsheetApp.openById(MAINTAIN_GS);
var ss = file.getSheetByName(MAINTAIN_SN);
/* Get Data from maintenance file */
/* getRange (Row, Column, RowRange, ColumnRange) */
vData = ss.getRange(7,1, ss.getLastRow() - 1, ss.getMaxColumns()).getDisplayValues();
vData.forEach ( function (vRow) {
// rowNum = rowNum + 1;
if(vRow[0].length > 0 ){
vClassName = vRow[0];
vURL = vRow[1];
vFTEmail = vRow[2];
CLASSLIST.push(vClassName);
//MAINLIST.push("{'className':'"+ vClassName + "','url':'" + vURL + "','ft':'" + vFTEmail +"'}")
MAINLIST.push(vClassName + "," + vURL + "," + vFTEmail)
}
});
}
function getURLByClass(sKeyClass) {
var vURL = "";
var mapList = [];
for(var i in MAINLIST){
mapList = MAINLIST[i].toString().split(",")
if (sKeyClass == mapList[0] ) {
vURL = mapList[1];
break;
}
}
return vURL;
}
function getFTMailByClass(sKeyClass) {
var vEmail = "";
var mapList = [];
for(var i in MAINLIST){
mapList = MAINLIST[i].toString().split(",")
if (sKeyClass == mapList[0] ) {
vEmail = mapList[2];
break;
}
}
return vEmail;
}
function generatePDFByClass(vClass){
//Temp
loadMainDetails();
Logger.log("loadMainDetails");
var ss = SpreadsheetApp.getActiveSpreadsheet()
var respSheet =ss.getSheetByName(STUDENT_FORM_SN);
// Sorting by Class No
var vRange = respSheet.getRange("A:G")
vRange.sort ({column: 3, ascending: true} );
var rowNum = 1;
var vData = [];
var vHeader = [];
var vSName, vFTClass;
var vPrevFTEmail = "", vPrevFTClass = "" ;
var vFTEmail = "";
var headerNum = 0;
var maxColumn = respSheet.getMaxColumns();
Logger.log("maxColumn" + maxColumn);
/* Get Headers to apply in template as Q */
vHeader = respSheet.getRange(1,1,1, maxColumn).getDisplayValues();
/* Get Data to apply in in template as A */
Logger.log("Header" + vHeader);
/* getRange (Row, Column, RowRange, ColumnRange) */
vData = respSheet.getRange(2,1, respSheet.getLastRow() - 1, maxColumn).getDisplayValues();
Logger.log("Data " + vData);
/* Response inputs */
vData.forEach ( function (vRow) {
rowNum = rowNum + 1;
//if(vRow[3] == vClass){
if(vRow[2] == vClass){
vSName = vRow[1];
vFTClass = vRow[2];
Logger.log("vFTClass " + vFTClass);
// vFTClass = vRow[3];
vPrevFTEmail = vFTEmail;
// Setup FT Email at Column AA for Reference.
//respSheet.getRange("AA" + rowNum).setValue(vFTEmail);
// Start Column
var vStartColumn = 1
// Student Line for generate PDF and Email
sData = respSheet.getRange(rowNum,vStartColumn, 1, maxColumn).getDisplayValues();
// var nPdfFile = createBulkPDF(vHeader, sData, vSName, vFTClass);
createBulkPDF(vHeader, sData, vSName, vFTClass);
vPrevFTClass = vFTClass;
}
});
}
function mainScript(){
//Temp
loadMainDetails();
var ss = SpreadsheetApp.getActiveSpreadsheet()
var respSheet =ss.getSheetByName(STUDENT_FORM_SN);
// Sorting by Class No
var vRange = respSheet.getRange("A:G")
vRange.sort ({column: 3, ascending: true});
var rowNum = 1;
var vData = [];
var vHeader = [];
var vSName, vFTClass;
var vPrevFTEmail = "", vPrevFTClass = "" ;
var vFTEmail = "";
var headerNum = 0;
var maxColumn = respSheet.getMaxColumns()
/* Get Headers to apply in template as Q */
vHeader = respSheet.getRange(1,1,1, maxColumn).getDisplayValues();
/* Get Data to apply in in template as A */
/* getRange (Row, Column, RowRange, ColumnRange) */
vData = respSheet.getRange(2,1, respSheet.getLastRow() - 1, maxColumn).getDisplayValues();
/* Response inputs */
vData.forEach ( function (vRow) {
rowNum = rowNum + 1;
if(vRow[0].length > 0 ){
vSName = vRow[1];
vFTClass = vRow[3];
vPrevFTEmail = vFTEmail;
vFTEmail = getFTMailByClass(vFTClass);
/* Send email by Grouping */
if (vPrevFTClass != "" && vFTClass != vPrevFTClass) {
sendRespMail(vPrevFTEmail, vPrevFTClass);
// vPrevFTClass = vFTClass;
// vAllPDF.splice(0, vAllPDF.length)
}
}
// Setup FT Email at Column AA for Reference.
//respSheet.getRange("AA" + rowNum).setValue(vFTEmail);
// Start Column
var vStartColumn = 1
// Student Line for generate PDF and Email
sData = respSheet.getRange(rowNum,vStartColumn, 1, maxColumn).getDisplayValues();
//var nPdfFile = createBulkPDF(vHeader, sData, vSName, vFTClass);
createBulkPDF(vHeader, sData, vSName, vFTClass);
vPrevFTClass = vFTClass;
//vAllPDF.push(nPdfFile);
});
/* Last Email Group */
//if (vFTClass != vPrevFTClass) {
// sendRespMail(vFTEmail, vFTClass);
sendRespMail(vFTEmail,vFTClass )
//}
}
function sendRespMail(email, className){
var url_link = getURLByClass(className);
var message = "Hello, this is a URL fpr group of PDFs containing the Data of your class (" + className + ")";
message = message + "stduents who filled out the Google Form.\n";
message = message + "https://drive.google.com/drive/folders/" + url_link;
var emailTo = email;
var subject = "Forms filled out this Friday";
var html = message;
if( emailTo != "") {
GmailApp.sendEmail(emailTo,subject,html,"" );
}
}
function sendRespMailforAttachment(email, pdfFiles){
var message = "Hello, this is a group of PDFs containing the Data of your class ";
message = message + "stduents who filled out the Google Form."
var emailTo = email;
var subject = "Forms filled out this Friday";
var html = message;
if( emailTo != "") {
// GmailApp.sendEmail(emailTo,subject,html,{ attachments: pdfFiles } );
}
}
function createBulkPDF(pHeader, pData, pSName, pClass){
Logger.log("createBulkPDF1");
var rowNum = 2;
var allPDF = [];
var vTemplateDocFile = DriveApp.getFileById(TEMPLATE_DOC_ID);
var vTempFolder = DriveApp.getFolderById(TEMPFOLDER_ID);
var tempFile = vTemplateDocFile.makeCopy(vTempFolder);
var tempDocFile = DocumentApp.openById(tempFile.getId());
var body = tempDocFile.getBody();
// Fill in Header for A { }
var hRowNum = 1;
pHeader.forEach ( function (hRow) {
hRowNum = hRowNum + 1;
for (var i = 1; i < hRow.length; i++){
body.replaceText("{Q" + i + "}", hRow[i] );
}
});
Logger.log("createBulkPDF2");
// Fill in Inputs for Q { }
var sRowNum = 1;
pData.forEach ( function (sRow) {
sRowNum = sRowNum + 1;
for (var i = 1; i < sRow.length; i++){
body.replaceText("{A" + i + "}", sRow[i] );
}
});
tempDocFile.saveAndClose();
var vPdfContentBlob = tempDocFile.getAs(MimeType.PDF);
var pdfName = pSName+Utilities.formatDate(new Date(),"GMT+8"," HH:mm:ss")+".pdf";
var classFolderID = getURLByClass(pClass);
var vPdfFolder = DriveApp.getFolderById(classFolderID);
var pdfRpt=vPdfFolder.createFile(vPdfContentBlob).setName(pdfName);
// var pdfRpt=vPdfFolder.createFile(vPdfContentBlob).setName(pdfName);
DriveApp.getFileById(tempFile.getId()).setTrashed(true);
Logger.log("createBulkPDF3");
return pdfRpt;
}
I understand that you already have a Sheet with the data and a separate function to convert it to a PDF, therefore you only need to refine the table so it only shows a particular student. Based on your comment, I guess that the table looks like this:
First of all I strongly recommend you to use a filter table instead of a pivot table, because it will fit better to your goal and you can code it pretty easily. You can include my example below into your project as is, you would only need to change the student name from Irène to whatever your need.
function studentFilter() {
var sheet = SpreadsheetApp.getActive().getActiveSheet();
var data = sheet.getDataRange().getA1Notation();
sheet.getRange(data).createFilter();
var criteria = SpreadsheetApp.newFilterCriteria().whenTextEqualTo('Irène')
.build();
sheet.getFilter().setColumnFilterCriteria(1, criteria);
};
That code would use the SpreadsheetApp class of Apps Script to open the Sheet and get its range. After that it would create a filter table using the .createFilter() method and the FilterCriteriaBuilder class. After both the filter table and its criteria is created, it just applies the criteria to the table. This would be the end result on the previous example:
At this point the Sheet already looks like your goal, so you only need to follow your usual workflow and call the printing PDF function.
This header is contained in a js file https://www.portaldefinancas.com/js-tx-ctb/th-cdib.js
document.write(""),document.write('</p></caption><thead><tr><th rowspan="4">Mês de<br>Referência</th><th colspan="7">Taxas - %</th></tr><tr> <th rowspan="3">Mensal</th><th colspan="4">Anualizada</th><th colspan="2">Acumulada</th></tr><tr> <th colspan="2">Ano de<br>252 dias<br> úteis</th><th colspan="2">Ano de<br>365/366 dias<br>corridos</th><th rowspan="2">No ano</th><th rowspan="2">Em <br>12 meses</th></tr><tr><th>Dias</th><th> Taxa</th><th>Dias</th><th> Taxa</th></tr></thead><tbody>');
How can I parse the headers respecting the merged rows and merged columns. The script I use today is
function getHeaders(url) {
var source = UrlFetchApp.fetch(url).getContentText()
source = source.split('document')[2]
var table = '<table><tr><th ' + source.match(/(?<=<th ).*(?=th>)/g) + 'th></tr></table>'
table=table.replace(/ê/g,'ê').replace(/ú/g,'ú').replace(/<br>/g,'\n')
var doc = XmlService.parse(table);
var rows = doc.getDescendants().filter(function(c) {
var element = c.asElement();
return element && element.getName() == "tr";
});
var data = rows.slice(0).map(function(row) {
return row.getChildren("th").map(function(cell) {
return cell.getValue();
});
});
return data;
}
but it doesn't respect merged areas. Thanks for any help !
Since intellectual exercises is my my drug of choice... I can't help it. Here is the possible solution. It works to a degree but it shows little the traits of lofty style of coding:
function main() {
var sheet = SpreadsheetApp.getActiveSheet();
var data = getHeaders();
data = handle_rowspans(data);
sheet.getRange(1, 1, data.length, data[0].length).setValues(data);
}
function getHeaders(url) {
// var source = UrlFetchApp.fetch(url).getContentText()
// source = source.split('document')[2]
var source = `<thead><tr><th rowspan="4">Mês de<br>Referência</th><th colspan="7">Taxas - %</th></tr><tr> <th rowspan="3">Mensal</th><th colspan="4">Anualizada</th><th colspan="2">Acumulada</th></tr><tr> <th colspan="2">Ano de<br>252 dias<br> úteis</th><th colspan="2">Ano de<br>365/366 dias<br>corridos</th><th rowspan="2">No ano</th><th rowspan="2">Em <br>12 meses</th></tr><tr><th>Dias</th><th> Taxa</th><th>Dias</th><th> Taxa</th></tr></thead><tbody>`;
source = handle_colspans(source);
table = '<table><tr><th ' + source.match(/(?<=<th ).*(?=th>)/g) + 'th></tr></table>';
table = table.replace(/ê/g, 'ê').replace(/ú/g, 'ú').replace(/<br>/g, '\n');
var doc = XmlService.parse(table);
var rows = doc.getDescendants().filter(function (c) {
var element = c.asElement();
return element && element.getName() == "tr";
});
var data = rows.slice(0).map(function (row) {
return row.getChildren("th").map(function (cell) {
return cell.getValue();
});
});
return data;
}
function handle_colspans(table) {
return table.split('</tr>').map(r => add_cells_in_row(r)).join('</tr>');
function add_cells_in_row(row) {
var cells = row.split('</th>');
for (var i in cells) {
if (/colspan/.test(cells[i])) {
var colspan = cells[i].replace(/.*colspan="(\d+).*/, '$1');
cells[i] += '{col' + colspan + '}';
cells[i] = [cells[i], ...(new Array(+colspan - 1).fill('<th>'))];
}
if (/rowspan/.test(cells[i])) {
var rowspan = cells[i].replace(/.*rowspan="(\d+).*/, '$1');
cells[i] += '{row' + rowspan + '}';
}
}
return cells.flat().join('</th>')
}
}
function handle_rowspans(array) {
for (var row in array) {
for (var col in array[row]) {
if (/\{row/.test(array[row][col])) {
var rowspan = array[row][col].replace(/.*\{row(\d+).*/s, '$1');
for (var r = 1; r < rowspan; r++) array[+row + r].splice(col, 0, '')
}
}
}
return array;
}
It will get you the table like this:
Whrere {row#} and {col#} means how many cells or rows to the left or to the bottom you need to join to the current cell to recreate the original design. It could be the next dose of the intellectual exercises. :)
A solution, example with url = https://www.portaldefinancas.com/js-tx-ctb/th-cdib.js
function getHeaders(url) {
var source = UrlFetchApp.fetch(url).getContentText()
source = source.split('document')[2]
var table = '<table><tr><th ' + source.match(/(?<=<th ).*(?=th>)/g) + 'th></tr></table>'
table=table.replace(/ê/g,'ê').replace(/ú/g,'ú').replace(/<br>/g,'\n')
var doc = XmlService.parse(table);
var rows = doc.getDescendants().filter(function(c) {
var element = c.asElement();
return element && element.getName() == "tr";
});
var n=0
var data=[]
rows.slice(0).map(function(row) {data[n++]=[]})
n=0
rows.slice(0).map(function(row) {
row.getChildren("th").map(function(cell) {
try{nbcols = cell.getAttribute('colspan').getValue()}catch(e){nbcols = 1}
try{nbrows = cell.getAttribute('rowspan').getValue()}catch(e){nbrows = 1}
var value = cell.getValue()
r=0
var free=0
while(r<nbrows*1){
c=0
while(c<nbcols*1){
while(data[n+r][free]!=null){free++}
data[n+r][free]=(value)
value=''
c++
}
r++
}
});
n++
});
return (data);
}
I am a teacher and need to create lots of multiple choice quizzes like this for my students.
You will see that each multiple choice question has exactly the same format - a question to be uploaded as an image and then followed by 4 multiple choice options - A, B, C and D.
My question is, is there any way to automate this process?
Each batch of questions are inside a googledrive folder and are named '1.png', '2.png', '3.png', etc - these are to be uploaded as images on the google form.
In a separate folder, I have a googlesheet listing all the answers to each question, it looks like this.
So the numbers that match with the answers (letters) in the spreadsheet correspond to the image files (eg. the first row of the spreadsheet above shows that the answer to question 1.png is A)
In a separate folder, I have another googlesheet, with feedback for both incorrect and correct answers which looks like this. Not all questions have feedback.
Is there anyway to automatically generate quizzes from these googlesheets and png files?
Thanks for taking the time to read through all this, and special thanks if you are able to suggest a solution?
Here's a solution to a questioner that wanted to randomly select a given number of questions from a question bank. This doesn't utilize google forms but rather it uses html forms.
Here's the code (your welcome to adapt it):
I had some questions on the code and corrected and problem and went in and updated the code. It's a little more organized and a bit easier to follow...I hope.
Code.gs:
function onOpen() {
SpreadsheetApp.getUi().createMenu('Questions Menu')
.addItem('Questions', 'launchQuestionsDialog')
.addToUi();
}
question.gs
function getQuestions() {
var ss=SpreadsheetApp.getActive();
var cpData=getCpData();
var qnum=cpData.qNum;
var qa=getQAndA();
var qi=getAnswerIndexes();
var html='';
var clr=['#f6d1ac','#c5e9bd'];
for(var i=0;i<qa.length;i++) {
html+=Utilities.formatString('<div id=d%s style="font-weight:bold;background-color:%s;padding:5px;"><span id="q%s">%s</span><input type="hidden" value="%s" class="hiding" />',qa[i][0],clr[i % 2],qa[i][0],qa[i][1],qa[i][0]);
html+=Utilities.formatString('<input type="hidden" value="%s" class="hiding" />',qa[i][0]);
for(var j=qi.firstIdx;j<=qi.lastIdx;j++) {
if(qa[i][j]) {
html+=Utilities.formatString('<br /><input type="radio" name="n%s" value="%s" />%s',qa[i][0],qa[i][j],qa[i][j]);
}
}
html+='</div>'
}
html+='<div id="controls"><br /><input type="button" value="Submit" onClick="recordData();" /></div>';
return {html:html}
}
function launchQuestionsDialog() {
var userInterface=HtmlService.createHtmlOutputFromFile('questions').setWidth(800).setHeight(500);
SpreadsheetApp.getUi().showModelessDialog(userInterface, "The new Questions");
}
function selectTest() {
var qA=selectQuestions(5,24);
Logger.log(qA);
}
function selectQuestionIndexes(n,m) {
var set=[];
do {
var i=Math.floor(Math.random()*(m));
if(set.indexOf(i)==-1) {
set.push(i);
}
}while(set.length<n);
return set;
}
function getCpData() {
var ss=SpreadsheetApp.getActive();
var cpSh=ss.getSheetByName('ControlPanel');
var cpRg=cpSh.getDataRange();
var cpVa=cpRg.getValues();
var qsrcSh=ss.getSheetByName(cpVa[1][0]);
var adesSh=ss.getSheetByName(cpVa[1][1]);
var qnum=cpVa[1][2];
var cpData={'qSrc':cpVa[1][0],'aDes':cpVa[1][1],'qNum':cpVa[1][2]};
return cpData;
}
function getAnswerIndexes() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName(getCpData().qSrc);
var rg=sh.getRange(1,1,1,sh.getLastColumn());
var vA=rg.getValues();
var re=/Answer \d{1,2}/i;
var fidx=0;
var lidx=0;
var first=true;
vA[0].forEach(function(e,i){if(String(vA[0][i]).match(re))if(first){fidx=i;first=false;}else{lidx=i;}});
return {'firstIdx':fidx,'lastIdx':lidx};
}
function recordData(responses) {
if(responses) {
var ss=SpreadsheetApp.getActive();
var sheetname=getCpData().aDes;
var sh=ss.getSheetByName(sheetname);
var ts=Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "MM/dd/yyyy HH:mm:ss");
responses.forEach(function(e,i){e.splice(0,0,ts);sh.appendRow(e)});
}
return true;
}
function doGet() {
return HtmlService.createHtmlOutputFromFile('questions');
}
function getQAndA() {
var qa=[];
var cma=',';
var ss=SpreadsheetApp.getActive();
var cpData=getCpData();
var qsrcSh=ss.getSheetByName(cpData.qSrc);
var qsrcRg=qsrcSh.getRange(2,1,qsrcSh.getLastRow()-1,qsrcSh.getLastColumn());
var qsrcVa=qsrcRg.getValues();
var qs=selectQuestionIndexes(cpData.qNum,qsrcVa.length);
var aIdxs=getAnswerIndexes();
for(var i=0;i<qsrcVa.length;i++) {
var qas='';
if(qs.indexOf(i)>-1) {
qas+=qsrcVa[i][0] + cma + qsrcVa[i][1];
for(j=aIdxs.firstIdx;j<=aIdxs.lastIdx;j++) {
if(qsrcVa[i][j]) {
qas+= cma + qsrcVa[i][j];
}
}
qa.push(qas.split(cma));
}
}
return qa;
}
questions.html:
<!DOCTYPE html>
<html>
<head>
<!--<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>-->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
$(function() {
google.script.run
.withSuccessHandler(function(hObj){
$('#container').html(hObj.html);
})
.getQuestions();
});
function recordData() {
var responses=[];
var cm=',';
var divs = document.getElementsByTagName("div");
for(var i=0;i<divs.length;i++) {
var id=divs[i].getAttribute(['id']);
var qnum=$('div#' + id + ' ' + 'input.hiding').val();
//var question=document.getElementById(id).innerHTML;
var question=$('#q' + qnum ).text();
var answer=$('input[name="n' + qnum + '"]:checked').val();
if(id!='controls') {
if(!answer) {
window.alert('You did not answer question number ' + Number(i+1) + '. It is a requirement of this survey that all questions must be answered.' );
return;
}else {
var end='is near';
var s=qnum + cm + question + cm + answer;
responses.push(s.split(cm));
}
}
}
google.script.run
.withSuccessHandler(displayThanks)
.recordData(responses);
}
function displayThanks() {
var divs = document.getElementsByTagName("div");
for(var i=0;i<divs.length;i++) {
divs[i].style.cssText="display:none;text-align:center";
}
var elemDiv = document.createElement('div');
elemDiv.innerHTML="<br /><h1>Thank You For Your Participation in This Survey</h1>";
document.body.appendChild(elemDiv);
}
console.log('My Code');
</script>
<style>
#reply{display:none;}
#collect{display:block;}
body {
background-image: url("http://myrabridgforth.com/wp-content/uploads/blue-sky-clouds.jpg");
background-color: #ffffff;
background-repeat: no-repeat;
background-position: left bottom;
}
</style>
</head>
<body>
<div id="container">
</div>
</body>
</html>
Here's what the various tabs on the spreadsheet look like:
The ControlPanel Tab:
The QuestionBank Tab:
The Test1 Tab:
Hopefully this will be of some value to you.
To over-come the problem in high densely populated area
ultra dense network is suggested. Ultra dense network
maintain a constant connectivity, data speed in highly
populated area.
I'm trying to create a form of 20 or so multiple choice questions (Survey, not right or wrong, if it matters) from a larger bank.
I have seen a few examples on how to generate a form from a google spreadsheet (http://alicekeeler.com/2014/12/12/google-forms-create-a-quiz-from-a-question-bank/ comes close, for example), but my problem goes a bit further in that I need to generate a form with 20 random questions from the bank for each user.
Having searched the web and read a bunch of documentation, I'm still having a hard time trying to determine if it is possible to generate a different form for each viewer using google app script (And if yes, how) or if I should move on to something more robust.
Random Questions from a Question Bank
An example of how you might do a project for making a pseudo random selection of questions from a question bank and displaying them in html format. Upon click a submit request and making sure that all questions have been answered and storing the responses in a spreadsheet.
Here's a link to this code and a video demo of the project.
Here's the Code.gs file:
function onOpen()
{
SpreadsheetApp.getUi().createMenu('Questions Menu')
.addItem('Questions', 'questionsToHtml')
.addToUi();
}
function selectTest()
{
var qA=selectQuestions(5,24);
Logger.log(qA);
}
function selectQuestionIndexes(n,m)
{
var set=[];
do
{
var i=getRandomIntInclusive(0,m);
if(set.indexOf(i)<0)
{
set.push(i);
}
}while(set.length<n);
return set;
}
function getRandomIntInclusive(min, max)
{
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min; //The maximum is inclusive and the minimum is inclusive
}
function getCpData()
{
var ss=SpreadsheetApp.getActive();
var cpSh=ss.getSheetByName('ControlPanel');
var cpRg=cpSh.getDataRange();
var cpVa=cpRg.getValues();
var qsrcSh=ss.getSheetByName(cpVa[1][0]);
var adesSh=ss.getSheetByName(cpVa[1][1]);
var qnum=cpVa[1][2];
var cpData={'qSrc':cpVa[1][0],'aDes':cpVa[1][1],'qNum':cpVa[1][2]};
return cpData;
}
function getAnswerIndexes()
{
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName(getCpData().qSrc);
var rg=sh.getRange(1,1,1,sh.getLastColumn());
var vA=rg.getValues();
var re=/Answer \d{1,2}/i;
var fidx=0;
var lidx=0;
var first=true;
for(var i=0;i<vA[0].length;i++)
{
if(String(vA[0][i]).match(re))
if(first)
{
fidx=i;
first=false;
}
else
{
lidx=i;
}
}
return {'firstIdx':fidx,'lastIdx':lidx};
}
function testResp()
{
var row = [[1,'What is the question?','no'],[2,'What is the question?','no'],[3,'What is the question?','no']];
recordData(row);
}
function recordData(responses)
{
if(responses)
{
var ss=SpreadsheetApp.getActive();
var sheetname=getCpData().aDes;
var sh=ss.getSheetByName(sheetname);
var ts=Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "MM/dd/yyyy HH:mm:ss");
for(var i=0;i<responses.length;i++)
{
responses[i].splice(0,0,ts);
sh.appendRow(responses[i]);
}
}
return true;
}
function questionsToHtml(web)
{
var web=(typeof(web)!='undefined')?web:false;
var br='<br />';
var cm=',';
var ss=SpreadsheetApp.getActive();
var cpData=getCpData();
var qnum=cpData.qNum;
var qa=getQAndA();
var qi=getAnswerIndexes();
var s='';
for(var i=0;i<qa.length;i++)
{
//s+='<table>';
//s+=Utilities.formatString('<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>' , qa[i][0],qa[i][1],(qa[i][2])?qa[i][2]:' ',(qa[i][3])?qa[i][3]:' ',(qa[i][4])?qa[i][4]:' ',(qa[i][5])?qa[i][5]:' ');
//s+='</table>';
s+='<div id="d' + qa[i][0] + '" style="font-weight:bold;">' + qa[i][1];
s+='<input type="hidden" value="' + qa[i][0] + '" class="hiding" />';
for(var j=qi.firstIdx;j<=qi.lastIdx;j++)
{
if(qa[i][j])
{
s+=br + '<input type="radio" name="n'+ qa[i][0] +'" value="' + qa[i][j] + '" />' + qa[i][j];
}
}
s+='</div>'
}
s+='<div id="controls">';
s+=br + '<input type="button" value="Submit" onClick="recordData();" />';
//s+=br + '<input type="button" value="Do It Again" onClick="google.script.run.questionsToHtml();" />';
s+='</div>';
s+='</body></html>';
//Logger.log(s);
if(!web)
{
var userInterface=HtmlService.createHtmlOutputFromFile('htmlToBody').append(s).setWidth(1000).setHeight(500);
SpreadsheetApp.getUi().showModelessDialog(userInterface, 'Current Form')
}
else
{
var output=HtmlService.createHtmlOutputFromFile('htmlToBody').append(s);
return output.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
}
function doGet()
{
return questionsToHtml(true)
}
function getQAndA()
{
var qa=[];
var cma=',';
var ss=SpreadsheetApp.getActive();
var cpData=getCpData();
var qsrcSh=ss.getSheetByName(cpData.qSrc);
var qsrcRg=qsrcSh.getRange(2,1,qsrcSh.getLastRow()-1,qsrcSh.getLastColumn());
var qsrcVa=qsrcRg.getValues();
var qs=selectQuestionIndexes(cpData.qNum,qsrcVa.length-1);
var aIdxs=getAnswerIndexes();
for(var i=0;i<qsrcVa.length;i++)
{
var qas='';
if(qs.indexOf(i)>-1)
{
qas+=qsrcVa[i][0] + cma + qsrcVa[i][1];
for(j=aIdxs.firstIdx;j<=aIdxs.lastIdx;j++)
{
if(qsrcVa[i][j])
{
qas+= cma + qsrcVa[i][j];
}
}
qa.push(qas.split(cma));
}
}
return qa;
}
Here's the htmlToBody.html file
<!DOCTYPE html>
<html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>
$(function() {
});
function recordData()
{
var responses=[];
var cm=',';
var divs = document.getElementsByTagName("div");
for(var i=0;i<divs.length;i++)
{
var id=divs[i].getAttribute(['id']);
var qnum=$('div#' + id + ' ' + 'input.hiding').val();
var question=document.getElementById(id).innerHTML;
var answer=$('input[name="n' + qnum + '"]:checked').val();
if(id!='controls')
{
if(!answer)
{
window.alert('You did not answer question number ' + Number(i+1) + '. It is a requirement of this survey that all questions must be answered.' );
return;
}
else
{
var end='is near';
var s=qnum + cm + question + cm + answer;
responses.push(s.split(cm));
}
}
}
google.script.run
.withSuccessHandler(displayThanks)
.recordData(responses);
}
function displayThanks()
{
var divs = document.getElementsByTagName("div");
for(var i=0;i<divs.length;i++)
{
divs[i].style.cssText="display:none;text-align:center";
}
var elemDiv = document.createElement('div');
elemDiv.innerHTML="<br /><h1>Thank You For Your Participation in This Survey</h1>";
document.body.appendChild(elemDiv);
}
console.log('My Code Here');
</script>
<style>
#reply{display:none;}
#collect{display:block;}
body {
background-image: url("");
background-color: #ffffff;
background-repeat: no-repeat;
}
</style>
</head>
<body>
I try to send the all attachments to my Google Drive account but I have problem with the search of GmailApp.search.
I have a Camera in my home that sends an email every 5 minutes with the same Subject and each message has an attached file that's name is the day-time.jpg
First I think in search by label and then of the process, I put a new label to mark and don't repeat the message, but I always receive all the message.
I tried to test many ways and always received the message that has label:processed.
function () {
//All message of the camera have the label GoogleDrive
var threads = GmailApp.search("has:attachment -label:processed label:GoogleDrive", 0, 5);
var folder = getFolder(driveFolder);
for (var x=0; x<threads.length; x++) {
var message = threads[x].getMessages();
for(var y=0; y<message.length; y++) {
var desc = message[y].getSubject() + " #" + message[y].getId();
var att = message[y].getAttachments();
for (var z=0; z<att.length; z++) {
try {
if (check) {
var name = att[z].getName();
if (name.indexOf(".") != -1) {
var extn = name.substr(name.lastIndexOf(".")+1).toLowerCase();
if (valid.indexOf(extn) != -1) {
file = folder.createFile(att[z]);
file.setDescription(desc);
} else {
Logger.log("Skipping " + name);
}
}
} else {
file = folder.createFile(att[z]);
file.setDescription(desc);
}
}
catch (e) {
Logger.log(e.toString());
}
}
}
threads[x].addLabel(moveToLabel); //this variable is processed label
}
}
Regards
I have this function. I use the ctrlq.org code as base, only I put the principal function.
Now I copy all the script
function createFilter(label, archiveLabel) {
var filter = "has:attachment -label:" + archiveLabel + " label:" + label;
return filter;
}
function help() {
var html = HtmlService.createHtmlOutputFromFile('help')
.setTitle("Google Scripts Support")
.setWidth(400)
.setHeight(260);
var ss = SpreadsheetApp.getActive();
ss.show(html);
}
function premium() {
var html = HtmlService.createHtmlOutput('Upgrade to the premium edition of Send to Google Drive and unlock new features. You can also opt for one-on-one support via email, Skype or Google Hangouts.')
.setTitle("Send to Google Drive Premium")
.setWidth(240)
.setHeight(100);
var ss = SpreadsheetApp.getActive();
ss.show(html);
}
function sendToGoogleDrive() {
var sheet = SpreadsheetApp.getActiveSheet();
var gmailLabels = sheet.getRange("D4:D4").getValue();
var driveFolder = sheet.getRange("D5:D5").getValue();
var archiveLabel = sheet.getRange("D6:D6").getValue();
var filetypes = sheet.getRange("D7:D7").getValue();
var valid = filetypes.replace(/\s/g,"").toLowerCase().split(",");
var check = true;
if ( (valid.indexOf("all") != -1) || (valid.length == 1 && valid[0] == "")) {
check = false;
}
var moveToLabel = getGmailLabel(archiveLabel);
var filter = createFilter(gmailLabels, archiveLabel);
var threads = GmailApp.search(filter, 0, 5);
var folder = getFolder(driveFolder);
sheet.getRange("D9:D9").setValue(sheet.getRange("D9:D9").getValue() + ", [th(" + threads.length + ")");
for (var x=0; x<threads.length; x++) {
var message = threads[x].getMessages();
sheet.getRange("D9:D9").setValue(sheet.getRange("D9:D9").getValue() + ", ms(" + message.length + ")");
for(var y=0; y<message.length; y++) {
var desc = message[y].getSubject() + " #" + message[y].getId();
var att = message[y].getAttachments();
//sheet.getRange("D9:D9").setValue(sheet.getRange("D9:D9").getValue() + ", at(" + att.length + ")");
for (var z=0; z<att.length; z++) {
try {
if (check) {
var name = att[z].getName();
if (name.indexOf(".") != -1) {
var extn = name.substr(name.lastIndexOf(".")+1).toLowerCase();
if (valid.indexOf(extn) != -1) {
file = folder.createFile(att[z]);
file.setDescription(desc);
} else {
Logger.log("Skipping " + name);
}
}
} else {
file = folder.createFile(att[z]);
file.setDescription(desc);
}
}
catch (e) {
Logger.log(e.toString());
}
}
}
threads[x].moveToTrash();
threads[x].addLabel(moveToLabel);
}
sheet.getRange("D9:D9").setValue(sheet.getRange("D9:D9").getValue() + "]");
}
function configure() {
reset(true);
ScriptApp.newTrigger("sendToGoogleDrive").timeBased().everyMinutes(5).create();
Browser.msgBox("Initialized", "The program is now running. You can close this sheet", Browser.Buttons.OK);
}
function init() {
return;
}
function onOpen() {
var menu = [
{name: "Help and Support »",functionName: "help"},
null,
{ name: "Step 1: Authorize", functionName: "init" },
{ name: "Step 2: Run Program", functionName: "configure" },
null,
{ name: "Uninstall (Stop)", functionName: "reset" },
null,
{name: "Upgrade to Premium »",functionName: "premium"},
null
];
SpreadsheetApp.getActiveSpreadsheet()
.addMenu("Gmail Attachments", menu);
}
function getFolder(parent) {
var parentFolder, searchFolder = DriveApp.getFoldersByName(parent);
if (searchFolder.hasNext()) {
parentFolder = searchFolder.next();
} else {
parentFolder = DriveApp.createFolder(parent);
}
return parentFolder;
}
function getGmailLabel(name) {
var label = GmailApp.getUserLabelByName(name);
if ( ! label ) {
label = GmailApp.createLabel(name);
}
return label;
}
function reset(e) {
var triggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < triggers.length; i++) {
ScriptApp.deleteTrigger(triggers[i]);
}
if (!e) {
Browser.msgBox("Script Stopped", "You can start the script anytime later!", Browser.Buttons.OK);
}
}
My inbox have these mails:
1
The problem is then I run the script the mail has a new label (now I send to Trash too), but in the next run the GmailApp.Search, find the mail that have processed label.
I copy the configuration sheet
2
It looks like you have not defined moveToLabel and hence the same set of Gmail threads are getting processed in each loop.
Add this line before the for loop.
var moveToLabel = GmailApp.getUserLabelByName("processed");
if ( ! moveToLabel ) {
moveToLabel = GmailApp.createLabel(processed);
}
You can get the full snippet at ctrlq.org.