I have a form in modal dialogue box, once the form is submitted I have to update the sidebar with the form data. How can it be achieved? I have tried by polling the form data at some specific time interval. Is there any other work around?
Communicating between Dialog and Sidebar
Well here's an example of something that's reasonably close to what you are talking about. It's something I made up just to learn how to deal with the PropertiesService. It's not complete but some of it works and you can pass information from the dialog to sidebar via the PropertiesService storage. There is a limit to how much you can store there though. I don't the exact number but I know 26KB is too much.
So anyway once it's running you can use one of the Sender Text Boxes to write a message and press the sender button and the message will go to the PropertiesService and and then back to the Dialog or Sidebar which ever one your sending with via the onSuccessHandler and it will be written in the conversation text box. And if you press refresh on the other dialog or sidebar then it's conversation will be updated as well.
I'm guessing you might be able to find a way to perform the refresh automatically.
Anyway this example covers a lot of the same ground that you might want to cover. Of course realize that I'm not a Google Guru so don't assume that the way I do things is the best way or even a good way. But it does work.
code.gs
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('My Handler Tools')
.addItem('Show MyDialog1','MyDialog1')
.addSeparator()
.addItem('Show MyDialog2','MyDialog2')
.addSeparator()
.addItem('Show SideBar3','SideBar3')
.addSeparator()
.addItem('Log Settings','logSettings')
.addItem('Delete Conversation','delConversation')
.addToUi();
};
function delConversation()
{
PropertiesService.getDocumentProperties().deleteAllProperties();
}
function savConversation(conversation)
{
PropertiesService.getDocumentProperties().setProperties(conversation);
return(conversation);
}
function getConversation()
{
var conversation = PropertiesService.getDocumentProperties().getProperties();
if(typeof(conversation.active) == 'undefined')
{
conversation = {'thread': '***** Start a new thread *****', 'active': true};
}
return conversation;
}
function MyDialog1()
{
var ui = HtmlService.createHtmlOutputFromFile('ModeLessDialog')
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setWidth(420)
.setHeight(600);
SpreadsheetApp.getUi().showModelessDialog(ui,'MyDialog1');
}
function MyDialog2()
{
var msg = '<html><head><link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">\
<style>\
#my_block{border:5px solid green;padding:10px 10px 10px 0px;}\
</style></head><body><div id="my_block">\
<input type="button" value="Button1" name="Button1" id="My_Button_1" class="My_Tools" />\
<br /><div style="margin-bottom:5px;"><input type="text" value="This is text" id="My_Text_1" class="My_Tools" name="Text1" />\
<br /><input type="button" value="Button2" name="Button2" id="My_Button_2" class="My_Tools" />\
<br /><input type="text" value="This is text" id="My_Text_2" class="My_Tools" name="Text2" />\
<br /><input type="button" value="Exit" onClick="google.script.host.close();" />\
</div></body></html>';
var title = 'MyDialog2';
dispStatus(title,msg);
}
function SideBar3()
{
var ui = HtmlService.createHtmlOutputFromFile('ModeLessDialog').setTitle('Handler Communications');
SpreadsheetApp.getUi().showSidebar(ui);
}
function logSettings(show)
{
var show = typeof(show) !== 'undefined' ? show : true;
var settings = PropertiesService.getDocumentProperties().getProperties();
var html = '<html><head><link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">\
<style>.branding-below{bottom:54px;top:0;}\
.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;}\
label{font-weight:bold;}\
#creator-options,#respondent-options{background-color:#eee;border-color:#eee;border-width:5px;border-style:solid;display:none;}\
#creator-email,#respondent-email,#button-bar,#submit-subject{margin-bottom:10px;}\
#response-step{display:inline;}</style></head>\
<body><div class="sidebar branding-below"><form><h3>Conversation Settings</h3><div class="block" style="border:2px solid black;padding:10px 5px 10px 5px;">';
Logger.clear();
for(var key in settings)
{
html += '<br />' + key + ' = ' + settings[key];
Logger.log(key + ' = ' + settings[key]);
}
html += '<br /><div class="block blockgroup"><input type="button" class="action" value="Exit" onclick="google.script.host.close();" /></div>';
html += '</div></div></form></body></html>';
if(show)dispStatus('Document Properties',html,400,400);
}
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);
}
ModeLessDialog.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">
<style>
#my_block{border:2px solid black;background-color:rgba(0,150,255,0.2);padding:10px 10px 10px 10px;}
#conv_block{border: 1px solid black;padding:10px 10px 10px 10px;}
.bttn_block{padding:5px 0px 10px 0px;}
.sndr_block {border:1px solid rgba(0,150,0,0.5);background-color:rgba(150,150,0,0.2);margin-bottom:2px;}
</style>
</head>
<body>
<form>
<div id="my_block" class="block form-group">
<div class="sndr_block">
<strong>Sender 1</strong>
<br /><input type="text" size="30"value="" id="sender1msg" class="action" />
<br /><div class="bttn_block"><input type="button" value="Send" name="Sender1" id="sender1" class="action" /></div>
</div>
<div class="sndr_block">
<strong>Sender 2</strong>
<br /><input type="text" size="30" value="" id="sender2msg" class="action" />
<br /><div class="bttn_block"><input type="button" value="Send" name="Sender2" id="sender2" class="action" /></div>
</div>
<div id="conv_block">
<strong>Conversation</strong>
<br /><textarea id="conversation" rows="10" cols="35"></textarea>
<br /><input type="button" value="Save" name="Save" id="save-msg" class="action" />
<input type="button" value="Delete" name="Delete" id="del-msg" class="action" />
<input type="button" class="action" id="disp-log-setting" value="Settings" onClick="google.script.run.logSettings();" />
<input type="button" value="Refresh" class="action" id="refresh" />
</div>
<div id="btn-bar">
<br /><input type="button" value="Exit" onClick="google.script.host.close();" class="green" />
</div>
</div>
</form>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js">
</script>
<script>
$(function() {
$('#sender1').click(sendMessage1);
$('#sender2').click(sendMessage2);
$('#del-msg').click(deleteConversation);
$('#save-msg').click(saveConversation);
$('#refresh').click(refreshConversation);
google.script.run
.withSuccessHandler(updateConversation)
.withFailureHandler(showStatus)
.getConversation();
});
function sendMessage1()
{
var message = $('#conversation').val() + '\nSender1:\n' + $('#sender1msg').val();
var newconversation = {'thread': message, 'active': true};
$('#sender1msg').val('');
$('#conversation').css("background-color", "#ffe866");
google.script.run
.withSuccessHandler(updateConversation)
.withFailureHandler(showStatus)
.savConversation(newconversation);
}
function sendMessage2()
{
var message = $('#conversation').val() + '\nSender2:\n' + $('#sender2msg').val();
var newconversation = {'thread': message, 'active': true};
$('#sender2msg').val('');
$('#conversation').css("background-color", "#ffe866");
google.script.run
.withSuccessHandler(updateConversation)
.withFailureHandler(showStatus)
.savConversation(newconversation);
}
function deleteConversation()
{
var conversation = {'thread': '***** Start a new thread *****', 'active': true};
$('#conversation').css("background-color", "#ffe866");
google.script.run
.withSuccessHandler(updateConversation)
.withFailureHandler(showStatus)
.savConversation(conversation);
}
function saveConversation()
{
var message = $('#conversation').val();
var newconversation = {'thread': message, 'active': true};
$('#conversation').css("background-color", "#ffe866");
google.script.run
.withSuccessHandler(updateConversation)
.withFailureHandler(showStatus)
.savConversation(newconversation);
}
function updateConversation(conversation)
{
$('#conversation').val(conversation.thread);
$('#conversation').css("background-color", "white");
}
function refreshConversation()
{
$('#conversation').css("background-color", "#ffe866");
google.script.run
.withSuccessHandler(updateConversation)
.withFailureHandler(showStatus)
.getConversation();
}
function showStatus()
{
dispStatus('showStatus','This is status');
$('#conversation').css("background-color", "#ffb3b3");
}
console.log('ModeLessDialogJavaScript');
</script>
</body>
</html>
Related
I would like to create a script to receive expense receipts in a Google drive, and log details provided by a Google Form (date, vendor, amount, and picture of the receipt...)
I've tried to replicate the script and html from How do I rename files uploaded to an apps script web app form?, and end up with error 400 with no details...
Also tried to merge Amit Agarwal's script
https://www.labnol.org/internet/receive-files-in-google-drive/19697/
with https://github.com/dwyl/learn-to-send-email-via-google-script-html-no-server
Second example logs entries into google sheets, but also sends email : Perfect! Amit's example allows to create named folders and I can rename the file with some additionnal code, love it!
But in my try to merge both, I end up with two buttons at the bottom of the form... one sends the rows, the other sends the file! :D
Here's my actual script.gs
// if you want to store your email server-side (hidden), uncomment the next line
//var TO_ADDRESS = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// spit out all the keys/values from the form in HTML for email
// uses an array of keys if provided or the object to determine field order
function doGet(e) {
return HtmlService.createHtmlOutputFromFile('index.html').setTitle("envoyez vos pièces jointes");
}
// this is from Amit Agarwal's example
function uploadFileToGoogleDrive(data, file, prenom, nom) {
try {
var dropbox = "Justificatifs reçus";
var folder, folders = DriveApp.getFoldersByName(dropbox);
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = DriveApp.createFolder(dropbox);
}
var contentType = data.substring(5,data.indexOf(';')),
bytes = Utilities.base64Decode(data.substr(data.indexOf('base64,')+7)),
blob = Utilities.newBlob(bytes, contentType, file),
file = folder.createFolder([prenom, nom].join(" ")).createFile(blob);
//this is an addition i've made to rename the files upon submission, used to work in previous tries, but now gives "null null.pdf'
//var newFileName = [prenom +'_'+ nom +".pdf"];
//file.setName(newFileName);
return "OK";
} catch (f) {
return f.toString();
}
}
// Here stops Amit Agarwal's script, below is the following of
//https://github.com/dwyl/learn-to-send-email-via-google-script-html-no-server
function formatMailBody(obj, order) {
var result = "";
if (!order) {
order = Object.keys(obj);
}
// loop over all keys in the ordered form data
for (var idx in order) {
var key = order[idx];
result += "<h4 style='text-transform: capitalize; margin-bottom: 0'>" + key + "</h4><div>" + sanitizeInput(obj[key]) + "</div>";
// for every key, concatenate an `<h4 />`/`<div />` pairing of the key name and its value,
// and append it to the `result` string created at the start.
}
return result; // once the looping is done, `result` will be one long string to put in the email body
}
// sanitize content from the user - trust no one
// ref: https://developers.google.com/apps-script/reference/html/html-output#appendUntrusted(String)
function sanitizeInput(rawInput) {
var placeholder = HtmlService.createHtmlOutput(" ");
placeholder.appendUntrusted(rawInput);
return placeholder.getContent();
}
function doPost(e) {
try {
Logger.log(e); // the Google Script version of console.log see: Class Logger
record_data(e);
// shorter name for form data
var mailData = e.parameters;
// names and order of form elements (if set)
var orderParameter = e.parameters.formDataNameOrder;
var dataOrder;
if (orderParameter) {
dataOrder = JSON.parse(orderParameter);
}
// determine recepient of the email
// if you have your email uncommented above, it uses that `TO_ADDRESS`
// otherwise, it defaults to the email provided by the form's data attribute
var sendEmailTo = (typeof TO_ADDRESS !== "undefined") ? TO_ADDRESS : mailData.formGoogleSendEmail;
// send email if to address is set
if (sendEmailTo) {
MailApp.sendEmail({
to: String(sendEmailTo),
subject: "Contact form submitted",
// replyTo: String(mailData.email), // This is optional and reliant on your form actually collecting a field named `email`
htmlBody: formatMailBody(mailData, dataOrder)
});
}
return ContentService // return json success results
.createTextOutput(
JSON.stringify({"result":"success",
"data": JSON.stringify(e.parameters) }))
.setMimeType(ContentService.MimeType.JSON);
} catch(error) { // if error return this
Logger.log(error);
return ContentService
.createTextOutput(JSON.stringify({"result":"error", "error": error}))
.setMimeType(ContentService.MimeType.JSON);
}
}
/**
* record_data inserts the data received from the html form submission
* e is the data received from the POST
*/
function record_data(e) {
var lock = LockService.getDocumentLock();
lock.waitLock(30000); // hold off up to 30 sec to avoid concurrent writing
try {
Logger.log(JSON.stringify(e)); // log the POST data in case we need to debug it
// select the 'responses' sheet by default
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheetName = e.parameters.formGoogleSheetName || "responses";
var sheet = doc.getSheetByName(sheetName);
var oldHeader = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
var newHeader = oldHeader.slice();
var fieldsFromForm = getDataColumns(e.parameters);
var row = [new Date()]; // first element in the row should always be a timestamp
// loop through the header columns
for (var i = 1; i < oldHeader.length; i++) { // start at 1 to avoid Timestamp column
var field = oldHeader[i];
var output = getFieldFromData(field, e.parameters);
row.push(output);
// mark as stored by removing from form fields
var formIndex = fieldsFromForm.indexOf(field);
if (formIndex > -1) {
fieldsFromForm.splice(formIndex, 1);
}
}
// set any new fields in our form
for (var i = 0; i < fieldsFromForm.length; i++) {
var field = fieldsFromForm[i];
var output = getFieldFromData(field, e.parameters);
row.push(output);
newHeader.push(field);
}
// more efficient to set values as [][] array than individually
var nextRow = sheet.getLastRow() + 1; // get next row
sheet.getRange(nextRow, 1, 1, row.length).setValues([row]);
// update header row with any new data
if (newHeader.length > oldHeader.length) {
sheet.getRange(1, 1, 1, newHeader.length).setValues([newHeader]);
}
}
catch(error) {
Logger.log(error);
}
finally {
lock.releaseLock();
return;
}
}
function getDataColumns(data) {
return Object.keys(data).filter(function(column) {
return !(column === 'formDataNameOrder' || column === 'formGoogleSheetName' || column === 'formGoogleSendEmail' || column === 'honeypot');
});
}
function getFieldFromData(field, data) {
var values = data[field] || '';
var output = values.join ? values.join(', ') : values;
return output;
}
And here's the index.html
<!DOCTYPE html>
<html>
<head>
<base target="_blank">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Envoyez vos justificatifs</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.5/css/materialize.min.css">
<style>
.disclaimer{width: 480px; color:#646464;margin:20px auto;padding:0 16px;text-align:center;font:400 12px Roboto,Helvetica,Arial,sans-serif}.disclaimer a{color:#009688}#credit{display:none}
</style>
</head>
<!-- START HERE -->
<link rel="stylesheet" href="https://unpkg.com/purecss#1.0.0/build/pure-min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
<!-- Style The Contact Form How Ever You Prefer -->
<link rel="stylesheet" href="style.css">
<form class="gform pure-form pure-form-stacked" method="POST" data-email="example#email.net"
action="https://script.google.com/macros/s/AKfycbzSlohhLp27NcnG8lBt13GUm4PblUMTL9uU1CTgcOBohz1iH0k/exec"
id="form" novalidate="novalidate" style="max-width: 480px;margin: 40px auto;">
<div id="forminner">
<div class="row">
<div class="col s12">
<h5 class="center-align teal-text">Envoyez vos justificatifs</h5>
<p class="disclaimer">This File Upload Form (tutorial) is powered by Google Scripts</p>
</div>
</div>
<fieldset class="pure-group">
<legend><H5>Vous êtes</H5></legend>
<input id="radio-group--madame" type="radio" name="radio-group" value="madame"> <label for="radio-group--madame">Madame</label>
<input id="radio-group--monsieur" type="radio" name="radio-group" value="monsieur"> <label for="radio-group--monsieur">Monsieur</label>
</fieldset>
<div class="form-elements">
<fieldset class="pure-group">
<label for="firstname">Votre prénom</label>
<input id="firstname" name="Prénom" placeholder="indiquez votre prénom" />
</fieldset>
<div class="form-elements">
<fieldset class="pure-group">
<label for="name">Votre nom</label>
<input id="name" name="Nom" placeholder="indiquez votre nom" />
</fieldset>
<fieldset class="pure-group">
<label for="email"><em>Votre</em> Adresse email</label>
<input id="email" name="Votre adresse email" type="email" value=""
required placeholder="Alice#paydesmerv.... ou votre vrai adresse pour recevoir la confirmation"/>
</fieldset>
<fieldset class="pure-group">
<legend><H5>Votre dépense</H5></legend>
<label for="date">Date</label>
<input id="date" type="date" name="date de la dépense" value="">
</fieldset>
<fieldset class="pure-group">
<label for="time">Heure de la dépense</label>
<input id="time" type="time" name="Heure de la dépense" value="">
</fieldset>
<fieldset class="pure-group">
<label for="menu">Type de dépense</label>
<select id="menu" name="Type de dépense">
<option selected="">Je ne sais pas quel type de dépense choisir</option>
<option>Carburant (essence, diesel, gasoil)</option>
<option>Location de matériel (voiture, informatique, photocopieur)</option>
<option>Voyages et déplacements (train, transports, taxi, VTC, péages, parking, avion)</option>
<option>Frais postaux (La Poste, timbres, colis, lettre recommandée)</option>
<option>Frais de mission (repas, restaurants)</option>
<option>Frais de missions (logement, hôtel)</option>
<option>Divers</option>
</select>
</fieldset>
<fieldset class="pure-group">
<label for="number">Montant de la dépense en €</label>
<input id="number" type="number" name="Montant de la dépense" min="0" step="0.01" value="0.00">
</fieldset>
<fieldset class="pure-group">
<label for="message">Message: </label>
<textarea id="message" name="Message Facultatif" rows="10"
placeholder="Vous pouvez apporter des précisions sur la dépense ici..."></textarea>
</fieldset>
<fieldset class="pure-group honeypot-field">
<label for="honeypot">To help avoid spam, utilize a Honeypot technique with a hidden text field; must be empty to submit the form! Otherwise, we assume the user is a spam bot.</label>
<input id="honeypot" type="text" name="honeypot" value="" />
</fieldset>
<legend><H5>Ajoutez le justificatif</H5></legend>
<p> Pas de remboursement possible sans justificatif</p>
<!-- Here is the issue: code below sends the file to my drive but no new row is added to the spreadsheet. -->
<div class="row">
<div class="file-field input-field col s12">
<div class="btn">
<span>Fichier</span>
<input id="files" type="file" name="Fichier reçu" multiple>
</div>
<div class="file-path-wrapper">
<input class="file-path validate" type="text" placeholder="choisissez un fichier sur votre ordinateur">
</div>
</div>
</div>
<!-- code below creates new row with filename but does not upload the file to drive...
exemple from : https://www.w3schools.com/tags/tryit.asp?filename=tryhtml5_input_type_file -->
<form action="/action_page.php">
Select files: <input type="file" name="Fichier reçu"><br><br>
<input type="submit">
</form>
<button class="waves-effect waves-light btn submit-btn" type="submit" onclick="submitForm(); return false;">Submit</button>
</div>
<!-- Customise the Thankyou Message People See when they submit the form: -->
<div class="thankyou_message" style="display:none;">
<h2><em>Thanks</em> for contacting us!
We will get back to you soon!</h2>
</div>
</form>
<!-- Submit the Form to Google Using "AJAX" -->
<script data-cfasync="false" src="form-submission-handler.js"></script>
<!-- END -->
<div class="row">
<div class="input-field col s12" id = "progress">
</div>
</div>
<div id="success" style="display:none">
<h5 class="left-align teal-text">File Uploaded</h5>
<p>Your file has been successfully uploaded.</p>
<p>The pro version (see demo form) includes a visual drag-n-drop form builder, CAPTCHAs, the form responses are saved in a Google Spreadsheet and respondents can upload multiple files of any size.</p>
<p class="center-align"><a class="btn btn-large" href="https://gum.co/GA14?wanted=true" target="_blank">Upgrade to Pro</a></p>
</div>
</form>
<div class="fixed-action-btn horizontal" style="bottom: 45px; right: 24px;">
<a class="btn-floating btn-large red">
<i class="large material-icons">menu</i>
</a>
<ul>
<li><a class="btn-floating red" href="shorturl" target="_blank" title="Buy License - File Upload Form"><i class="material-icons">monetization_on</i></a></li>
<li><a class="btn-floating blue" href="shorturl" target="_blank" title="Video Tutorial"><i class="material-icons">video_library</i></a></li>
<li><a class="btn-floating green" href="http://www.labnol.org/internet/file-upload-google-forms/29170/" target="_blank" title="How to Create File Upload Forms"><i class="material-icons">help</i></a></li>
</ul>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.5/js/materialize.min.js"></script>
<script src="https://gumroad.com/js/gumroad.js"></script>
<script>
var file,
reader = new FileReader();
reader.onloadend = function(e) {
if (e.target.error != null) {
showError("File " + file.name + " could not be read.");
return;
} else {
google.script.run
.withSuccessHandler(showSuccess)
.uploadFileToGoogleDrive(e.target.result, file.name, $('input#nom').val(), $('input#prenom').val());
}
};
function showSuccess(e) {
if (e === "OK") {
$('#forminner').hide();
$('#success').show();
} else {
showError(e);
}
}
function submitForm() {
var files = $('#files')[0].files;
if (files.length === 0) {
showError("Choisissez un fichier a télécharger");
return;
}
file = files[0];
if (file.size > 1024 * 1024 * 5) {
showError("The file size should be < 5 MB. Please <a href='http://www.labnol.org/internet/file-upload-google-forms/29170/' target='_blank'>upgrade to premium</a> for receiving larger files in Google Drive");
return;
}
showMessage("Téléchargement du fichier");
reader.readAsDataURL(file);
}
function showError(e) {
$('#progress').addClass('red-text').html(e);
}
function showMessage(e) {
$('#progress').removeClass('red-text').html(e);
}
</script>
So, I'd like to have a unique form to send emails, log into sheets and save file in drive.
Additionally, that would be perfect is the file link could appear inside sheets and if the uploaded file could also be sent as attachment with the email.
Thanks a lot for proof reading my codes and putting me on the right direction!
Collecting Receipt Information
This function collects Date,Vendor,Amount and Uploads an image. It runs as both a dialog and/or a webapp. The spreadsheet displays the images url which allows you to hover over in order to get a link to the image which can be view in a default viewer.
This is the form:
Here's the code:
Code.gs:
function onOpen() {
SpreadsheetApp.getUi().createMenu('Receipt Collection')
.addItem('Get Receipt', 'showAsDialog')
.addToUi();
}
function uploadTheForm(theForm) {
Logger.log(JSON.stringify(theForm));
var rObj={};
rObj['vendor']=theForm.vendor;
rObj['amount']=theForm.amount;
rObj['date']=theForm.date;
rObj['notes']=theForm.notes;
var fileBlob=Utilities.newBlob(theForm.bytes, theForm.mimeType, theForm.filename);
var fldr = DriveApp.getFolderById(receiptImageFolderId);
rObj['file']=fldr.createFile(fileBlob);
rObj['filetype']=fileBlob.getContentType();
Logger.log(JSON.stringify(rObj));
var cObj=formatFileName(rObj);
Logger.log(JSON.stringify(cObj));
var ss=SpreadsheetApp.openById(SSID);
ss.getSheetByName('Receipt Information').appendRow([cObj.date,cObj.vendor,cObj.amount,cObj.notes,cObj.file.getUrl()]);
var html=Utilities.formatString('<br />FileName: %s',cObj.file.getName());
return html;
}
function formatFileName(rObj) {
if(rObj) {
Logger.log(JSON.stringify(rObj));
var mA=rObj.date.split('-');
var name=Utilities.formatString('%s_%s_%s.%s',Utilities.formatDate(new Date(mA[0],mA[1]-1,mA[2]),Session.getScriptTimeZone(),"yyyyMMdd"),rObj.vendor,rObj.amount,rObj.filetype.split('/')[1]);
rObj.file.setName(name);
}else{
throw('Invalid or No File in formatFileName() upload.gs');
}
return rObj;
}
function doGet() {
var output=HtmlService.createHtmlOutputFromFile('receipts').setTitle('Receipt Information');
return output.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL).addMetaTag('viewport', 'width=360, initial-scale=1');
}
function showAsDialog() {
var ui=HtmlService.createHtmlOutputFromFile('receipts');
SpreadsheetApp.getUi().showModelessDialog(ui, 'Receipts')
}
receipts.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
$(function(){
google.script.run
.withSuccessHandler(function(rObj){
$('#dt').val(rObj.date);
//$('#vndr').val(rObj.vendor);
//$('#amt').val(rObj.amount);
//$('#notes').val(rObj.notes);
})
.initForm();
});
function fileUploadJs(frmData) {
var amt=$('#amt').val();
var vndr=$('#vndr').val();
var img=$('#img').val();
if(!amt){
window.alert('No amount provided');
$('#amt').focus();
return;
}
if(!vndr) {
window.alert('No vendor provided');
$('#vndr').focus();
return;
}
if(!img) {
window.alert('No image chosen');
$('#img').focus();
}
document.getElementById('status').style.display ='inline';
const file = frmData.receipt.files[0];
const fr = new FileReader();
fr.onload = function(e) {
const obj = {vendor:frmData.elements.vendor.value, date:frmData.elements.date.value, amount: frmData.elements.amount.value, notes: frmData.elements.notes.value, filename: file.name, mimeType:file.type, bytes:[...new Int8Array(e.target.result)]};
google.script.run
.withSuccessHandler(function(hl){
document.getElementById('status').innerHTML=hl;
})
.uploadTheForm(obj);
};
fr.readAsArrayBuffer(file);
}
console.log('My Code');
</script>
<style>
input,textarea{margin:5px 5px 5px 0;}
</style>
</head>
<body>
<h3 id="main-heading">Receipt Information</h3>
<div id="formDiv">
<form id="myForm">
<br /><input type="date" name="date" id="dt"/>
<br /><input type="number" name="amount" placeholder="Amount" id="amt" />
<br /><input type="text" name="vendor" placeholder="Vendor" id="vndr"/>
<br /><textarea name="notes" cols="40" rows="2" placeholder="NOTES" id="notes"></textarea>
<br/>Receipt Image
<br /><input type="file" name="receipt" id="img" />
<br /><input type="button" value="Submit" onclick="fileUploadJs(this.parentNode)" />
</form>
</div>
<div id="status" style="display: none">
<!-- div will be filled with innerHTML after form submission. -->
Uploading. Please wait...
</div>
</body>
</html>
global.gs
var receiptImageFolderId='your receipt image folder id';
var SSID='your spreadsheet id';
The Spreadsheet Looks Like this:
Note I changed image Id to image Url so that you can just click on the url to get a viewer to look at the image. I think this is much cleaner than trying to put images of varying sizes into the spreadsheet.
I have a form response sheet (https://docs.google.com/spreadsheets/d/1YCneMRUC6ZKK0V3qs0mROhr6j62mdNIWAxcW71aAQIg/edit#gid=0) which saves all the requests from our stakeholders, and our workflow lead has to manually assign these requests to the members of our team in a round-robin fashion while ensuring each of the team members has an equal distribution of requests.
However, if a duplicate task is submitted (which is very much possible), it should be assigned to the same person that handled it earlier.
Is it possible to employ Google scripts solution to get this type of random yet equal distribution of tasks among the assignee group? The agent availability on any given day is also important, as they could be out of office, therefore the workflow lead keeps revising the agent list almost on a daily basis. Hence, it's all the more useful to have a Google AppScript solution to this problem (assigning one task at a time to the next available agent in queue). If the script can email the agent that would be ideal, but not necessary. Kindly advise! Thanks.
Round Robin Assignment
This script provides the following assignments:
If task title is repeated it assigns that task to original assignee.
If task title is new then it assigns that task to the assignee that has the least tasks.
If the title is new and all assignees have the same number of tasks then it makes a random selection with Math.floor(Math.random() * assigneeArray.length);
Here's the code:
Code.gs:
function onOpen() {
SpreadsheetApp.getUi().createMenu('My Tools')
.addItem('Add Task', 'addTask')
.addItem('Add Assignee', 'addAssignee')
.addSubMenu(SpreadsheetApp.getUi().createMenu('Utility')
.addItem('Select Columns Skip Header', 'jjeSUS1.selectColumnsSkipHeader')
.addItem('Create Named Range', 'jjeSUS1.createNamedRange'))
.addToUi();
}
function addAssignee() {
showFormDialog({filename:'addAssignee',title:'Add Assignee'});
}
function postAssigneeData() {
}
function addTask() {
showFormDialog({filename:'addTask',title:'Add Task'});
}
function include(filename){
return HtmlService.createHtmlOutputFromFile(filename).getContent();
}
function showFormDialog(dObj){
var form=HtmlService.createHtmlOutputFromFile(dObj.filename).getContent();
var ui=HtmlService.createTemplateFromFile('html').evaluate();
ui.append(form);
ui.append("</body></html>");
SpreadsheetApp.getUi().showModelessDialog(ui, dObj.title);
}
function saveData(dObj) {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName(dObj.sheetName);
var hrg=sh.getRange(1,1,1,sh.getLastColumn());
var hA=hrg.getValues()[0];
var vA=[];
for(var i=0;i<hA.length;i++) {
vA.push((dObj[hA[i]])?dObj[hA[i]]:'');//Column headers must agree with form names
}
dObj['row']=sh.getLastRow()+1;
var cA=Object.keys(dObj).filter(function(el){return (el!=='row' && el !='sheetName')});
for(var i=0;i<cA.length;i++) {
saveValue(dObj.row,cA[i],dObj[cA[i]],dObj.sheetName,1);
}
return dObj;
}
function makeAssignment(aObj) {
Logger.log(aObj);
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Assignments')
var title=aObj.Title;
var taskObj=getTasks();
//Check to see if someone has already done this once
if(taskObj.taskA.indexOf(title)>-1) {
saveValue(aObj.row,'Assignment',taskObj[aObj.Title],'Assignments',1);
saveValue(aObj.row,'Date',Utilities.formatDate(new Date(),Session.getScriptTimeZone(), "E MM d, yyyy HH:mm"),'Assignments',1);
postTaskData(aObj.Title,taskObj[aObj.Title]);
}else{
var assA=getAssigneeTasks();
if(assA[0].allCountsEqual=='false') {
//they don't have the same number of tasks so take the lowest one
saveValue(aObj.row,'Assignment',assA[0].email,'Assignments',1);
saveValue(aObj.row,'Date',Utilities.formatDate(new Date(),Session.getScriptTimeZone(), "E MM d, yyyy HH:mm"),'Assignments',1);
postTaskData(aObj.Title,assA[0].email);
}else{
//they all have the same number of task so take a random one
var n=Math.floor(Math.random()*assA.length);
saveValue(aObj.row,'Assignment',assA[n].email,'Assignments',1);
saveValue(aObj.row,'Date',Utilities.formatDate(new Date(),Session.getScriptTimeZone(), "E MM d, yyyy HH:mm"),'Assignments',1);
postTaskData(aObj.Title,assA[n].email);
}
}
return true;
}
function getTasks() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Data');
var taskObj={'taskA':[]};
var h=jjeSUS1.getColumnHeight(1, sh, ss);
if(h>2) {
var rg=sh.getRange(3,1,h-2,2);
var vA=rg.getValues();
for(var i=0;i<vA.length;i++) {
taskObj[vA[i][0]]=vA[i][1];
if(taskObj.taskA.indexOf(vA[i][0])==-1) {
taskObj.taskA.push(vA[i][0]);//Unique Task Array
}
}
}
return taskObj
}
function postTaskData(key,value) {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Data');
sh.appendRow([key,value]);
}
function getAssigneeTasks() {
var taskObj=getTasks();
var aeqA=getAssignees().map(function(el){return {email:el,count:0,allCountsEqual:'false'}});
var keysA=Object.keys(taskObj).filter(function(el){return (el != 'taskA')});
for(var i=0;i<aeqA.length;i++) {
for(var j=0;j<keysA.length;j++) {
if(taskObj[keysA[j]]==aeqA[i].email){
aeqA[i].count+=1;
}
}
}
aeqA.sort(function(a,b){return a.count - b.count;});
var isTrue=true;
var maxCount=aeqA[aeqA.length-1].count;
aeqA.forEach(function(el){if(el.count!=maxCount){isTrue=false;}});
if(isTrue) {
aeqA.map(function(el){return el.allCountsEqual='true';});
}
return aeqA;
}
function saveValue(row,columnName,value,sheetName,headerRow) {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName(sheetName);
var hA=sh.getRange(headerRow,1,1,sh.getLastColumn()).getValues()[0];
sh.getRange(row,hA.indexOf(columnName)+1).setValue(value);
}
function getAssignees() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Assignees');
var hrg=sh.getRange(1,1,1,sh.getLastColumn());
var hA=hrg.getValues()[0];
return sh.getRange(2, hA.indexOf('Email')+1, sh.getLastRow()-1,1).getValues().map(function(r){return r[0]});
}
function closeDialog() {
var userInterface=HtmlService.createHtmlOutputFromFile('dummy');
SpreadsheetApp.getUi().showModelessDialog(userInterface,'Closing');
}
css.html:
<style>
body {background-color:#ffffff;}
input[type="button"],input[type="text"]{margin:0 0 2px 0;}
</style>
resources.html:
<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>
html.html:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<?!= include('resources') ?>
<?!= include('css') ?>
<?!= include('script') ?>
</head>
<body>
script.html:
<script>
$(function(){
document.getElementById('txt1').focus();
});
function getInputObject(obj) {
var rObj={};
var length=Object.keys(obj).length;
for(var i=0;i<length;i++){
console.log('Name: %s Type: %s',obj[i].name,obj[i].type);
if(obj[i].type=="text"){
rObj[obj[i].name]=obj[i].value;
}
if(obj[i].type=="select-one"){
rObj[obj[i].name]=obj[i].options[obj[i].selectedIndex].value;
}
if(obj[i].type="hidden"){
if(obj[i].name) {
rObj[obj[i].name]=obj[i].value;
}
}
}
return rObj;
}
function processForm(obj){
var fObj=getInputObject(obj);
console.log(JSON.stringify(fObj));
google.script.run
.withSuccessHandler(function(rObj){
document.getElementById("btn").disabled=true;
$('#msg').html('<br /><h1>Data Saved.</h1>');
if(rObj.sheetName=='Assignments') {
google.script.run
.withSuccessHandler(function(){
$('#msg').html('<br /><h1>Assignments Complete.</h1>');
google.script.host.close();
})
.makeAssignment(rObj);
}else{
google.script.host.close();
}
})
.saveData(fObj);
}
console.log('My Code');
</script>
addAssignee.html:
<div id="heading"><h1>Add Assignee</h1></div>
<div id="content">
<h3>Please Enter First Name, Last Name, Phone and Email into the text areas adjacent to the text box labels.</h3>
<form id="assigneeForm" onsubmit="event.preventDefault();processForm(this);" >
<br /><input type="text" id="txt1" name="First" /> First
<br /><input type="text" id="txt2" name="Last" /> Last
<br /><input type="text" id="txt3" name="Phone" /> Phone
<br /><input type="text" id="txt3" name="Email" /> Email
<br /><input type="hidden" value="Assignees" name="sheetName" />
<br /><input id="btn" type="submit" value="Submit" />
<br />
</form>
</div>
<div id="msg"></div>
<div id="cntl"><input type="button" id="btn" value="Close" onClick="google.script.host.close();" ></div>
addTask.html:
<div id="heading"><h1>Add Task</h1></div>
<div id="content">
<h3>Please Enter Title and Description into the text areas adjacent to the text box labels.</h3>
<form id="assigneeForm" onsubmit="event.preventDefault();processForm(this);" >
<br /><input type="text" id="txt1" name="Title" /> Title
<br /><input type="text" id="txt2" name="Description" /> Description
<br /><input type="hidden" value="Assignments" name="sheetName" />
<br /><input id="btn" type="submit" value="Submit" />
<br />
</form>
</div>
<div id="msg"></div>
<div id="cntl"><input type="button" id="btn" value="Close" onClick="google.script.host.close();" ></div>
The three pages of my spreadsheet look as follows: (names are on images)
JavaScript Arrays
JavaScript Objects
HtmlService
Templated Html
Public Libraries
I try to save file on google drive and information in google sheets. But information in Sheets comes like Java.object.
Here is how it looks like in Code.gs :
var FOLDER_ID = '1jxBwrsz0JdBHcADpUUMespe';
var SHEET_ID = '1oJcKQ2RrtxxE1mn_CP-UAHefDxV7zg4';
function doPost(e) {
var data = Utilities.base64Decode(e.parameters.data);
var blob = Utilities.newBlob(data, e.parameters.mimetype, e.parameters.name);
var folder = DriveApp.getFolderById(FOLDER_ID);
var file = folder.createFile(blob);
folder.createFolder(name)
var res = [];
SpreadsheetApp.openById(SHEET_ID).getSheets()[0].appendRow([e.parameters.name, file.getUrl()].concat(res));
return ContentService.createTextOutput("Done.")
}
Here is how it looks like in the HTML file :
<form action="https://script.google.com/macros/s/AKfycbxkzg6ud1VyTI2W4gs-CRJRS3i3qLDXQIGevtyy/exec" id="form" method="post">
<div id="data"></div>
<div class="form-group">
<label for="name">Имя</label>
<input type="text" id='name' name="name" placeholder="Имя" />
</div>
<div class="form-group">
<label for="comment">Комм</label>
<input type="text" name="comment" placeholder="Комментарий" />
</div>
<input name="file" id="uploadfile" type="file">
<input id="submit" type="submit">
</form>
<script>
$('#uploadfile').on("change", function() {
var file = this.files[0];
var fr = new FileReader();
var name = $('#name').value;
fr.fileName = file.name
fr.onload = function(e) {
e.target.result
html = '<input type="hidden" name="data" value="' + e.target.result.replace(/^.*,/, '') + '" >';
html += '<input type="hidden" name="mimetype" value="' + e.target.result.match(/^.*(?=;)/)[0] + '" >';
html += '<input type="hidden" name="filename" value="' + e.target.fileName + '" >';
html += '<input type="hidden" name="name" value="' + name + '" >';
$("#data").empty().append(html);
}
fr.readAsDataURL(file);
});
</script>
Result in Google sheet
How to get data in a readable format?
Personally, I never use forms. Here's an examples that creates 5 text boxes. Validates that you put something into them and returns the code to the server via google.script.run for further processing (i.e. Logger.log the data). I create the html server side and loaded it on the on DOM ready event with JQuery.
Codes.gs
function buildForm(){
var s='';
for(var i=0;i<5;i++){
s+=Utilities.formatString('<br /><input class="jim" type="text" value="" id="%s" />%s',"txt" + i,"text" + Number(i+1));
}
s+='<br /><input type="button" value="Submit" onClick="getValues();" /><input type="button" value="Close" onClick="google.script.host.close();" />';
return s;
}
function saveValues(A){
for(var i=0;i<A.length;i++){
Logger.log('\nid=%s\nvalue=%s',A[i].id,A[i].value);
}
}
function showFormDialog(){
var ui=HtmlService.createHtmlOutputFromFile('form')
SpreadsheetApp.getUi().showModelessDialog(ui,'My Form');
}
form.html:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>
$(function(){
google.script.run
.withSuccessHandler(function(html){$('#form').html(html);})
.buildForm();
});
function getValues(){
var elements=document.getElementsByClassName('jim');
var el=[];
console.log('elements.length=%s',elements.length);
for(var i=0;i<elements.length;i++){
var obj={};
obj['id']='txt' + i;
obj['value']=$('#' + 'txt' + i).val();
el.push(obj);
}
var dataValid=true;
for(var i=0;i<el.length;i++){
console.log('id=%s, value=%s',el[i].id, el[i].value);
if(!el[i].value){
dataValid=false;
$('#'+el[i].id).css('background','#ffff00');
}else{
$('#'+el[i].id).css('background','#ffffff');
}
}
if(dataValid){
google.script.run.saveValues(el);
}else{
alert('Invalid Data Found...Try again sucker....');
}
}
console.log('MyCode');
</script>
<style>
input {margin:5px 5px 0 0;}
</style>
</head>
<body>
<div id="form"></div>
</body>
</html>
Ive been using geolocation and HTML recently, i want to try to show an iframe when the submit button is pressed, i keep getting stuck on what to do.
<!DOCTYPE html>
<html>
<body>
<input type="text" id="text" />
<input type="button" id="btn" value="Submit" onClick="javascript: window.open('https://api.ipdata.co/' + document.getElementById('text').value);" />
</body>
</html>
Oh and here’s my iframe
The IPadress is the ipadress of a device i am testing this out on. I want to put this in where the “javascript: window.open” is
I'm not sure why You'd want to show an iframe with a JSON response...
var text = document.getElementById("text"),
btn = document.getElementById("btn"),
iframe = document.getElementById("iframe");
function text2iframe() {
iframe.src = "https://api.ipdata.co/" + text.value;
}
btn.addEventListener("click", text2iframe);
<input type="text" id="text" value="47.91.202.22"><br>
<input type="button" id="btn" value="Submit"><br>
<iframe id="iframe"></iframe>
When you can go for the JSON directly!
var text = document.getElementById("text"),
btn = document.getElementById("btn"),
pre = document.getElementById("pre");
function ipdata() {
var request = new XMLHttpRequest();
request.open('GET', "https://api.ipdata.co/" + text.value, true);
request.addEventListener("load", function() {
if (request.status >= 200 && request.status < 400) {
pre.textContent = request.responseText
var responseObj = JSON.parse(request.responseText);
console.log( responseObj.country_name )
console.dir( responseObj );
} else {
// error
}
});
request.addEventListener("error", function() {
// connection error
});
request.send();
}
btn.addEventListener("click", ipdata);
<input type="text" id="text" value="47.91.202.22"><br>
<input type="button" id="btn" value="Submit"><br>
<pre id="pre"></pre>
I have four user input text fields in html created to user input data. I want to pass this four values into Google spreadsheet. This HTML is created using Google Apps Script.
I am not familiar with Google Apps Script but looking badly to develop a tool. Can anyone help me to work on this
This is a simple HTML file communicating with Google Apps Script contained in a Spreadsheet. The HTML file and the Google Apps Script communicate with each other and I pass one array from the HTML file to the Google Script.
The Code.gs file:
function doGet()
{
var html = HtmlService.createHtmlOutputFromFile('index');
return html.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
}
function getData(a)
{
var ts = Utilities.formatDate(new Date(), "GMT-6", "M/d/yyyy' 'HH:mm:ss");
a.splice(0,0,ts);
var ss=SpreadsheetApp.openById('SPREADSHEETID')
ss.getSheetByName('Form Responses 1').appendRow(a);
return true;
}
function getURL()
{
var ss=SpreadsheetApp.openById('SPREADSHEETID');
var sht=ss.getSheetByName('imgURLs');
var rng=sht.getDataRange();
var rngA=rng.getValues();
var urlA=[];
for(var i=1;i<rngA.length;i++)
{
urlA.push(rngA[i][0]);
}
return urlA;
}
The index.html file:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<div id="data">
<br />Text 1<input name="t1" type="text" size="15" id="txt1" placeholder="Text 1" />
<br />Text 2<input name="t2" type="text" size="15" id="txt2" placeholder="Text 2" />
<br />Text 3<input name="t3" type="text" size="15" id="txt3" placeholder="Text 3" />
<br />Text 4<input name="t4" type="text" size="15" id="txt4" placeholder="Text 4" />
<br /><input type="radio" name="Type" value="Member" checked />Member
<br /><input type="radio" name="Type" value="Guest" />Guest
<br /><input type="radio" name="Type" value="Intruder" />Intruder
<br /><input type="button" value="submit" id="btn1" />
<br /><img id="img1" src="" alt="img1" width="300" />
</div>
<div id="resp" style="display:none;">
<h1>Response</h1>
<p>Your data has been received.</p>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>
$(function() {
$('#btn1').click(validate);
$('#txt4').val('');
$('#txt3').val('');
$('#txt2').val('');
$('#txt1').val('')
google.script.run
.withSuccessHandler(setURL)
.getURL();
});
function setURL(url)
{
$('#img1').attr('src',url[0]);
}
function setResponse(a)
{
if(a)
{
$('#data').css('display','none');
$('#resp').css('display','block');
}
}
function validate()
{
var txt1 = document.getElementById('txt1').value || '';
var txt2 = document.getElementById('txt2').value || '';
var txt3 = document.getElementById('txt3').value || '';
var txt4 = document.getElementById('txt4').value || '';
var type = $('input[name="Type"]:checked').val();
var a = [txt1,txt2,txt3,txt4,type];
if(txt1 && txt2 && txt3 && txt4)
{
google.script.run
.withSuccessHandler(setResponse)
.getData(a);
return true;
}
else
{
alert('All fields must be completed.');
}
}
function loadTxt(from,to)
{
document.getElementById(to).value = document.getElementById(from).value;
}
function radioValue()
{
var radios = document.getElementsByName('genderS');
for (var i = 0, length = radios.length; i < length; i++)
{
if(radios[i].checked)
{
return radios[i].value;
}
}
}
console.log('My Code');
</script>
</body>
</html>