Creating multiple PDFs but not all creating properly - google-apps-script

I have made this script below to change a value of one cell and the PDF that page into a document. It works well but only the first 5 or 6 PDFs are creating properly and the rest seem to be 'luck of the draw'. Some are fine and others give me a HTML error message.
Here is my code:
function CreateClassPacks() {
SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
var foldersave=DriveApp.getFolderById('xxxxxxxxxxxxxxxxxxxxx');
var d= new Date();
var dateStamp = d.getDate()+"/"+d.getMonth()+"/"+d.getYear();
var request = {
"method": "GET",
"headers":{"Authorization": "Bearer "+ScriptApp.getOAuthToken()},
"muteHttpExceptions": true
};
var key='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
var fetch='https://docs.google.com/spreadsheets/d/'+key+'/export?format=pdf&size=A4&portrait=false'
var classCodeSheetNum = 0
var dataRange = SpreadsheetApp.getActiveSpreadsheet().getSheets()[classCodeSheetNum].getRange(2, 1, 10, 1);
var data = dataRange.getValues();
Logger.log(data)
var sheetNum = 1
for (var r=0; r<(data.length)-1; r++) {
for (i in data[0]) {
SpreadsheetApp.getActiveSpreadsheet().getSheets()[sheetNum].getRange('A2').setValue(data[r][i]);
var source = SpreadsheetApp.getActiveSpreadsheet();
var sheet = source.getSheets()[sheetNum];
var classCode = sheet.getRange("A2").getValue();
for(var w=0; w< sheetNum;w++)
{
sheet = source.getSheets()[w];
sheet.hideSheet();
}
var name = classCode + " " + dateStamp + ".pdf";
var pdf = UrlFetchApp.fetch(fetch, request);
pdf = pdf.getBlob().setName(name);
var file = foldersave.createFile(pdf)
for(var q=0; q< sheetNum;q++)
{
sheet = source.getSheets()[q];
sheet.showSheet();
}
}
}
}
This is what i get in place of the files that are not correctly created as PDFs:
<!DOCTYPE html><html lang="en"><head><meta name="description" content="Web word processing, presentations and spreadsheets"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0"><link rel="shortcut icon" href="//ssl.gstatic.com/docs/common/drive_favicon1.ico"><title>Too Many Requests</title><link href="//fonts.googleapis.com/css?family=Product+Sans" rel="stylesheet" type="text/css"><style>/* Copyright 2017 Google Inc. All Rights Reserved. */
.goog-inline-block{position:relative;display:-moz-inline-box;display:inline-block}* html .goog-inline-block{display:inline}*:first-child+html .goog-inline-block{display:inline}#drive-logo{margin:18px 0;position:absolute;white-space:nowrap}.docs-drivelogo-img{background-image:url('//ssl.gstatic.com/images/branding/googlelogo/1x/googlelogo_color_116x41dp.png');background-size:116px 41px;display:inline-block;height:41px;vertical-align:bottom;width:116px}.docs-drivelogo-text{color:#000;display:inline-block;opacity:0.54;text-decoration:none;font-family:'Product Sans',Arial,Helvetica,sans-serif;font-size:32px;text-rendering:optimizeLegibility;position:relative;top:-6px;left:-7px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#media (-webkit-min-device-pixel-ratio:1.5),(min-resolution:144dpi){.docs-drivelogo-img{background-image:url('//ssl.gstatic.com/images/branding/googlelogo/2x/googlelogo_color_116x41dp.png')}}</style><style type="text/css">body {background-color: #fff; font-family: Arial,sans-serif; font-size: 13px; margin: 0; padding: 0;}a, a:link, a:visited {color: #112ABB;}</style><style type="text/css">.errorMessage {font-size: 12pt; font-weight: bold; line-height: 150%;}</style></head><body><div id="outerContainer"><div id="innerContainer"><div style="position: absolute; top: -80px;"><div id="drive-logo"><span class="docs-drivelogo-img" title="Google logo"></span><span class="docs-drivelogo-text"> Drive</span></div></div>Wow, this file is really popular! It might be unavailable until the crowd clears. Try again.</div></div></body><style>#outerContainer {margin: auto; max-width: 750px;}#innerContainer {margin-bottom: 20px; margin-left: 40px; margin-right: 40px; margin-top: 80px; position: relative;}</style></html>
I would love it if anyone could help as this is killing me lol!

As the error says, it is because you are sending too many requests.
Try having a sleep of about 30ms or so in between your calls:
Utilities.sleep(30);
var file = foldersave.createFile(pdf);
This can slow down the requests and might work.

Related

Add fields to this html form who add/edit data in a spreadsheet

I found the scripts below on this post, and I tried to figure out how to add a new column on the sheet and make it work with the html form the same way as the two columns already existing
But without success...
If someone can take the time to explain to me how to do it, it would be very nice 🙏
The HTML /CSS side and basic JS are understandable for me but the rest stay hard to understand by myself
Here the sheet sample
Thanks !
CODE.GS
function doGet(request) {
return HtmlService.createTemplateFromFile('Form').evaluate();
}
/* #Include JavaScript and CSS Files */
function include(filename) {
return HtmlService.createHtmlOutputFromFile(filename)
.getContent();
}
/* Find ID*/
function getID(IDsearch){
var url = "https://docs.google.com/spreadsheets/d/1QESrQb4rYhmr0uc7q6ptvmdmMbo0Bxp_hZrvKaobdI8/edit#gid=0";
var ss = SpreadsheetApp.openByUrl(url);
var ws = ss.getSheetByName("Database");
/*set cells to plain text*/
var range = ws.getRange(1, 1, ws.getMaxRows(), ws.getMaxColumns());
range.setNumberFormat("#");
var data = ws.getRange(3, 1, ws.getLastRow(), 2).getValues();
var dataInput = data.map(function(r){return r[1];}); //ID column
var position = dataInput.indexOf(IDsearch); //index of the row where ID is
Logger.log(position);
var dataArray = ws.getRange(position+3, 1, 1, 2).getValues(); //array with data from searched ID
var clientsDataString = dataArray.toString();
var clientsDataArray = clientsDataString.split(',');
if(position > -1){
return clientsDataArray;
} else {
return position;
}
}
function processForm(formObject) {
var url = "https://docs.google.com/spreadsheets/d/1QESrQb4rYhmr0uc7q6ptvmdmMbo0Bxp_hZrvKaobdI8/edit#gid=0";
var ss = SpreadsheetApp.openByUrl(url);
var ws = ss.getSheetByName("Database");
var ranges = ws.getRange(4, 2, ws.getLastRow() - 3, 1).createTextFinder(formObject.ID).findAll();
if (ranges.length > 0) {
for (var i = 0; i < ranges.length; i++) {
ranges[i].offset(0, -1, 1, 2).setValues([[formObject.name, formObject.ID]]);
}
} else {
ws.appendRow([formObject.name, formObject.ID]);
}
}
JavaScript.html
<script>
function preventFormSubmit() {
var forms = document.querySelectorAll('form');
for (var i = 0; i < forms.length; i++) {
forms[i].addEventListener('submit', function(event) {
event.preventDefault();
});
}
}
window.addEventListener('load', preventFormSubmit);
function handleFormSubmit(formObject) {
google.script.run.processForm(formObject);
document.getElementById("myForm").reset();
}
/* Search for ID */
document.getElementById("btn-procurar").addEventListener("click", onSearch);
function onSearch() {
var IDsearch = document.getElementById("insertID").value;
google.script.run.withSuccessHandler(populateForm).getID(IDsearch);
}
function populateForm(clientsData) {
document.getElementById("name").value = clientsData[0];
document.getElementById("ID").value = clientsData[1];
}
</script>
form.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="/Contact-Form-Clean.css">
<link rel="stylesheet" href="/styles.css">
<?!= include('JavaScript'); ?>
<?!= include('form-css'); ?>
</head>
<body>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div class="register-photo" style="padding-top: 30px;">
<div class="form-container" style="width: 695px;">
<form id="myForm" onsubmit="handleFormSubmit(this)" method="post" style="width: 720px;padding: 10px;padding-right: 20px;padding-left: 25px;">
<input class="form-control" type="text" id="name" name="name" placeholder="Name" style="width: 300px;display: inline-block;margin-bottom: 20px;">
<input class="form-control" type="number" id="ID" name="ID" placeholder="ID" style="width: 165px;display: inline-block;" required="">
<button type="submit" id="btn-submeter" onclick="return confirm('Submit?')" class="btn btn-primary btn-block" style="width: 644px;margin-bottom: 35px;">Save data</button>
<script>
document.getElementById("myForm").addEventListener("submit", myFunction);
function myFunction() {
alert("Success");
}
</script>
<div id="output"></div>
<div style="margin-bottom: 15px;padding: 5px;background-color: rgba(255,255,255,0);padding-top: 0px;border-top: double;">
<h6 class="text-left" style="margin-top: 15px;display: inline-block;width: 519px;margin-right: 20px;margin-bottom: 10px;">Search/Fetch ID</h6>
<input class="form-control" type="text" id="insertID" name="insertID" placeholder="Insert ID" style="width: 155px;display: inline-block;">
<button class="btn btn-primary" id="btn-procurar" onclick="onSearch()" type="button" style="width: 450px;margin: 10px 0px 25px 0px;padding: 6px;margin-top: 0px;margin-bottom: 0px;margin-left: 29px;">Search by ID</button>
</div>
</form>
</div>
</div>
<script src="/js/jquery-3.4.1.min.js"></script>
<script src="/bootstrap/js/bootstrap.min.js"></script>
</body>
</html>
You want to put the values of "name", "ID", "address", "email" and "phone" using the HTML form.
In your updated shared Spreadsheet, 5 input tags are put.
You want to achieve this by modifying your script.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Modification points:
In your current script, 2 values are used like [formObject.name, formObject.ID].
For this, please modify to 5 values like [formObject.name, formObject.ID, formObject.address, formObject.email, formObject.phone].
modified script
When your script is modified, it becomes as follows.
From:
function processForm(formObject) {
var url = "https://docs.google.com/spreadsheets/d/1QESrQb4rYhmr0uc7q6ptvmdmMbo0Bxp_hZrvKaobdI8/edit#gid=0";
var ss = SpreadsheetApp.openByUrl(url);
var ws = ss.getSheetByName("Database");
var ranges = ws.getRange(4, 2, ws.getLastRow() - 3, 1).createTextFinder(formObject.ID).findAll();
if (ranges.length > 0) {
for (var i = 0; i < ranges.length; i++) {
ranges[i].offset(0, -1, 1, 2).setValues([[formObject.name, formObject.ID]]);
}
} else {
ws.appendRow([formObject.name, formObject.ID]);
}
}
To:
function processForm(formObject) {
var url = "https://docs.google.com/spreadsheets/d/1QESrQb4rYhmr0uc7q6ptvmdmMbo0Bxp_hZrvKaobdI8/edit#gid=0";
var ss = SpreadsheetApp.openByUrl(url);
var ws = ss.getSheetByName("Database");
var ranges = ws.getRange(4, 2, ws.getLastRow() - 3, 1).createTextFinder(formObject.ID).findAll();
var v = [formObject.name, formObject.ID, formObject.address, formObject.email, formObject.phone]; // Added
if (ranges.length > 0) {
for (var i = 0; i < ranges.length; i++) {
ranges[i].offset(0, -1, 1, v.length).setValues([v]); // Modified
}
} else {
ws.appendRow(v); // Modified
}
}
Note:
In your updated shared Spreadsheet, type="number" is used for the input tag of phone. In this case, for example, when the value is 01 33 33 33 33 33, an error occurs because 01 33 33 33 33 33 is not the number. If you want to show 01 33 33 33 33 33, please modify to type="string".

Upload / Naming folders to a destination folder on Google Drive using Google Apps Script

My skills in coding/scripting are limited, please bear with me on incorrect terminologies; I need people to input three values on the form, [Objectives, Goals, and Remarks] followed by uploading an attachment (s) to my Google Drive. The script that I found (attached below) is working but needs to be adjusted (edited) for my needs. Please, I need help with the following:
Right now, the files when uploaded, proceed to the default Google Drive folder. But I need them to be directed to a folder that I named, UPLOADS on my Google Drive.
Each uploaded file to create the same folder name at the destination, e.g., a .pdf FILE with the name "Fountain" when uploaded, to create a FOLDER with the name "Fountain" in the destination folder.
3.The upload progress bar is working fine, but I would prefer the page to refresh after each submission, so the information on the form disappears.
*The input values on the form [Objectives, Goals, and Remarks] are not being recorded anywhere, after each submission - they are not on Google sheet; Any suggestions to solve this are welcome.
Thank you.
<!DOCTYPE html>
<html>
<body>
<div id="formcontainer">
<label for="myForm">2020 Vision:</label>
<br><br>
<form id="myForm">
<label for="myForm">Information:</label>
<div>
<input type="text" name="objectives" placeholder=“Objectives:”>
</div>
<div>
<input type="text" name="goals" placeholder=“Goals:”>
</div>
<div>
<label for="fileText">Remarks:</label>
<TEXTAREA name="projectDescription"
placeholder="Describe your attachment(s) here:"
style ="width:400px; height:200px;"
></TEXTAREA>
</div>
<br>
<label for="attachType">Choose Attachment Type:</label>
<br>
<select name="attachType">
<option value="Pictures Only">Picture(s)</option>
<option value="Proposals Only">Documents</option>
<option value="Pictures & Proposals">All</option>
</select>
<br>
<label for="myFile">Upload Attachment(s):</label>
<br>
<input type="file" name="filename" id="myFile" multiple>
<input type="button" value="Submit" onclick="iteratorFileUpload()">
</form>
</div>
<div id="output"></div>
<div id="progressbar">
<div class="progress-label"></div>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<script>
var numUploads = {};
numUploads.done = 0;
numUploads.total = 0;
folderName;
// Upload the files into a folder in drive
// This is set to send them all to one folder (specificed in the .gs file)
function iteratorFileUpload() {
folderName = "Batch: "+new Date();
var allFiles = document.getElementById('myFile').files;
if (allFiles.length == 0) {
alert('No file selected!');
} else {
//Show Progress Bar
numUploads.total = allFiles.length;
$('#progressbar').progressbar({
value : false
});//.append("<div class='caption'>37%</div>");
$(".progress-label").html('Preparing files for upload');
// Send each file at a time
for (var i = 0; i < allFiles.length; i++) {
console.log(i);
sendFileToDrive(allFiles[i]);
}
}
}
function sendFileToDrive(file) {
var reader = new FileReader();
reader.onload = function (e) {
var content = reader.result;
console.log('Sending ' + file.name);
var currFolder = ‘UPLOADS’; // my desired destination folder
google.script.run.withSuccessHandler(updateProgressbar).uploadFileToDrive(content, file.name, folderName);
}
reader.readAsDataURL(file);
}
function updateProgressbar( idUpdate ){
console.log('Received: ' + idUpdate);
numUploads.done++;
var porc = Math.ceil((numUploads.done / numUploads.total)*100);
$("#progressbar").progressbar({value: porc });
$(".progress-label").text(numUploads.done +'/'+ numUploads.total);
if( numUploads.done == numUploads.total ){
//uploadsFinished();
numUploads.done = 0;
};
}
</script>
<script>
function fileUploaded(status) {
document.getElementById('myForm').style.display = 'none';
document.getElementById('output').innerHTML = status;
}
</script>
<style>
body {
max-width: 400px;
padding: 20px;
margin: auto;
}
input {
display: inline-block;
width: 100%;
padding: 5px 0px 5px 5px;
margin-bottom: 10px;
-webkit-box-sizing: border-box;
‌​ -moz-box-sizing: border-box;
box-sizing: border-box;
}
select {
margin: 5px 0px 15px 0px;
}
input[type="submit"] {
width: auto !important;
display: block !important;
}
input[type="file"] {
padding: 5px 0px 15px 0px !important;
}
#progressbar{
width: 100%;
text-align: center;
overflow: hidden;
position: relative;
vertical-align: middle;
}
.progress-label {
float: left;
margin-top: 5px;
font-weight: bold;
text-shadow: 1px 1px 0 #fff;
width: 100%;
height: 100%;
position: absolute;
vertical-align: middle;
}
</style>
</body>
function doGet() {
return HtmlService.createHtmlOutputFromFile('form')
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
function uploadFileToDrive(base64Data, fileName, folderName) {
try{
var splitBase = base64Data.split(','),
type = splitBase[0].split(';')[0].replace('data:','');
var byteCharacters = Utilities.base64Decode(splitBase[1]);
var ss = Utilities.newBlob(byteCharacters, type);
ss.setName(fileName);
var dropbox = folderName || "UPLOADS"; //my desired destination for uploads
var folder, folders = DriveApp.getFoldersByName(dropbox);
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = DriveApp.createFolder(dropbox);
}
var file = folder.createFile(ss);
return file.getName();
}catch(e){
return 'Error: ' + e.toString();
}
}
I made some little changes to your code:
HTML file
in your "sendFileToDrive" I passed the folder name (in this case "Uploads") to your "uploadFileToDrive" in your Apps Script.
function sendFileToDrive(file) {
var reader = new FileReader();
reader.onload = function (e) {
var content = reader.result;
console.log('Sending ' + file.name);
// set the file's name where you want to set your files
var folderName = "UPLOADS"; // my desired destination folder
google.script.run.withSuccessHandler(updateProgressbar).uploadFileToDrive(content, file.name, folderName);
}
reader.readAsDataURL(file);
}
To reset the values in your form after submitting it you can do it in the following way (Keep in mind this is vanilla JS, so there are several ways on the Internet to achieve this).
Write this line of code document.getElementById("myForm").reset(); in your "updateProgressbar"
function updateProgressbar( idUpdate ){
console.log('Received: ' + idUpdate);
numUploads.done++;
var porc = Math.ceil((numUploads.done / numUploads.total)*100);
$("#progressbar").progressbar({value: porc });
$(".progress-label").text(numUploads.done +'/'+ numUploads.total);
if( numUploads.done == numUploads.total ){
//uploadsFinished();
numUploads.done = 0;
// Reset the form's fields values
document.getElementById("myForm").reset();
};
}
Apps Script file
In your Apps Script code, I made these changes to create the folder from the file you just uploaded and then putting it in your "Uploads" folder. The problem you were having is you were trying to create a new folder in your if-else, but with the condition you had, you were not going to enter to the part of the code to create the folder.
function doGet() {
return HtmlService.createHtmlOutputFromFile('form')
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
function uploadFileToDrive(base64Data, fileName, folderName) {
try{
var splitBase = base64Data.split(','),
type = splitBase[0].split(';')[0].replace('data:','');
var byteCharacters = Utilities.base64Decode(splitBase[1]);
var ss = Utilities.newBlob(byteCharacters, type);
ss.setName(fileName);
// Get your Uploads folder
var folders = DriveApp.getFoldersByName(folderName);
// Regex for splitting the file's extention like .pdf
var patt1 = /\.[0-9a-z]+$/i;
if (folders.hasNext()) {
var folder = folders.next();
// Create a folder with the name of the file you just uploaded in your root path
// DriveApp.createFolder(fileName.split(patt1)[0]);
// Create the file in your Uploads folder and a folder
var file = folder.createFile(ss);
folder.createFolder(fileName.split(patt1)[0]);
return file.getName();
} else return null;
} catch(e){
return 'Error: ' + e.toString();
}
}
EDIT
Notice the class folder has a special method called createFolder(name), which will allow you to create a folder in the currently folder you are handling as I put it in my Updated code. Check the Updated Docs links for more info.
Docs
I used these docs to help you find the solution to your problem:
Class DriveApp
Web Apps
Class Folder - createFolder(name)

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 jump-to-todays-date onOpen for a spreadsheet with multiple tabs

I have a large team of people that access one spreadsheet. Each person has a tab with a column of dates (Column C), and they enter information received for each date. I'd like EACH sheet to jump to the current date upon opening their tab. The script below works, but only for the first tab/sheet, and must be manually run for all other tabs, which is not worth the trouble. How can I fix this so that it runs on every tab once it's opened?
function onOpen() {
var menu = [{name: "Jump to today's date", functionName: "jumpToDate"}];
SpreadsheetApp.getActiveSpreadsheet().addMenu("Custom", menu);
jumpToDate();
}
function jumpToDate() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var range = sheet.getRange("C:C");
var values = range.getValues();
var day = 24*3600*1000;
var today = parseInt((new Date().setHours(0,0,0,0))/day);
var ssdate;
for (var i=0; i<values.length; i++) {
try {
ssdate = values[i][0].getTime()/day;
}
catch(e) {
}
if (ssdate && Math.floor(ssdate) == today) {
sheet.setActiveRange(range.offset(i,0,1,1));
break;
}
}
}
Currently there's no event trigger selecting sheet in google-apps-script
But each tab has unique link. If each user has own tab, they might save the link to personal tad, and script will work when Spreadsheet is open, because of this line:
var sheet = ss.getActiveSheet();
You can't detect the sheet selection from script but you can change the way users are selecting the active sheet in a spreadsheet.
Using a sidebar it can be more user friendly than the usual selection mode, especially when you have many sheets.
The code below is a simplified version of a sidebar tool I use a lot. I removed all the unnecessary items (at least I hope so!) and integrated your code to jump to the current date.
It uses JQUERY because it's part of a much bigger script where I use it a lot but I admit it's not very useful in this simple script... I was just too lazy to remove it :)
The whole code is quite long but is quite simple to understand.
You can also start from a copy of this public copy.
code.gs
function onOpen() {
SpreadsheetApp.getUi()
.createMenu("Authorize")
.addItem('Authorize script', 'authorize')
.addToUi();
showSidebar();
}
function authorize(){
showSidebar();
}
function showSidebar() {
var ui = HtmlService.createTemplateFromFile('Sidebar')
.evaluate()
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setTitle('Page selection');
SpreadsheetApp.getUi().showSidebar(ui);
}
function createList(){
var ssProfs_envois = SpreadsheetApp.getActiveSpreadsheet();
var list = [];
var sh, sheetName;
for(var s=0;s<ssProfs_envois.getNumSheets();s++){
sh = ssProfs_envois.getSheets()[s];
sheetName = sh.getName();
list.push(sheetName);
}
var message = '';
var color;
message+= '<table style="border-collapse:collapse;font-family:arial,sans;font-size:9pt;" border = 1 >';
message+='<tr valign="top" cellpadding=5>'
for(var n=0;n<list.length;n++){
color="#000"
message+='<tr><td align="center">&nbsp'+(n+1)+'&nbsp</td><td>'+
'<input type="button" id="button'+n+'" value="'+list[n]+'" style="color:'+color+'; width:200px;font-size:10pt;white-space:normal;" onclick="selectSheet(\''+n+'\')"/></td></tr>';
}
message+='</table>';
return message;
}
function showSheetGS(sheetNumber){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[sheetNumber].activate();
jumpToDate(sheet);
}
function jumpToDate(sheet) {
var range = sheet.getRange("A1:A");
var values = range.getValues();
var day = 24*3600*1000;
var today = parseInt((new Date().setHours(0,0,0,0))/day);
var ssdate;
for (var i=0; i<values.length; i++) {
try {
ssdate = values[i][0].getTime()/day;
}
catch(e) {
}
if (ssdate && Math.floor(ssdate) == today) {
sheet.setActiveRange(range.offset(i,0,1,1));
break;
}
}
}
Sidebar.html
<?!= HtmlService.createHtmlOutputFromFile('Stylesheet').getContent(); ?>
<!-- Below is the HTML code that defines the sidebar element structure. -->
<div class="sidebar branding-below">
<!-- The div-table class is used to make a group of divs behave like a table. -->
<h3>Available tabs,<br><span style="color:#070"> Green typeface = </span>sheet already selected</h3>
<div id="sidebarList">
<br><br><br>...wait a moment,<br>list is being created.
<p>If nothing happens then please authorize the script using the menu.</p>
</div>
<!-- Enter sidebar bottom-branding below. -->
<div class="sidebar bottom">
<img alt="Add-on logo" class="logo" width="25"
src="http://insas.cluster006.ovh.net/serge/apps-script_2x.png">
<span class="gray branding-text">Sheet Select tool<br>&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp© SG 2017</span>
<?!= HtmlService.createHtmlOutputFromFile('SidebarJavaScript').getContent(); ?>
SidebarJavaScript.html
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<script>
/**
* Run initializations on sidebar load.
*/
populateEncodeLocal();
function populateEncodeLocal(){
//console.log('populateEncode');
google.script.run.withSuccessHandler(populateList).createList();
}
function selectSheet(sheetNumber){
console.log(sheetNumber);
google.script.run.withSuccessHandler(showSheet(sheetNumber)).showSheetGS(sheetNumber);
}
function showSheet(n){
console.log("showSheet"+n);
$('#button'+n).css('font-size','14pt').css('color','#070');
}
function populateList(data){
//console.log(data);
$('#sidebarList').html(data);
}
</script>
Stylesheet.html
<!-- This CSS package applies Google styling; it should always be included. -->
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css">
<style>
html, body {
max-width: 100%;
overflow-x: hidden;
}
#sidebarList {
width:270px;
height:600px;
overflow:auto;
}
input[type="button"] {
height: 23px;
width: 270px;
padding-left: 8px;
line-height: 24px;
}
label {
font-weight: bold;
}
.branding-below {
bottom: 3px;
top: 0px;
padding-left:0px;
}
.branding-text {
left: 7px;
position: relative;
top: 3px;
}
.logo {
vertical-align: middle;
}
.width-100 {
width: 100%;
box-sizing: border-box;
-webkit-box-sizing : border-box;‌
-moz-box-sizing : border-box;
}
#sidebar-value-block,
#dialog-elements {
background-color: #eee;
border-color: #eee;
border-width: 5px;
border-style: solid;
}
#sidebar-button-bar,
#dialog-button-bar {
margin-bottom: 10px;
}
.div-table{
display:table;
width:280px;
height:500px;
background-color:#eee;
border:1px solid #666666;*/
border-spacing:2px;
font-size:8,5pt;
}
.div-table-row{
display:table-row;
width:auto;
clear:both;
}
.div-table-td, .div-table-th {
display:table-cell;
width:auto;
background-color:rgb(230, 230, 230);
padding-left:4px;
padding-right:4px;
}
.div-table-th {
/*float:left;*/
font-weight: bold;
}
.div-table-td {
/*float:right;*/
}
div.ui-datepicker{
padding:0px;
font-size:90%;
width: 250px;
heigth: 90px;
}
th {
border-bottom: 0px solid #acacac;
font-weight: normal;
padding: 1px 1px 0;
text-align: left;
}
td {
border-bottom: 0px solid #ebebeb;
padding: 1px 0;
}
}
</style>
It's kind of possible, but slow.
The idea is call jumpToDate to all sheets, one by one, with trigger onOpen(). However, Google Sheets never renders all sheets at the same time when you open a single sheet, but only the one you opened and, it seems, the most frequently opened ones. When you click an "unpopular" sheet tab, your browser needs to render it, which took 500 milliseconds in my tests on a simple sheet. It may take more time in your larger spreadsheet.
Here is how I adapted your code to answer your question:
function jumpToDate() {
/* optional:*/ /*var ui = SpreadsheetApp.getUi();
var response = ui.alert('Today', 'To scroll to today, please click yes.', ui.ButtonSet.YES_NO);
if(response == "NO"){return}; */
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets(); // this now gets all the sheets
var numSheets = ss.getNumSheets(); // gets number of sheets
var day = 24*3600*1000;
var today = parseInt((new Date().setHours(0,0,0,0))/day);
var range;
var values;
var ssdate;
for (h = 0; h < numSheets; h++) {
range = sheet[h].getRange("C:C"); // note the [h]
ss.setActiveSheet(sheet[h]);
Utilities.sleep(500); // without this line, it runs much faster but some sheets don't load.
// I recommend that you play around and increase/decrease the 500 number
values = range.getValues();
for (var i=0; i<values.length; i++) {
try {
ssdate = values[i][0].getTime()/day;
}
catch(e) {
}
if (ssdate && Math.floor(ssdate) == today) {
sheet[h].setActiveRange(range.offset(i,0,1,1)); // note the [h]
break;
}
}
}
}
It's likely that this option is too slow for your needs, so I would recommend that we approach the problem differently. Does the page really need to show all those rows that we don't edit everyday? Do we even read them often? If not, we could hide the rows we that don't use as much. It can be done using the hideRows(rowIndex, numRows) method.
Creating function hideOld() that hides all rows before today
Customizing hideOld() to hide rows before (today - 10 days)
Adding a trigger that runs hideOld() at 3:00 AM daily.

Issuing JSON resquest for openweather in Raspberry Pi Coder

I'm learning HTML, CSS, and Javascript with Coder on the Raspberry pi. Currently, I'm trying to make a simple page that displays time, date, and the current weather. Something is going wrong with the $.getJSON call in the getWeather() function.
Typing the URL passed to $.getJSON works correctly (i.e., a page is loaded with all the information in JSON), but the "Got Weather" string is never displayed. I've also tried using the AJAX call requesting JSON or JSONP data type. Neither of those methods worked either. What am I missing?
$(document).ready( function() {
//This code will run after your page loads
function displayTime() {
var current_time = new Date();
var hours = current_time.getHours();
var minutes = current_time.getMinutes();
var seconds = current_time.getSeconds();
var meridiem = "AM"; // default is AM
var day = current_time.getDay();
if(seconds < 10) {
seconds = "0" + seconds;
}
if(minutes < 10) {
minutes = "0" + minutes;
}
// Set the meridiem for a 12hr clock
if(hours > 12) {
hours -= 12;
meridiem = "PM"
} else {
meridiem = "AM"
}
var clock_div = document.getElementById('clock');
clock_div.innerText = hours + ":" + minutes + ":" + seconds + " " + meridiem;
// Depending on the value of 'day', set the corresponding string
var day_div = document.getElementById('day');
var weekday = new Array(7);
weekday[0] = "Sunday";
weekday[1] = "Monday";
weekday[2] = "Tuesday";
weekday[3] = "Wednesday";
weekday[4] = "Thursday";
weekday[5] = "Friday";
weekday[6] = "Saturday";
var today = weekday[current_time.getDay()];
day_div.innerText = today;
// Get the date information
var date = current_time.getDate();
// Get the year
var year = current_time.getFullYear();
// Get the month and set the string
var month = new Array(12);
month[0] = "January";
month[1] = "February";
month[2] = "March";
month[3] = "April";
month[4] = "May";
month[5] = "June";
month[6] = "July";
month[7] = "August";
month[8] = "September";
month[9] = "October";
month[10] = "November";
month[11] = "December";
var this_month = month[current_time.getMonth()];
// set the string
var date_div = document.getElementById('date');
date_div.innerText = this_month + " " + date + " " + year;
}
function getWeather() {
var api_key = REMOVED; // API key for open weather
var weather_api = "http://api.openweathermap.org/data/2.5/weather?lat=40.115&lon=-88.27&units=imperial&appid=" + api_key;
var weather_div = document.getElementById('weather');
$.getJSON(weather_api).then(function(result){
//alert("City: "+result.city.name);
//alert("Weather: "+ result.list[0].weather[0].description);
weather_div.innerText = "Got Weather";
});
//weather_div.innerText = "Got Weather";
}
// This runs the displayTime function the first time
displayTime();
getWeather();
// This makes the clock "tick" repeatedly by calling it every 1000 ms
setInterval(displayTime, 1000);
setInterval(getWeather, 2000);
});
HTML:
<!DOCTYPE html>
<html>
<head>
<title>Coder</title>
<meta charset="utf-8">
<!-- Standard Coder Includes -->
<script>
var appname = "{{app_name}}"; //app name (id) of this app
var appurl = "{{&app_url}}";
var staticurl = "{{&static_url}}"; //base path to your static files /static/apps/yourapp
</script>
<link href="/static/apps/coderlib/css/index.css" media="screen" rel="stylesheet" type="text/css"/>
<script src="/static/common/js/jquery.min.js"></script>
<script src="/static/common/ace-min/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="/static/apps/coderlib/js/index.js"></script>
<script>
Coder.addBasicNav();
</script>
<!-- extra inludes to get weather from OpenWeather -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-1.11.0.min.js"><\/script>')</script>
<script src="js/vendor/jquery-ui.min.js"></script>
<!-- End Coder Includes -->
<!-- This app's includes -->
<link href="{{&static_url}}/css/index.css" media="screen" rel="stylesheet" type="text/css"/>
<script src="{{&static_url}}/js/index.js"></script>
<!-- End apps includes -->
</head>
<body>
<div id='day'></div><br>
<div id='date'></div>
<div id='clock'></div>
<div id='weather'></div>
</body>
</html>
CSS:
body {
background-color: black;
}
#day {
height:100px;
width: 300px;
margin-left: 10px;
padding-top: 50px;
position: fixed;
top: 0px; left: 20px;
font-family: courier, monospace;
text-align: center;
color: white;
font-size: 30px;
font-weight: bold;
}
#date {
height:100px;
width: 300px;
margin-left: 10px;
padding-top: 50px;
position: fixed;
top: 50px; left: 20px;
font-family: courier, monospace;
text-align: center;
color: white;
font-size: 20px;
}
#clock {
height:100px;
width: 300px;
margin-left: 10px;
padding-top: 50px;
position: fixed;
top: 100px; left: 20px;
font-family: courier, monospace;
text-align: center;
color: white;
font-size: 20px;
}
I found the issue. Opening up the developer console showed an error along the lines of "Blocked loading mixed active content...". It seems I need to better familiarize myself with the developer tools.
Apps built within Coder are accessed over HTTPS. However, the call to openweather is over HTTP. The openweather API permits HTTPS calls only if you have a pro (paid) subscription. Fortunately, https://forecast.io allows a set number of free calls per day and uses HTTPS.