uiInstance.close() has no effect - google-apps-script

I have the following code inside a script for a Google Spreadsheet:
var uiInstance;
function displayDialog() {
uiInstance = UiApp.createApplication()
.setWidth(130)
.setHeight(130);
uiInstance.add(uiInstance.createLabel("foo"));
SpreadsheetApp.getUi().showModalDialog(uiInstance, 'bar');
}
This dialog is intended to inform the user the script is calculating something and I want to close the dialog again once the script has finished its work. If I use
uiInstance.close();
inside or outside the function nothing seems to happen though; the dialog remains opened until the user closes it. Is there any solution for my problem?

you have to "return" to effectively close the uiInstance, return is needed to reflect any change you made to the Ui, including closing it.
try
return uiInstance.close();
EDIT following your comment :
UiApp instances can only be closed from a handler function, I thought that was how you were using it (but I might have been wrong).
Below is a small code example :
function displayDialog() {
var uiInstance = UiApp.createApplication()
.setWidth(130)
.setHeight(130);
uiInstance.add(uiInstance.createLabel("foo"));
var handler = uiInstance.createServerHandler('closeDialog');
uiInstance.add(uiInstance.createButton('close',handler));
SpreadsheetApp.getUi().showModalDialog(uiInstance, 'bar');
}
function closeDialog(){
return UiApp.getActiveApplication().close();
}
There is also a tricky workaround that can "simulate" a user action. It uses a property of some widgets to trigger a handler function when they change value. In the example below I used a checkBox to start the process
It will close the dialog when the task in doStuf is done.
function displayDialog() {
var uiInstance = UiApp.createApplication()
.setWidth(130)
.setHeight(130);
uiInstance.add(uiInstance.createLabel("foo"));
var handler = uiInstance.createServerHandler('doStuf');
var chk = uiInstance.createCheckBox().setValue(true).setId('chk').setVisible(false);
chk.addValueChangeHandler(handler);
uiInstance.add(chk);
chk.setValue(false,true)// This actually calls the doStuf function (using the handler)
SpreadsheetApp.getUi().showModalDialog(uiInstance, 'bar');
}
function doStuf(){
Utilities.sleep(5000);// replace with something useful ...
return UiApp.getActiveApplication().close();
}

Use:
uiInstance = uiInstance.close();
SpreadsheetApp.getUi().showModalDialog(uiInstance, 'bar');

Related

how to detect div value (Django rendered) change with MutationObserver

I am rendering a value from django backend to frontend, and I am trying to detect the div value change with MutationObserver. Below is my current code:
MutationObserver part:
window.addEventListener('load', function () {
var element = document.getElementById('myTaskList');
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
var observer = new MutationObserver(myFunction);
observer.observe(element, {
childList: true
});
function myFunction() {
console.log("this is a trial")
console.log(element);
console.log(element.innerHTML);
}
// setTimeout(function(){
// element.innerHTML = 'Hello World!';
// }, 1000);
//
// setTimeout(function(){
// element.innerHTML = 'Hello Space!';
// }, 2000);
});
html part:
<div hidden id="myTaskList">{{resultList | safe}}</div>
I am rendering a string "dummyValue" to the div, but just don't see the value from the console.log() statements inside function.
this works well when I uncomment the setTimeout functions though.
Thanks for any help on why MutationObserver won't detect the rendered div value
I finally figured out the reason. Hope this might be helpful for people having similar issues in the future.
So, basically I was using my Django form submit button to do two actions at one time:
1. submit data to the view and process the data in the view;
2. trigger another function with the click action through
Ajax.
The second action was blocked by the first action, and I was only able to get result from action 1.
My solution: I modified action 1 to use Ajax as well. As I mentioned above, I originally used the Django form to submit data. I trigger action 2 inside the success function of action 1. Everything is working well now.

onchange event is not firing in chrome

I want to select two files from the local system using HTML5 input element. I have written code as follows which is working fine in firefox but is not working in chrome. The onchange event is not firing whether i use addEventListener method or follow old style of input1.onchange = function name. As per my requirement i need to select two files one after another in one case and in all other cases i need to select only a single file. Can you let me know how to make it work on chrome also.
html snippet
<button id="openfolder"></button>
JavaScript snippet
function init() {
document.getElementById('openfolder').addEventListener("click",
menu_onclick,false);
}
//I am not appending the element to the document.
function menu_onclick(ev) {
var input = document.createElement('input');
input.id = 'selectFile';
input.type = 'file';
input.addEventListener("change",file_onchange,false);
input.click();
}
function file_onchange(ev){
var input1 = document.createElement('input');
input1.id = 'selectXMLFile';
input1.type = 'file';
input1.addEventListener("change",xmlSelect,false);
input1.click();
}

Creating a basic chatbar?

Below is old; look at the updated text at the bottom.
So my friends and I use google docs to communicate while in school, and we setup the challenge to create a working and "efficient" chat bar to communicate with better results. I've been around JavaScript for quite some time, but have never fooled around with Google Apps Script before. We are using the document app for our chats; the code I came up with is as below, but I have a few problems with it:
Errors when a user closes it, then goes to Chat -> Open Chat in the toolbar to re-open, saying, "Error encountered: An unexpected error occurred"; does not specify a line or reason
Needs a hidden element somewhere in the document which can allow users to see what others have typed, but that they can't edit without using the chat box (would add event listener to update chat box when text is ammended)
//Main function, ran when the document first opens.
function onOpen() {
var app = UiApp.createApplication(); //Create a Ui App to use for the chat bar
if(getCurrentUser()=="dev1"||getCurrentUser()=="dev2"){ //user-Id's hidden for privacy
DocumentApp.getUi().createMenu('Chat')
.addItem('AutoColor', 'autoColor')
.addItem('Open Chat', 'createChatBox')
.addItem('Elements', 'displayElements') //Hidden as it is not important for regular use
.addItem('MyID', 'showUser')
.addToUi();
}else{
DocumentApp.getUi().createMenu('Chat')
.addItem('AutoColor', 'autoColor')
.addItem('Open Chat', 'createChatBox')
.addToUi();
}
}
//Creates and returns the chats GUI
function createChatBox(){
var app = UiApp.getActiveApplication()
app.setTitle("Chat Bar (not yet working)");
var vPanel = app.createVerticalPanel().setId('chatPanel').setWidth('100%');
var textArea = app.createTextArea().setId('chatBox').setName('chatBox').setReadOnly(true).setText('').setSize('250px', '450px'); //Read only so they can not edit the text, even if it won't affect overall chat
var textBox = app.createTextBox().setId('messageBox').setName('messageBox').setText('Words');
var chatHandler = app.createServerHandler("sayChat").addCallbackElement(textArea).addCallbackElement(textBox);
var chatButton = app.createButton().setId("sayButton").setText("Say!").addMouseUpHandler(chatHandler);
vPanel.add(textArea);
vPanel.add(textBox);
vPanel.add(chatButton);
app.add(vPanel);
DocumentApp.getUi().showSidebar(app);
return app;
}
//The event handler for when the "Say!" (post) button is pressed. Is probably where the conflict stems from.
function sayChat(eventInfo){
var app = UiApp.getActiveApplication();
var parameter = eventInfo.parameter;
app.getElementById("chatBox").setText(parameter.chatBox+"["+getCurrentUser()+"]: "+parameter.messageBox);
app.getElementById("messageBox").setText("");
return app;
}
//A debug function and a function to tell you the unique part of your email (useless, really)
function showUser(){
DocumentApp.getUi().alert("Your userId is: "+getCurrentUser());
}
//Returns the unique part of a person's email; if their email is "magicuser#gmail.com", it returns "magicuser"
function getCurrentUser(){
var email = Session.getActiveUser().getEmail();
return email.substring(0,email.indexOf("#"));
}
//The Auto-color and displayElements methods are hidden as they contain other user-info. They both work as intended and are not part of the issue.
I do not need someone to rewrite the code (although that'd be greatly appreciated!), but instead point out what I'm doing wrong or suggest something to change/add.
Last, before you suggest it, the google docs chat does not work with our computers. It is not the fault of the document, but probably a compatability error with our browser. It is because of this issue that we are going through this fun yet hasty process of making our own chat method.
Update
I decided to give up on my version of the chat using pure Google Apps Script and help improve my friends version using both G-A-S and HTML. I added image thumbnail/linking support with command /img or /image, along with improved time and counter, and some other behind the scenes updates. Here is a quick screenshot of it:
Magnificent chat programmed from scratch, and no buggy update methods, just a casual refresh database to check for messages and set HTML text-area text. No more buggy getText methods. For each new message in the database, whether targeted toward the user or toward everyone in the chat, we load all the database messages up to a limit (50 messages at a time), then display them. The use of HTML in the messages is key to its appearence and features, such as images.
function getChat() {
var chat = "";
var time = getTime();
var username = getCurrentUsername();
var db = ScriptDb.getMyDb();
var query = db.query({time : db.greaterThan(getJoinTime())}).sortBy('time', db.DESCENDING).limit(50);
var flag = query.getSize() % 2 != 0;
while(query.hasNext()) {
var record = query.next();
if(record.showTo == "all" || record.showTo == getCurrentUsername()) {
var text = record.text;
for(var i = 0; i < text.split(" ").length; i++) {
var substr = text.split(" ")[i];
if(substr.indexOf("http://") == 0 || substr.indexOf("https://") == 0) {
text = text.replace(substr, "<a href='" + substr + "'>" + substr + "</a>");
}
}
var message = "<pre style='display:inline;'><span class='" + (flag? "even" : "odd") + "'><b>[" + record.realTime + "]</b>" + text;
message += "</span></pre>";
chat += message;
flag = !flag;
}
}
//DocumentApp.getUi().alert(getTime() - time);
return chat;
}
I am going to re-do his getChat() method to only check for new messages, and not load every message at each refresh.
First thing to to to get rid of your error message is to create the UiApp in the createChat function instead of onOpen.
I also used a client handler to clear the textBox because it's just more efficient. Here is the modified code :
code removed see updates below
As for your second request I'm not sure I understand exactly what you want to do... could you explain more precisely the behavior you expect ? (this is more a comment than an answer but I used the "answer field" to be more readable)
EDIT : I played a little with this code and came to something that -almost- works... it still needs to be improved but it's worth showing how it works.
I used scriptProperties to store the common part of the conversation, I think that's a good approach but the issue it to know when to update its content. Here is the code I have so far, I keep being open to any suggestion/improvement of course.
code removed, new version below
EDIT 2 : here is a version with an auto update that works quite good, the script updates the chat area automatically for a certain time... if no activity then it stops and wait for a user action. please test (using 2 accounts) and let us know what you think.
note I used a checkBox to handler the autoUpdate, I keep it visible for test purpose but of course it could be hidden in a final version.
EDIT 3 : added a message to warn the user when he's been put offline + changed textBox to colored textArea to allow for longer messages + condition to clear the messageBox so that the warning message doesn't go in the conversation. (set the time out to a very short value for test purpose, change the counter value to restore to your needs)
function onOpen() {
if(getCurrentUser()=="dev1"||getCurrentUser()=="dev2"){ //user-Id's hidden for privacy
DocumentApp.getUi().createMenu('Chat')
.addItem('AutoColor', 'autoColor')
.addItem('Open Chat', 'createChatBox')
.addItem('Elements', 'displayElements') //Hidden as it is not important for regular use
.addItem('MyID', 'showUser')
.addToUi();
}else{
DocumentApp.getUi().createMenu('Chat')
.addItem('AutoColor', 'autoColor')
.addItem('Open Chat', 'createChatBox')
.addToUi();
}
}
function createChatBox(){
ScriptProperties.setProperty('chatContent','');
var app = UiApp.createApplication().setWidth(252);
app.setTitle("Chat Bar");
var vPanel = app.createVerticalPanel().setId('chatPanel').setWidth('100%');
var chatHandler = app.createServerHandler("sayChat").addCallbackElement(vPanel);
var textArea = app.createTextArea().setId('chatBox').setName('chatBox').setReadOnly(true).setText('').setSize('250px', '450px');
var textBox = app.createTextArea().setId('messageBox').setName('messageBox').setText('Start chat...').setPixelSize(250,100).setStyleAttributes({'padding':'5px','background':'#ffffcc'}).addKeyPressHandler(chatHandler);
var clearTextBoxClientHandler = app.createClientHandler().forTargets(textBox).setText('');
textBox.addClickHandler(clearTextBoxClientHandler);
var chatButton = app.createButton().setId("sayButton").setText("Say!").addMouseUpHandler(chatHandler);
var chkHandler = app.createServerHandler('autoUpdate').addCallbackElement(vPanel);
var chk = app.createCheckBox().setId('chk').addValueChangeHandler(chkHandler);
vPanel.add(textArea);
vPanel.add(textBox);
vPanel.add(chatButton);
vPanel.add(chk);
app.add(vPanel);
DocumentApp.getUi().showSidebar(app);
return app;
}
function sayChat(e){
var app = UiApp.getActiveApplication();
var user = '['+getCurrentUser()+'] : ';
if(e.parameter.messageBox=="You have been put offline because you didn't type anything for more than 5 minutes..., please click here to refresh the conversation"){
app.getElementById('messageBox').setText('');// clear messageBox
ScriptProperties.setProperty('chatTimer',0);// reset counter
return app;
}
if(e.parameter.source=='messageBox'&&e.parameter.keyCode!=13){return app};
var content = ScriptProperties.getProperty('chatContent');
ScriptProperties.setProperty('chatContent',content+"\n"+user+e.parameter.messageBox)
app.getElementById("chatBox").setText(content+"\n"+user+e.parameter.messageBox+'\n');
app.getElementById('messageBox').setText('');
app.getElementById('chk').setValue(true,true);
ScriptProperties.setProperty('chatTimer',0);
return app;
}
function autoUpdate(){
var app = UiApp.getActiveApplication();
var content = ScriptProperties.getProperty('chatContent');
var counter = Number(ScriptProperties.getProperty('chatTimer'));
++counter;
if(counter>20){
app.getElementById('chk').setValue(false);
app.getElementById('messageBox').setText("You have been put offline because you didn't type anything for more than 5 minutes..., please click here to refresh the conversation");
return app;
}
ScriptProperties.setProperty('chatTimer',counter);
var content = ScriptProperties.getProperty('chatContent');
app.getElementById("chatBox").setText(content+'*'); // the * is there only for test purpose
app.getElementById('chk').setValue(false);
Utilities.sleep(750);
app.getElementById('chk').setValue(true,true).setText('timer = '+counter);
return app;
}
function showUser(){
DocumentApp.getUi().alert("Your userId is: "+getCurrentUser());
}
function getCurrentUser(){
var email = Session.getEffectiveUser().getEmail();
return email.substring(0,email.indexOf("#"));
}

How to make style of SubmitButton consistent with Button style in Google App Script UI?

I am building an UI form via code (not using the UI Builder) and I noticed that the SubmitButton class style is not consistent with the Button class look & feel.
Would you know any way to adjust the look & feel of either the Button class or the SubmitButton class to make them similar.
I noticed that the Button has a call setStylePrimaryName, setStyleName etc... but the documentation is vague - says: "This is useful for debugging"!!!
Any suggestion?
See below screenshoot, first button is of class Button, second button is SubmitButton. You can see they don't even align.
You can style (a button) the way you want with setStyleAttribute
var _btn= {
"background-color":"none",
"background":"none",
"width":"80px",
"height":"24px",
"border":"None",
"font-family":"hobo std",
"font-size":"0.9em",
"color":"3f3f3f",
"opacity":"1",
}
....
....
var closeb = app.createButton("Submit");
library.applyCSS(submit,_btn);
....
....
And in your library you have the function (credits to James Fereira)
function applyCSS(element, style){
for (var key in style){
element.setStyleAttribute(key, style[key]);
}
}
I resolved this cosmetic dilemma by using multiple submit buttons in the same form. I experimented with CSS sans success; the Submit & Reset buttons are two unique beasts in the world of button widgets.
Here is some working code
that demonstrates a multiple page form where each page uses three submitButton's to advance back and forth doing multiple doPost()'s.
// Muliple page form using Google Apps Script
function doGet(eventInfo) {return GUI(eventInfo)};
function doPost(eventInfo) {return GUI(eventInfo)};
function GUI (eventInfo) {
var n = (eventInfo.parameter.state == void(0) ? 0 : parseInt(eventInfo.parameter.state));
var ui = ((n == 0)? UiApp.createApplication() : UiApp.getActiveApplication());
var Form;
switch(n){
case 0: {
Form = getForm(eventInfo,n); // Use identical forms for demo purpose only
} break;
case 1: {
Form = getForm(eventInfo,n); // In reality, each form would differ but...
} break;
default: {
Form = getForm(eventInfo,n) // each form must abide by (implement) the hidden state variable
} break;
}
return ui.add(Form);
};
function getForm(eventInfo,n) {
var ui = UiApp.getActiveApplication();
// Increment the ID stored in a hidden text-box
var state = ui.createTextBox().setId('state').setName('state').setValue(1+n).setVisible(true).setEnabled(false);
var H1 = ui.createHTML("<H1>Form "+n+"</H1>");
var H2 = ui.createHTML(
"<h2>"+(eventInfo.parameter.formId==void(0)?"":"Created by submission of form "+eventInfo.parameter.formId)+"</h2>");
// Add three submit buttons to go forward, backward and to validate the form
var Next = ui.createSubmitButton("Next").setEnabled(true).setVisible(true);
var Back = ui.createSubmitButton("Back").setEnabled(n>1).setVisible(true);
var Validate = ui.createSubmitButton("Validate").setEnabled(n>0).setVisible(true);
var Buttons = ui.createHorizontalPanel().add(Back).add(Validate).add(Next);
var Body = ui.createVerticalPanel().add(H1).add(H2).add(state).add(Buttons).add(getParameters(eventInfo));
var Form = ui.createFormPanel().setId((n>0?'doPost[':'doGet[')+n+']').add(Body);
// Add client handlers using setText() to adjust state prior to form submission
// NB: Use of the .setValue(val) and .setValue(val,bool) methods give runtime errors!
var onClickValidateHandler = ui.createClientHandler().forTargets(state).setText(''+(parseInt(n)));
var onClickBackHandler = ui.createClientHandler().forTargets(state).setText(''+(parseInt(n)-1));
Validate.addClickHandler(onClickValidateHandler);
Back.addClickHandler(onClickBackHandler);
// Add a client handler executed prior to form submission
var onFormSubmit = ui.createClientHandler()
.forTargets(state).setEnabled(true) // Enable so value gets included in post parameters
.forTargets(Body).setStyleAttribute("backgroundColor","#EEE");
Form.addSubmitHandler(onFormSubmit);
return Form;
}
function getParameters(eventInfo) {
var ui = UiApp.getActiveApplication();
var panel = ui.createVerticalPanel().add(ui.createLabel("Parameters: "));
for( p in eventInfo.parameter)
panel.add(ui.createLabel(" - " + p + " = " + eventInfo.parameter[p]));
return panel;
}
The code uses a single "hidden" state (here visualized in a TextBox) and multiple SubmitButton's to allow the user to advance forward and backward through the form sequence, as well as to validate the contents of the form. The two extra SubmitButton's are "rewired" using ClientHandler's that simply modify the hidden state prior to form submission.
Notes
Note the use of the .setText(value) method in the client handler's. Using the Chrome browser I get weird runtime errors if I switch to either of the TextBox's .setValue(value) or .setValue(value, fireEvents) methods.
I tried (unsuccessfully) to implement this logic using a Script Property instead of the hidden TextBox. Instead of client handlers, this requires using server handlers. The behavior is erratic, suggesting to me that the asynchronous server-side events are occurring after the form submission event.

Unknown macro in library

I am trying to make a library, went well so far, but after adding a few functions it went bad.
When I run the script form the editor it is written in the script works. But when I try to test it the script cannot recognize the server handlers, giving an error: Unknown macro handler_function_name
I checked, all the names in the handlers correspond to names of functions. I read that some people had problems because the code was in different files, moved all the code in the same file the problem is still there.
It does not behave like that for all the handlers...
What else could be the reason for this?
edit:
The app creates additional panels during as a response to "clicks". Handlers of elements on those panels are the ones who's macros (that is handler functions) the app is not able to "find".
How can this be solved?
(except for the solution to put all the panels in the original panel and then change visibility, this works as far as handlers go but raises other problems)
So to put some code here, this is very very simple code...
function notWorkingGUI(){
var app=UiApp.createApplication();
var appPanel=app.createVerticalPanel().setId("appPanel");
var handler1=app.createServerHandler("handlerFunction1").addCallbackElement(appPanel);
var firstButton=app.createButton("Button 1", handler1);
appPanel.add(firstButton);
app.add(appPanel);
SpreadsheetApp.getActive().show(app);
}
function handlerFunction1(e){
var app=UiApp.getActiveApplication();
var appPanel2=app.createVerticalPanel().setId("appPanel2").setStyleAttribute("zIndex", 0).setStyleAttribute("position", "fixed");
var handler2=app.createServerHandler("handlerFunction2").addCallbackElement(appPanel2);
var secondButton=app.createButton("Button 2", handler2);
var label=app.createLabel("This should get visible after the click").setId("label").setVisible(false);
appPanel2.add(secondButton).add(label);
app.add(appPanel2);
return app;
}
function handlerFunction2(e){
var app=UiApp.getActiveApplication();
app.getElementById("label").setVisible(true);
return app;
}
This will work as expected when executed from the editor in which it is written, that is it will show firstButton then secondButton and finaly the label, however if it would be published as a library and invoked from an other script it would only recognise functionHandler1, that is show firstButton, secondButton but after a click on the secondButton an error message will be seen.
However if the script would be written like this:
function workingGUI(){
//previous first part
var app=UiApp.createApplication();
var appPanel=app.createVerticalPanel().setId("appPanel");
var handler1=app.createServerHandler("handlerFunction1a").addCallbackElement(appPanel);
var firstButton=app.createButton("Button 1", handler1);
//previous second part
var appPanel2=app.createVerticalPanel().setId("appPanel2").setStyleAttribute("zIndex", 0).setStyleAttribute("position", "fixed");
var handler2=app.createServerHandler("handlerFunction2a").addCallbackElement(appPanel2);
var secondButton=app.createButton("Button 2", handler2).setId("button2");
appPanel.add(firstButton);
app.add(appPanel);
SpreadsheetApp.getActive().show(app);
}
function handlerFunction1a(e){
var app=UiApp.getActiveApplication();
var label=app.createLabel("This should get visible after the click").setId("label").setVisible(false);
app.getElementById("appPanel2").add(app.getElementById("button2")).add(label);
app.add(app.getElementById("appPanel2"));
return app;
}
function handlerFunction2a(e){
var app=UiApp.getActiveApplication();
app.getElementById("label").setVisible(true);
return app;
}
Note that all handlers must be defined in the main function, meaning that also all the elements using those handlers and all the callback elements have to be defined here.
Then it would work even as a library, however for some reason this makes the script much much slower even for such a simple example.
The issue is here:
http://code.google.com/p/google-apps-script-issues/issues/detail?id=1346
It is calling the local code rather than the library code.
I wonder if it is still slow if you add a stub function in the local code?
i.e.
function runthis() {
library.createGUI();
}
function myevent() {
library.myevent();
}
I worked around this problem, it makes the script a bit slower but if you define all the handlers (that implies all the UI elements) in the original function it will work.