Google apps script UI services to HTML services - user-interface

I try to convert this simple google apps script code below to HTML services code.
The code below is written with the deprecated google apps script UI services!
Can anyone help me with HTML services example code in this usecase?
// Script with deprecated UI-services
// How to create this app with HTML-services?!!!?
//This script runs in a google website.
//It has one textobject and 1 button.
//when the button is pressed the value entered is stored in the spreadsheet
ssKey = 'sheetkey....';
function doGet(){
var app = UiApp.createApplication().setTitle('myApp');
//Create panels en grid
var MainPanel = app.createVerticalPanel();
var Vpanel1 = app.createVerticalPanel().setId('Vpanel2');
var grid = app.createGrid(4, 2).setId('myGrid');
//Vpanel1 widgets
var nameLabel = app.createLabel('Name');
var nameTextBox = app.createTextBox().setWidth('400px').setName('name').setId('name');
var submitButton = app.createButton('Verstuur').setId('submitButton');
grid.setWidget(0, 0, nameLabel)
.setWidget(0, 1, nameTextBox)
.setWidget(1, 1, submitButton);
//Set handlers en callbackelement
var handler = app.createServerClickHandler('InsertInSS');
handler.addCallbackElement(Vpanel1);
submitButton.addClickHandler(handler);
// build screen
Vpanel1.add(grid);
app.add(Vpanel1);
return app;
}
function InsertInSS(e){
var app =UiApp.getActiveApplication();
var collectedData = [new Date(), e.parameter.name] ;
var SS = SpreadsheetApp.openById(ssKey);
var Sheet = SS.getSheetByName('Contacts');
Sheet.getRange(Sheet.getLastRow()+1, 1, 1, collectedData.length).setValues([collectedData]);
app.getElementById('submitButton').setVisible(false);
//Reset fields on screen
app.getElementById('name').setText("");
return app;
}

Your Ui output looks like this:
Create an HTML file, and enter this code:
testForm.html
<div>
<div>
Name: <input id='idNameField' type='text'/>
</div>
<br/>
<input type='button' value='Verstuur' onmouseup='runGoogleScript()'/>
</div>
<script>
function onSuccess(argReturnValue) {
alert('was successful ' + argReturnValue);
//Reset fields on screen
Document.getElementById('idNameField').value = "";
}
function runGoogleScript() {
console.log('runGoogleScript ran!');
var inputValue = document.getElementById('idNameField').value;
google.script.run.withSuccessHandler(onSuccess)
.InsertInSS(inputValue);
};
</script>
Copy the follow code into:
Code.gs
function doGet() {
return HtmlService.createTemplateFromFile('testForm')
.evaluate() // evaluate MUST come before setting the NATIVE mode
.setTitle('The Name of Your Page')
.setSandboxMode(HtmlService.SandboxMode.NATIVE);
};
In a seperate .gs file add this code:
function InsertInSS(argPassedInName){
var ssKey = 'sheetkey....';
var SS = SpreadsheetApp.openById(ssKey);
var Sheet = SS.getSheetByName('Contacts');
Sheet.getRange(Sheet.getLastRow()+1, 1, 1, argPassedInName.length).setValue(argPassedInName);
}

Related

what to do with a function that produces a random image on button click in Javascript

I've written a function that should produce a random image from an array in Javascript.
I'm wanting to execute the function with an html button. I've written the code but it doesn't work. The image should be directed to a flex box div.
Code
var myImages=
["https://picsum.photos/536/354","Images/IMG_4830.jpeg","Images/IMG_4338.jpeg",
"Images/IMG_4096.JPG"];
function randomImages(){
var rnd = Math.floor(Math.random()*myImages.length);
if(rnd == 0){
rnd = 1;
}
var image = document.createElement("img");
var div=document.getElementById("flex-box-create").src = myImages[rnd]
div.appendChild(image)
}
button = <div><button class="btn btn-primary" id="image-Generator"
onclick="randomImages()">Click</button></div>
thanks
The source should be set on the image not the div, try this:
var image = document.createElement("img");
image.src = myImages[rnd];
var div = document.getElementById("flex-box-create");
div.appendChild(image);

Data Validation range from different spreadsheets

I have in my main spreadsheet a dropdown with data validation of a range from another tab in the same spreadsheet with data imported from another spreadsheet or with IMPORTRANGE function or imported with a script.
In both cases the main spreadsheet is very slow cause I have a lot of tab with data imported with both methods.
There is a way to do the data validation of the dropdown in the main sheet taking the data I need directly from the other spreadsheets without import them previously in the main spreadsheet with the IMPORTRANGE function or with a script?
I have tried to write a draft script but not works:
function externalSheetDataValidation() {
var cell = SpreadsheetApp.getActiveRange();
var dataValidationSheet = SpreadsheetApp.openById("xxxxxxxxxx");
var sheet = dataValidationSheet.getSheets()[0];
var range = sheet.getRange("B2:B5000");
var rule = SpreadsheetApp.newDataValidation()
.requireValueInRange(range, true)
.setAllowInvalid(false)
.build();
cell.setDataValidation(rule);
Logger.log(dataValidationSheet.getName());
}
You want to populate a dropdown based on the values of a range from a different spreadsheet.
You are currently importing those values to a sheet in your spreadsheet in order to use them via requireValueInRange.
You would like to skip the import process.
If that's the case, you can just do the following:
Create a function that returns a simple array with the values from the source range:
function importSheetA() {
return SpreadsheetApp.openById('xxxxx')
.getSheetByName('xxxxx')
.getRange('xxxxx')
.getValues()
.flat(); // This ensures a simple array is returned
}
Populate the dropdowns with requireValueInList instead of requireValueInRange, using the values returned by importSheetA:
function populateDropdown() {
var values = importSheetA();
var rule = SpreadsheetApp.newDataValidation()
.requireValueInList(values, true)
.setAllowInvalid(false)
.build();
var range = SpreadsheetApp.getActiveRange();
range.setDataValidation(rule);
}
Note:
You could update the populated options when the source range is edited if you install an onEdit trigger, and you could also specify what range of cells should be populated with dropdowns without them being necessarily selected, but I'm not sure that's what you want.
Update:
If your data has more than 500 items, value in list criteria is not an option. Your only other option would be to use List from a range instead, but as you said, this would require the source range to be on the same spreadsheet as the dropdown, which you wanted to avoid.
As a workaround, I'd suggest you to programmatically copy the data to a hidden sheet in the target spreadsheet, and use the data in this hidden sheet as your source range when creating the dropdown. For example, this:
function copyRange() {
var cell = SpreadsheetApp.getActiveRange();
var rangeNotation = "B2:B5000"; // Change according to your preferences
var sourceData = SpreadsheetApp.openById(xxxxx)
.getSheetByName(xxxxx)
.getRange(rangeNotation)
.getValues();
var targetSS = SpreadsheetApp.getActiveSpreadsheet();
var hiddenSheetName = "Hidden source data"; // Change according to your preferences
var hiddenSheet = targetSS.getSheetByName(hiddenSheetName);
if (!hiddenSheet) hiddenSheet = targetSS.insertSheet(hiddenSheetName);
var sourceRange = hiddenSheet.getRange(rangeNotation);
sourceRange.setValues(sourceData);
hiddenSheet.hideSheet();
var rule = SpreadsheetApp.newDataValidation()
.requireValueInRange(sourceRange, true)
.setAllowInvalid(false)
.build();
cell.setDataValidation(rule);
}
Reference:
requireValueInList(values, showDropdown)
Cached Dropdown Dialog
GS:
function getSelectOptions(){
const cs=CacheService.getScriptCache();
const v=JSON.parse(cs.get('cached'));
if(v){return v;}
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Options');
var rg=sh.getDataRange();
var vA=rg.getValues();
var options=[];
for(var i=0;i<vA.length;i++)
{
options.push(vA[i][0]);
}
cs.put('cached', JSON.stringify(vA), 300)
return vA;
}
function showMyselectionDialog() {
SpreadsheetApp.getUi().showModelessDialog(HtmlService.createHtmlOutputFromFile('ah2'), 'Selections');
}
html:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<script>
google.script.run
.withSuccessHandler(function(vA) {
updateSelect(vA);
})
.getSelectOptions();
function updateSelect(vA,id){//the id allows me to use it for other elements
var id=id || 'sel1';
var select = document.getElementById(id);
select.options.length = 0;
for(var i=0;i<vA.length;i++)
{
select.options[i] = new Option(vA[i],vA[i]);
}
}
</script>
<body>
<select id='sel1'></select>
</body>
</html>

Dropzone.js progressbar not updating on emit using s3.managedUpload

I found a couple of examples on how to do AWS uploads with dropzone.js testing them independently and now I'm trying to combine them into a finished form for our website's image uploader.
The one example I found showed that you could trigger progress updates from the S3.managedUpload as follows:
file.s3upload.on('httpUploadProgress', function(progress) {
if (progress.total) {
var percent = ((progress.loaded * 100) / progress.total);
console.log('httpUploadProgress', percent, progress.loaded);
myDropzone.emit('uploadprogress', file, percent, progress.loaded);
}
});
With my console.log in there, I can see multiple loops of the httpUploadProgress with increasing percentage. But the {dropzone}.emit doesn't seem to update the progress bar.
I've tried using the external 'myDropzone' var as well as making my AWSSendFile function (where the above code is included) as a prototype of dropzone itself so I can refer to it with this.emit(). No luck.
The dropzone template is:
dropzoneTemplate:'
<div class="Item dropzone-previews">
<div class="Icon"><img data-dz-thumbnail /></div>
<div class="Description dz-preview dz-file-preview">
<span class="Filename" data-dz-name ></span><br/>
<div class="dz-progress"><span class="dz-upload dz-progress" data-dz-uploadprogress ></span></div>
</div>
</div>'
Full code with the prototyped functions:
// override the uploadFiles function to send via AWS SDK instead of xhr
Dropzone.prototype.uploadFiles = function (files) {
for (var j = 0; j < files.length; j++) {
var file = files[j];
this.AWSsendFile(file);
}
};
Dropzone.prototype.AWSsendFile = function(file) {
let dz = this;
file.s3upload.send(function(err, data) {
if (err) {
dz.emit("error", file, err.message);
} else {
dz.emit("complete", file);
}
});
// listen to the AWS httpUploadProgress event, and emit an equivalent Dropzone event
file.s3upload.on('httpUploadProgress', function(progress) {
if (progress.total) {
var percent = ((progress.loaded * 100) / progress.total);
console.log('httpUploadProgress', percent, progress.loaded);
dz.emit('uploadprogress', file, percent, progress.loaded);
}
});
};
function acceptCallback(file, done) {
// options for the managed upload for this accepted file
// define the bucket, and the S3 key the file will be stored as
let fullKey = userId + "/" + file.name;
let params = {
Bucket: bucket,
Key: fullKey,
Body: file,
Region: 'us-east-2'
};
// add an S3 managed upload instance to the file
file.s3upload = new AWS.S3.ManagedUpload({params: params});
done();
};
function abortUpload(file) {
if (file.s3upload) file.s3upload.abort();
};
example console.log data:
httpUploadProgress 97.33840304182509 49152
httpUploadProgress 100 50496
UPDATE:
I noticed one problem with the async embedded callbacks and modified the 'prototyped' functions above to show my changes. Setting a local variable for dz = this to hold the handle of the main dropzone object. This makes the progress bar work hunky-dory and it now goes to 100%, but now the 'complete' emit call doesn't seem to be telling it to show as complete.

code highlighting apex (Firefox 31)

The Oracle Application Express code editor is just plain back text on white background. No Code highlighting. Also I can't press "tab" without the textfield loosing focus.
I am using firefox 31 (can't upgrade, rescricted by Admin at work here) Also I can't install plugins. I know you can change css on specific sites using a special folder in firefox ("chrome"-folder / userContent.css). I already used this to change die default size of the textfield, because it was frickin small everytime I opened the edit page.
So do you know any framework or script I can use in Apex ? (I could copy that shit to jsfiddle.net every time but that sucks
(I also found the scratchpad in Firefox, which can run js and jquery. Does that help ?)
[SOLVED]
since you can't use
<script src = "">
etc. in plain js, I had to use loadScript. For css files it was even more complicated, but I got it all working.
This is my code, I run it in scratchpad (firefox). It uses ACE to change a div to an editor with highlighting. When clicking apply I revert the editor-changes in the DOM but keep the text/code.
// Load Ace js
loadScript("http://cdnjs.cloudflare.com/ajax/libs/ace/1.1.01/ace.js", function(){
//initialization code
});
// Load Ace css
var cssId = 'myCss'; // you could encode the css path itself to generate id..
if (!document.getElementById(cssId)){
var head = document.getElementsByTagName('head')[0];
var link = document.createElement('link');
link.id = cssId;
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = 'http://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.0.0/css/bootstrap.min.css';
link.media = 'all';
head.appendChild(link);
}
// change textarea to div
var editorRegion = document.getElementById('F4000_P4651_PLUG_SOURCE_fieldset');
editorRegion.innerHTML = editorRegion.innerHTML.replace("textarea","div");
// run ACE
highlight();
// Modify the apply Button in Apex to first revert ACE-Editor to normal, then do the usual apply.
var applyChanges = document.getElementById('B3456326662');
applyChanges.setAttribute("onclick","modifiedApply()");
function modifiedApply(){
close();
setTimeout(normalApply, 500);
}
function normalApply(){
javascript:apex.submit('Apply_Changes');
}
// Revert ACE-Changes, but keep changed text/code.
function close(){
var value = editor.getValue();
editor.destroy();
var oldDiv = editor.container;
var newDiv = oldDiv.cloneNode(false);
newDiv.textContent = value;
oldDiv.parentNode.replaceChild(newDiv, oldDiv);
newDiv.outerHTML = newDiv.outerHTML.replace("div","textarea");
var old_new_old = document.getElementById('F4000_P4651_PLUG_SOURCE');
old_new_old.textContent = old_new_old.textContent.substring(0, old_new_old.textContent.length - 6);
}
var editor;
function highlight() {
editor = ace.edit("F4000_P4651_PLUG_SOURCE");
editor.setTheme("ace/theme/monokai");
editor.getSession().setUseWorker(false);
editor.getSession().setMode("ace/mode/javascript");
document.getElementsByClassName('ace_print-margin')[0].setAttribute("style","left:1000px");
}
function loadScript(url, callback){
var script = document.createElement("script")
script.type = "text/javascript";
if (script.readyState){ //IE
script.onreadystatechange = function(){
if (script.readyState == "loaded" ||
script.readyState == "complete"){
script.onreadystatechange = null;
callback();
}
};
} else { //Others
script.onload = function(){
callback();
};
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}

Cannot update label on Google Apps Script GUI Builder Interface at runtime

I have an interface that calls a script for spreadsheet creation using data taken from other spreadsheet. I want the interface to update its labels at runtime in order to give visual feedback to the user and let him know the script is running and it's not stuck. When I try to update the label I put in the interface, it doesn't update the first time, but updates correctly after myFunction() reaches its end. Which means I can see the message "Creation Completed", but the message "Creating file..." is never shown. Also, the button buttonCompile is never disabled so it seems that the instructions before myFunction() are not executed at all. How can I get the labels updated and the button disabled before myFunction() starts executing? (I already double-checked variable references)
function doGet() {
var app = UiApp.createApplication();
app.add(app.loadComponent("File creation"));
var buttonCreate = app.getElementById('createBtn');
var handlerCrea = app.createServerHandler('createClickHandler');
buttonCreate.addClickHandler(handlerCreate);
return app;
}
function createClickHandler(e) {
var app = UiApp.getActiveApplication();
var label = app.getElementById('createLbl');
label.setText("Creating file...");
var buttonCompile = app.getElementById('compileBtn');
buttonCompile.setEnabled(false);
myFunction();
label.setText("Creation completed.");
buttonCompile.setEnabled(true);
app.close();
return app;
}
The cause of this behavior is that the GUI is updated only after leaving a handler. A workaround is to use two handlers. The 1st one sets the label text to Creating file... and disables the button, the 2nd one executes the myFunction function, changes the text to Creation completed, and eanbles the button. Here is an example. It disables/enables the button and the worker handler simply waits 5 seconds.
function doGet(e) {
var app = UiApp.createApplication();
var container = app.createHorizontalPanel().setId('container');
var btnPerformance = app.createButton("Performance Demo").setId('btnPerformance');
var handlerPerformance = app.createServerHandler('onBtnPerformanceClick');
var handlerWait = app.createServerHandler('onWait');
btnPerformance.addClickHandler(handlerPerformance);
btnPerformance.addClickHandler(handlerWait);
container.add(btnPerformance);
app.add(container);
return app;
}
function enableControls(enable) {
var lstControls = [ 'btnPerformance' ];
var app = UiApp.getActiveApplication();
for (var i = 0; i < lstControls.length; i++) {
var ctl = app.getElementById(lstControls[i]);
ctl.setEnabled(enable);
}
}
function onWait(e) {
enableControls(false);
return UiApp.getActiveApplication();
}
function onBtnPerformanceClick(e) {
Utilities.sleep(5000);
enableControls(true);
return UiApp.getActiveApplication();
}

Resources