Google Apps Script, HTML addClickHandler ServerHandler does NOT work - google-apps-script

Can anyone confirm that HTML widgets accept ClickHandlers on the Server side ? I can't get my below code to work.
I create a serverHandler (and for good measure I have even added a useless callback element). Subsequently, I add it to a HTML.addClickHander (for good measure I have even added it to .addMouseUpHandler as well). The function is NOT executed.
var mouseclick = app.createServerHandler("handleTrainingClick_").addCallbackElement(lstFilter);
var params = [ "fromOrg", "trainingTitle", "dueDate", "medical", "status" ];
var resultSet = blSelectActiveTrainings_();
while (resultSet.hasNext()) {
var training = resultSet.next();
var html = TRAINING_ROW;
for (var pI in params) {
html = html.replace("$"+params[pI], training[params[pI]]);
}
pnlList.add(app.createHTML(html).setId(training.id).addClickHandler(mouseclick).addMouseUpHandler(mouseclick)
.addMouseMoveHandler(mousemove).addMouseOutHandler(mouseout).addMouseOverHandler(mouseover));
}
function handleTrainingClick_(e) {
Logger.log(e.source);
var app = UiApp.getActiveApplication();
return app;
}

HTML widgets server side handlers work just fine. It was an incorrect reference in my code. Thanks all.

Related

Is this possible to send and get back the value from google app script to html page without rendering html output?

After much discussion and R&D, image cropping is not possible with Google APP scripts. So I decided to try one using the Canvas API.
I am trying to pass the value from server script(.gs) to the HTML file and get back the value in the server side script without opening HTML output as in sidebar or model/modelLess dialog box. You can say silently call HTML, complete the process and return the value to server script method.
I tried but getFromFileArg() is not running when i am running the callToHtml().
Is this possible with below script? what you will suggest?
Server side (.gs)
function callToHtml() {
var ui = SlidesApp.getUi();
var htmlTemp = HtmlService.createTemplateFromFile('crop_img');
htmlTemp["data"] = pageElements.asImage().getBlob();
var htmlOutput = htmlTemp.evaluate();
}
function getFromFileArg(data) {
Logger.log(data);
}
crop_img.html template :
<script>
var data = <?= data ?>;
//call the server script method
google.script.run
.withSuccessHandler(
function(result, element) {
element.disabled = false;
})
.withFailureHandler(
function(msg, element) {
console.log(msg);
element.disabled = false;
})
.withUserObject(this)
.getFromFileArg(data);
</script>
You cannot "silently" call the HTML this way, no.
The HTML needs to go to the user and the user is not inside of your web app, but Google's web app (Slides), so you have to play by their rules.
You need to use one of the available UI methods such as showSidebar. You could have the displayed HTML be a spinner or message like "processing..." while the JavaScript runs.
function callToHtml() {
var ui = SlidesApp.getUi();
var htmlTemp = HtmlService.createTemplateFromFile('crop_img');
htmlTemp["data"] = pageElements.asImage().getBlob();
ui.showSidebar(htmlTemp.evaluate());
}

refreshing a button's text is not working with GAS

I have the following code which creates a simple app to allow the user to enter two values and click a button:
function start() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var app = UiApp.createApplication();
app.setTitle("Appraisals Analysis");
app.setHeight(100);
app.setWidth(500);
var grid = app.createGrid(3, 2);
grid.setId("grid");
grid.setCellSpacing(2);
grid.setCellPadding(2);
var uafLabel = app.createLabel("Unprocessed apparaisals folder name: ");
uafLabel.setStyleAttributes({"font-weight": "bold"});
var uafTextBox = app.createTextBox();
uafTextBox.setName('uafTextBox').setId('uafTextBox');
uafTextBox.setText('Unprocessed Appraisals');
grid.setWidget(0, 0, uafLabel);
grid.setWidget(0, 1, uafTextBox);
var pafLabel = app.createLabel("Processed apparaisals folder name: ");
pafLabel.setStyleAttributes({"font-weight": "bold"});
var pafTextBox = app.createTextBox();
pafTextBox.setName('pafTextBox').setId('pafTextBox');
pafTextBox.setText('Processed Appraisals');
grid.setWidget(1, 0, pafLabel);
grid.setWidget(1, 1, pafTextBox);
var button = app.createButton('Submit').setId("submitButton");
grid.setWidget(2, 0, button);
var mypanel = app.createVerticalPanel();
mypanel.add(grid);
app.add(mypanel);
var clickHandler = app.createServerClickHandler("parseFiles");
button.addClickHandler(clickHandler);
clickHandler.addCallbackElement(grid);
ss.show(app);
}
I then have the parseFiles function which can take up to 2 minutes to do its job as follow
function parseFiles(e) {
var app = UiApp.getActiveApplication();
var processedFolder = DocsList.getFolder(e.parameter.pafTextBox);
var workingFolder = DocsList.getFolder(e.parameter.uafTextBox);
var appraisals = workingFolder.find('Performance Appraisal');
app.getElementById("submitButton").setText("Parsing Files...");
for (var i in appraisals) {
var doc = DocumentApp.openById(appraisals[i].getId());
parseDocument(doc, getEmpName(doc.getName()));
}
return app;
}
My problem is that when I click the button, the work gets done, but the button text stays as "Submit" instead of changing to "Parsing Files...". Once the work is over, then the button changes.
Any idea what I may be doing wrong?
Regards
Crouz
You got nothing wrong, just the concept. Think like this: all Apps Script code you write runs on a google server (server-side), but the interface is (obviously) shown on your computer (client-side). The Apps Script "environment" has a client-side script (that we do not have access or control of) that receives the information on how to build the interface you defined in your code (server-side).
So, everything you do in your code gets updated at once, in a bundle, after your function finishes. And that's why we need to return app, so that our UiApp definition gets sent/returned to the client-side that have triggered the script.
For very simple situations, like disabling or setting the text on a button or label, there's a clientHandler that can perform basic operations on directly the client-side without requiring a network trip to the server-side to run your custom code. Since these operations are done on the client-side they're done "instantly". Note that this is not for generic code, but only predefined operations. clientHandlers are really meant just for simple stuff. It's difficult (if not impossible) to do complex operations.
Here's my suggestion using a clientHandler:
function start() {
//your current code...
clickHandler.addCallbackElement(grid);
var clientHandler = app.createClientHandler().forEventSource().setText('Parsing Files...').setEnabled(false);
button.addClickHandler(clientHandler);
ss.show(app);
}
function parseFiles(e) {
//...
app.getElementById("submitButton").setText("Submit again").setEnabled(true);
//...
return app;
}
Note that you can add multiple handlers, client or server, to a button (or any other widget that accept handlers) and all of them will run concurrently.
Also, it's very important to notice that we're talking about UiApp here, when using HtmlService the approach is significantly different.

Google Chrome Indexdb - redundant code

I am tryint yo understand some code from an opensource project that handles indexDB commands within a Google Chrome application.
The code is as follows :
var db = pm.indexedDB.db;
var trans = db.transaction([pm.indexedDB.TABLE_DRIVE_CHANGES], "readwrite");
var store = trans.objectStore(pm.indexedDB.TABLE_DRIVE_CHANGES);
var boundKeyRange = IDBKeyRange.only(driveChange.id);
var request = store.put(driveChange);
request.onsuccess = function (e) {
callback(driveChange);
};
request.onerror = function (e) {
console.log(e.value);
};
Although the app works, to me it seems that the following line is redundant code
var boundKeyRange = IDBKeyRange.only(driveChange.id);
Or am I missing something? The variable 'boundKeyRange' is never referenced anywhere.
Unless boundKeyRange is used later, you're not missing something. IDBKeyRange.only just creates an IDBKeyRange object, and if that object isn't used in some IndexedDB request, it does absolutely nothing.

Passing state/data to Google Apps Script ServerHandler

I am trying to work out how I can pass some arbitrary state to a ServerHandler in Google Apps Script. The following code illustrates the question - can anybody help?
Thanks.
function myFunc(e) {
// want to get my data object back out here..?
}
function setUp()
{
var data = getMyDataArray();
// ... set up UI...
var h = app.createServerHandler('myFunc');
// How do I passs my data object to the myFunc handler?
flow.add(app.createButton().setText("OK").addClickHandler(h));
app.add(flow);
s.show(app);
}
You can use Hidden elements to store arbitrary data and send it along with a server handler invocation. The issue is that the the element can only store a string. But you can solve this using JSON.
function myFunc(e) {
var yourObj = Utilities.jsonParse(e.parameter.yourObject);
//do what you need
}
function setUp()
{
var data = getMyDataArray();
// ... set up UI...
var hidden = app.createHidden("yourObject", Utilities.jsonStringify(data));
var h = app.createServerHandler('myFunc').addCallbackElement(hidden);
flow.add(app.createButton().setText("OK").addClickHandler(h));
app.add(flow);
s.show(app);
}

Is there a work around for an object oriented front-end in Google Script

I'm having some problems with an event handler in the object below. I can't remember the error message but it basically said that it could not find the function. The code below is an example of what I'm trying to do.
var anObject = function () {
var n = 0;
var HandleClick(e) {
n ++;
};
return {
Init: function () {
var app = UiApp.getActiveApplication();
var handler = app.createServerHandler("HandleClick");
var com = UiApp.LoadComponent("MyGui", {prefix: "a"});
com.getElementById("button").addClickHandler(handler);
}
}
}
Would really appreciate a work-around if possible, if that is not possible then please tell me what you would suggest because I'm not sure how best to get around this.
Thanks guys.
All handler functions must be top level functions on your script. It's not possible to have it inside an object like this.