Generating a user-specific form with google app script - google-apps-script

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>

Related

How can I parse html table inside a script respecting merged cells

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);
}

Automatically generating multiple choice quizzes on google forms

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.

How to add conditional questions and logical flow to google form?

I'm currently working on trying to build a google form from a spreadsheet, and I am not sure how to programmatically build conditional questions and branching with the google apps script.
The format of the spreadsheet looks like this:
https://imgur.com/a/bBYkU
And the issue I'm having is figuring how to make it so that questions 1.1 for example goes to 1.2 when you say yes, but goes to question 2 if you say no. I looked into PageNavigationType, but I am iterating through the spreadsheet so I'm unsure how to link to a page I haven't made yet.
Questions from a Question Bank
This does not have a conditional branching based upon previous answers but covers a lot of other stuff. I've done something similar to what you're doing but it was built for a single purpose and not generalized for reprogramming by another user.
Here's an example of a webapp contained in a Spreadsheet that I call Question from a Question Bank. You select the number of questions and it picks that number of questions randomly from a Question Bank. It generates the form, collects the results and stores them on a spreadsheet. I have a video that shows how to use and install it here.
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;
}
htmlToBody.html
<!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>

Link to Google Drive from Content of Google Sheets

I'm absolutely swimming in code issues and can't seem to get this working. I'm hoping that this group of wise Google Apps Script-geniuses can help.
First: My Google Drive uses the following folder structure (with first subfolders for each letter of the alphabet and then second subfolders for each matter, e.g.
/Clients
/A
/Albert, Bob (1-15-0003) Re Matter
/B
/Bork, Mat (1-54-0003) Re Other Matter
I have a Google Sheets document with various sheets that each refer to a different matter number in their respective cell G2. I would like a script that automatically searches my Google Drive for those second subfolders and creates a hyperlink to the folder.
My code is as follows, but sadly it doesn't work. It seems that it can't find the subfolders.
function SearchFolder() {
// Searches Google Drive for the folder for the Active Sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var searchTerm = ss.getRange('G2').getValue();
var Folders = DriveApp.searchFolders("title contains '"+searchTerm.replace("'","\'")+"' and trashed = false and hidden = false");
var Folder = Folders.next();
sheet.getRange('G2').setFormula("=HYPERLINK(\""+Folder.getUrl()+"\",\""+searchTerm+"\")");
}
Expected result would be, if G2 of my current sheet reads 1-15-0003 for that G2 to be replaced with a hyperlink with the Google Drive URL pointing to the folder ID for /A/Albert, Bob (1-15-0003) Re Matter.
Thanks for any help.
An Interactive Directory Tree
These functions work together with the JStree JQuery plugin to produce a directory interactive tree. There's a couple of Id's you'll have to add namely the ID of the DataStorage Folder and the ID of the Root Directory in your tree. There is also a doGet function already done in here in case you want a webapp. There are several files that I use for html as well as some html code that is integrated into the traverse code. It was a fairly complicated project for me. There are probably better ways to do this.
File In DataStorage Folder: TopToBodyWeb
<html>
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/jstree.min.js"></script>
<style>.file{color:#d81840;}.contrls{padding:10px 10px 10px 10px;}.timestmp {width: 200px;margin-left:5px;padding:10px 0px 5px 50px;background-color:rgba(121, 25, 0, 0.36);color:white;}i.jstree-icon.jstree-themeicon{display:none;}</style>
<script type="text/javascript">
$(function () { $('#selector').jstree({"core" : {"themes":{"variant":"large"}},"plugins" : [ "wholerow"]});
});
</script>
</head>
<body><div id="selector"><ul>
File In DataStorage Folder: TopToBody
<html>
<head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/jstree.min.js"></script>
<style>.file{color:#d81840;}.contrls{padding:10px 10px 10px 10px;}.timestmp {width: 200px;margin-left:5px;padding:10px 0px 5px 50px;background-color:rgba(121, 25, 0, 0.36);color:white;}i.jstree-icon.jstree-themeicon{display:none;}</style>
<script type="text/javascript">
$(function () { $('#selector').jstree({"core" : {"themes":{"variant":"large"}},"plugins" : [ "wholerow"]});
});
</script>
</head>
<body><div class="cntrls"><input type="button" value="Exit" onClick="google.script.host.close();" />
<input type="button" value="Delete Cache & Exit" onClick="google.script.run.clearDirlist();google.script.host.close();" /></div><div id="selector"><ul>
File In DataStorage Folder: BodyToEndWeb
</ul></div></body></html>
File In DataStorage Folder: BodyToEnd
</ul></div><div class="cntrls"><input type="button" value="Exit" onClick="google.script.host.close();" />
<input type="button" value="Delete Cache & Exit" onClick="google.script.run.clearDirlist();google.script.host.close();" /></div><br /></body></html>
DataStorage Folder: Also contains file: CurrentDirectoryListing
This is Code.gs:
var GStorage = '';
var folderID = "TheIDofDataStorageFolderGoesHere";
var guli = 0;
var glii = 0;
var glevel = 0;
function onOpen()
{
var ui = SpreadsheetApp.getUi();
ui.createMenu('My Tools')
.addItem('Dir MyDrive','runtraverseFolder')
.addToUi();
}
function runFile()
{
var file = loadFile();
if(file)
{
dispStatus('Stored MyDrive Directory List', file, 1000, 600);
}
else
{
SpreadsheetApp.getUi().alter('File not found in function runfile');
}
}
function loadFile(filename)
{
var filename = (typeof(filename) !== 'undefined')? filename : 'UploadFile';
var fldr = DriveApp.getFolderById(folderID);
var file = fldr.getFilesByName(filename);
var s = '';
while(file.hasNext())
{
var fi = file.next();
var target = fi.getName();
if(target == filename)
{
s = fi.getBlob().getDataAsString();
}
}
return s;
}
function delFile(filename)
{
var filename = (typeof(filename) !== 'undefined')? filename : 'UploadFile';
var fldr = DriveApp.getFolderById(folderID)
var file = fldr.getFilesByName(filename);
var targetFound = false;
while(file.hasNext())
{
var fi = file.next();
var target = fi.getName();
if(target == filename)
{
targetFound = true;
fldr.removeFile(fi);
SpreadsheetApp.getUi().alert('File: ' + filename + ' was removed from: ' + fldr.getName() + '/' + target);
}
}
return targetFound;
}
function doGet()
{
var output=HtmlService.createHtmlOutput(getStoredDirectory());
return output.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
This is traverse.gs:
function runtraverseFolder()
{
var dirlist = loadDirlist();
var controls = '<div class="cntrls"><input type="button" value="Exit" onClick="google.script.host.close()" /> \
<input type="button" value="Delete Cached Listing & Start Over" onClick="google.script.run.clearDirlist();" /></div><br />';
var toptobody = loadFile('TopToBody');
var bodytoend = loadFile('BodyToEnd');
if(dirlist)
{
dispStatus('Stored MyDrive Directory List', toptobody + GStorage + bodytoend, 600, 500);
}
else
{
GStorage ='';
GFlag = 0;
traverseFolder(DriveApp.getFolderById('ThisistheIDoftheRootTraverseFolder'));
saveDirlist();
dispStatus('Current MyDrive Directory Listing', toptobody + GStorage + bodytoend, 600, 500);
}
}
function getStoredDirectory()
{
loadDirlist();
var toptobody = loadFile('TopToBodyWeb');
var bodytoend = loadFile('BodyToEndWeb');
return toptobody + GStorage + bodytoend;
}
function traverseFolder(folderObj)
{
glevel++;
if(glevel < 2)
{
GStorage += '<li class="fldr" id="fd' + glii++ + '"' + 'data-jstree=\'{ "selected" : true, "opened" : true }\'' + '><a href="https://drive.google.com/open?id='+ folderObj.getId() +'" target="_blank" >Folder: ' + folderObj.getName() + '</a></span>';
//GStorage += '<li class="fldr" id="fd' + glii++ + '"' + 'data-jstree=\'{ "selected" : true, "opened" : true }\'' + '><a href="#" target="_blank" title="Dummy Link can not get file listing" >Folder: ' + folderObj.getName() + '</a></span>';
}
else
{
GStorage += '<li class="fldr" id="fd' + glii++ + '"><a href="https://drive.google.com/open?id=' + folderObj.getId() +'" target="_blank" >Folder: ' + folderObj.getName() + '</a></span>';
//GStorage += '<li class="fldr" id="fd' + glii++ + '"><a href="#" target="_blank" title="Dummy Link can not get file listing." >Folder: ' + folderObj.getName() + '</a></span>';
}
GStorage += '<ul id="ul'+ guli++ +'">';
var subs = folderObj.getFolders();
var files = folderObj.getFiles();
if(files)
{
//GStorage += '<ul id="ul'+ guli++ +'">';
while(files.hasNext())
{
var fi = files.next();;
GStorage += '<li class="file" id="fi' + glii++ + '"><a href="https://drive.google.com/open?id='+ fi.getId() +'" target="_blank" title="Right Click to Open File in new tab." >File: ' + fi.getName() + '</a></span>';
}
//GStorage += '</ul>';
}
while (subs.hasNext())
{
traverseFolder(subs.next());
}
GStorage += '</ul></li>';
glevel--;
}
function saveDirlist()
{
var filename = 'CurrentDirectoryListing';
var fldr = DriveApp.getFolderById(folderID);
var fldrname = fldr.getName();
var file = fldr.getFilesByName(filename);
var targetFound = false;
var timeStamp = '<div class="timestmp">' + Utilities.formatDate(new Date(), "GMT-7", "yyyy-MM-dd HH:mm:ss") + '</div>';
while(file.hasNext())
{
var fi = file.next();
var target = fi.getName();
if(target == filename)
{
targetFound = true;
fi.setContent(timeStamp + GStorage);
SpreadsheetApp.getUi().alert('Directory Listing was updated using: ' + target); }
}
if(!targetFound)
{
var created = fldr.createFile('CurrentDirectoryListing',timeStamp + GStorage);
if(created)
{
//SpreadsheetApp.getUi().alert( 'Directory Listing was stored here: ' + fldr.getName() + '/' + created.getName());
}
else
{
SpreadsheetApp.getUi().alert('Unknown Error: Directory List was not stored');
}
}
}
function loadDirlist()
{
var filename = 'CurrentDirectoryListing';
var fldr = DriveApp.getFolderById(folderID);
var file = fldr.getFilesByName(filename);
var targetFound = false;
while(file.hasNext())
{
var fi = file.next();
var target = fi.getName();
if(target == filename)
{
targetFound = true;
GStorage = fi.getBlob().getDataAsString();
//SpreadsheetApp.getUi().alert('Directory Listing was retreived from: ' + fldr.getName() + '/' + target);
}
}
return targetFound;
}
function clearDirlist()
{
var filename = 'CurrentDirectoryListing';
var fldr = DriveApp.getFolderById(folderID)
var file = fldr.getFilesByName(filename);
var targetFound = false;
while(file.hasNext())
{
var fi = file.next();
var target = fi.getName();
if(target == filename)
{
targetFound = true;
fldr.removeFile(fi);
//SpreadsheetApp.getUi().alert('File: ' + filename + ' was removed from: ' + fldr.getName() + '/' + target);
}
}
return targetFound;
}
This is utility.gs:
function dispStatus(title,html,width,height)
{
// Display a modeless dialog box with custom HtmlService content.
var title = typeof(title) !== 'undefined' ? title : 'No Title Provided';
var width = typeof(width) !== 'undefined' ? width : 250;
var height = typeof(height) !== 'undefined' ? height : 300;
var html = typeof(html) !== 'undefined' ? html : '<p>No html provided.</p>';
var htmlOutput = HtmlService
.createHtmlOutput(html)
.setWidth(width)
.setHeight(height);
SpreadsheetApp.getUi().showModelessDialog(htmlOutput, title);
}
As a followup, I think my mistake was in the getvalue command. My code that is now working (obviously having changed the relevant cell, and assuming the sheet name to be included in the name of the folder) is:
function SearchFolder() {
// Searches Google Drive for the folder for the Active Sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var searchTerm = ss.getSheetName();
var Folders = DriveApp.searchFolders("title contains '"+searchTerm.replace("'","\'")+"' and trashed = false and hidden = false");
var Folder = Folders.next();
sheet.getRange('K6').setFormula("=HYPERLINK(\""+Folder.getUrl()+"\",\""+searchTerm+"\")");
}

GmailApp.search doesn't filter the labels

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.