I did:
function doGet() {
var app = UiApp.createApplication();
// Create a dialog box.
var dialog = app.createDialogBox();
// Add a label to the dialog and set the dimensions and position.
dialog.setPopupPosition(100, 100).setSize(500, 500).show();
// Show the dialog. Note that it does not have to be "added" to the UiInstance.
dialog.show();
return app;
}
Deployed and got:
This is obviously not what I expected.
what am I doing wrong?
Don't worry, .createDialogBox() seems to be broken. Write your entry into the issue tracker I'll star it as soon as your entry shows up there.
I use PopupPanel which works fine and gives the same functionality minus the draggability
Related
I'm having a nightmare doing a lot of scenarios using Apps Script, but nothing works! I have a function that makes a GET request returns an array of cards. Now, sometimes I need this card refreshes again to fetch the new content.
function listTemplatesCards(){
var getAllTemplates = getTemplates();
var allTemplates = getAllTemplates.templates;
var theUserSlug = getAllTemplates.user_slug;
var templateCards = [];
//There are templates
if(allTemplates.length > 0){
allTemplates.forEach(function(template){
templateCards.push(templateCard(template, theUserSlug).build());
});
return templateCards;
}
}
This function is called on onTriggerFunction. Now, if I moved to another card and I wanted to back again to the root but in clean and clear way, I use this but it doesn't work:
//Move the user to the root card again
var refreshNav = CardService.newNavigation().popToRoot();
return CardService.newActionResponseBuilder().setStateChanged(true).setNavigation(refreshNav).build();
Simply, what I want is once the user clicks on Refresh button, the card refreshes/updates itself to make the call again and get the new data.
The only way I've found to do this is to always use a single card for the root. In the main function (named in the appscript.json onTriggerFunction), return only a single card, not an array of cards. You can then use popToRoot().updateCard(...) and it works.
I struggled with this for over a day, improving on Glen Little's answer so that its a bit more clear.
I have my root card to be refreshed defined in a funciton called: onHomepage.
I update the appscript.json manifest to set the homepageTrigger and onTriggerFunction to return the function that builds my root card.
"gmail": {
"homepageTrigger": {
"enabled": true,
"runFunction":"onHomepage"
},
"contextualTriggers":[
{
"unconditional":{},
"onTriggerFunction": "onHomepage"
}
]
}
Then it is as simple as building a gotoRoot nav button function that will always refresh the root page.
function gotoRootCard() {
var nav = CardService.newNavigation()
.popToRoot()
.updateCard(onHomepage());
return CardService.newActionResponseBuilder()
.setNavigation(nav)
.build();
}
As far as gmail addons are considered, cards are not refreshed but updated with new cards. And it is pretty simple.
//lets assume you need a form to be updated
function updateProfile() {
//ajax calls
//...
//recreate the card again.
var card = CardService.newCardBuilder();
//fill it with widgets
//....
//replace the current outdated card with the newly created card.
return CardService.newNavigation().updateCard(card.build());
}
A bad hack that works for my Gmail add-on:
return CardService.newActionResponseBuilder()
.setStateChanged(true) // this doesn't seem to do much. Wish it would reload the add-on
.setNotification(CardService.newNotification()
.setText('Created calendar event')
)
// HACK! Open a URL which closes itself in order to activate the RELOAD_ADD_ON side-effect
.setOpenLink(CardService.newOpenLink()
.setUrl("https://some_site.com/close_yoself.html")
.setOnClose(CardService.OnClose.RELOAD_ADD_ON))
.build();
The contents of close_yoself.html is just:
<!DOCTYPE html>
<html><body onload="self.close()"></body></html>
So, it looks like Google has considered and solved this issue for an ActionResponse which uses OpenLink, but not for one using Navigation or Notification. The hack above is definitely not great as it briefly opens and closes a browser window, but at least it refreshes the add-on without the user having to do so manually.
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');
Is it possible to selectively remove items from Google Chrome browsing history? I have a website from my history that wants to be the default everytime I start a search with a specific letter, but I often reference my history to re-find things.
So I would like to remove all history from, say, www.pythonismyfavoritest.com without removing everything; is that possible?
Try searching www.pythonismyfavoritest.com in the search bar in chrome://history/ and then remove each item by clicking the check box in the left and then hitting the "remove selected items" button.
The chrome history api works with url such chrome://history/#q=hello&p=0
Here's something I wrote in JavaScript. It works through the Console Debugger. I tried using it in a bookmark but I get no response from the page.
** // UPDATE (07.28.15)
I added a shorter approach provided by #Denis Gorbachev to the checkbox targeting, which helped shorten some of this code. I also added "auto-stop" functionality, meaning the loop will stop once it has finally cleared the list.
** // UPDATE (08.20.14)I made a few changes to the code, to make it more user friendly. Other users may not be code-savvy, and others may simply prefer convenience. Therefore, I whipped up a couple buttons (start/stop) to control the usage; as well as address some "ASSERTION FAILED" exceptions/errors that were being thrown when attempted to run the script loop.. Enjoy!!
In your address bar, type in the following address to to the meat of the history page.. It's normally loaded in an iframe, with the left-side menu loaded in another frame.. // **
chrome://history-frame/
Next, load your Console Debugger/Viewer by pressing Ctrl+Shift+J(For Mac users, ⌘+⌥+J)
You can also press F12 and select the "Console" tab.
In the Console Debugger/Viewer, copy & paste the following code:
function removeItems() {
removeButton = document.getElementById('remove-selected');
overlayWindow = document.getElementById('overlay');
//revision (07.28.15): Replaced the For Loop targeting the checkboxes, thanks to Denis Gorbachev via comments (02.19.15)
Array.prototype.forEach.call(document.querySelectorAll("input[type=checkbox]"), function(node) {node.checked = "checked"})
setTimeout(function () {
if (removeButton.getAttribute("disabled") !== null) {
removeButton.removeAttribute("disabled")
}
/* revision (08.20.14): no longer binding to that condition, button should no longer be disabled, so click! */
if ((overlayWindow.hasAttribute("hidden")) && (overlayWindow.getAttribute("hidden") !== false)) {
removeButton.click();
}
/* revision (08.20.14): new Interval, to check against the overlay DIV containing the confirmation "Remove" button */
/* Attempting to click the button while the DIV's "hidden" attribute is in effect will cause FAILED ASSERTION */
stopButton = setInterval(function () {
if (overlayWindow.hasAttribute("hidden")) {
if (overlayWindow.getAttribute("hidden") == "false") {
hidden = false
} else {
hidden = true
}
} else {
hidden = false
}
if (!hidden) {
document.getElementById("alertOverlayOk").click();
clearInterval(stopButton)
}
}, 250)
}, 250)
}
//revision (08.20.14): Lets build our buttons to control this so we no longer need the console
//stop button (08.20.14)
var stopButton = document.createElement('button');
stopButton.setAttribute('id', "stopButton");
stopButton.innerHTML = "Stop";
stopButton.style.background = "#800";
stopButton.style.color = "#fff";
stopButton.style.display = "none";
stopButton.onclick = function () {
clearInterval(window.clearAllFiltered);
document.getElementById("stopButton").style.display = "none";
document.getElementById("startButton").style.display = ""
};
//start button (08.20.14)
var startButton = document.createElement('button');
startButton.setAttribute('id', "startButton");
startButton.innerHTML = "Start";
startButton.style.background = "#090";
startButton.style.color = "#fff";
startButton.onclick = function () {
window.clearAllFiltered = setInterval(function () {
/* revision (07.28.15): Stop the Loop automatically if there are no more items to remove */
if(document.getElementById("results-header").innerText=="No search results found."){
document.getElementById("stopButton").click();
}
if (document.getElementById("loading-spinner").getAttribute("hidden") !== null) {
removeItems()
}
}, 250); //adjust Time Here (1500 [millisec] = 1.5sec)
document.getElementById("stopButton").style.display = "";
document.getElementById("startButton").style.display = "none"
};
/* revision (08.20.14): Now we add our buttons, and we're ready to go! */
editingControls = document.getElementById('editing-controls');
editingControls.appendChild(stopButton);
editingControls.appendChild(startButton);
This removeItems function will select loop through all form inputs and check all checkboxes, enable the "Remove Selected Items" button and click it. After a half-second, it'll check if the "Are You Sure" prompt is displayed and, if so, click the "Yes/Remove" button automatically for you so that it will load a new list of items to do this process all over again..
The item is looped using the variable "clearAllFiltered", which is a setInterval loop, which is checking for the status of the "Loading" screen..
To start erasing your filtered history items, you can now click the green Start button.
** // UPDATE (07.28.2015) It will now stop on ITS OWN.
To stop the loop manually, you can now click the red Stop button. Simple as that!
1) Go to your history settings ( chrome://history/ )
2) In the top right hand corner will be a search bar with a 'Search History" button
3) Type in the sitename you want to remove from history, then click the button
4) Click the box on the first one, then scroll to the bottom of the page
5) Press and hold the Shift key, then click the last box (This will check all on that page)
6) Scroll back up and select the 'Remove Selected Items" Button
7) Repeat steps 4-6 until all your Youtube History is gone.
Hopefully Chrome will update this clear history feature, but for now this seems to be the fastest option
Easy way is Shift+Delete.
For example when you type "you", "youtube.com" will be shown as selected in suggestions. Just click Shift+Delete. Then retype "you" and you will see no "youtube.com" in that list anymore.
If you are talking about getting rid of the suggested search/auto-completion... then removing specific items from your chrome://history won't do it (in my experience). I want to fill in more detail to the answer #LacOniC gave.
In the screenshot you can see I typed "ba" and Chrome is suggesting completion based on my browsing history (the items in green).
In my experience, removing specific items from your history will not remove them from showing up in this address bar auto-completion.
To quickly remove these auto complete items:
Start typing a few letters that generate the offending suggestion.
Use your keyboard's arrow keys to select the suggestion you don't like (selected item is highlighted blue in screenshot).
Press shift+delete on windows or shift+fn+delete on mac to remove the selected item.
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.
I have searched everywhere and cannot find a clear example of how to use Googles UI Builder and apps script. I have no clue what I'm missing. I think this should be simple :v/ YES, I've read all Googles docs, watched vids etc - several times - there is no combination of GUIB (Google's UI Builder) and a callback handler function, that I can find.
EDIT: there are examples for SpreadSheets - not GSites
What I need to do:
I would like to embed a textbox and button to collect a search phrase from a user, on a Google site page. I have built the very simple UI with a single flowpanel, textbox and button, but can only ever get "Undefined" returned from Logger.log() no matter what I do (see code below).
A bit of a rant:
I have been very careful to name, and call by the right names. I've tried using a formpanel BUT in GUIB, you can only put ONE widget in it?! ...AND a submit button will only go into a formpanel - huh - I can't put my text box in as well!? (Why bother with the formpanel then - I don't get that! ...yeah I know about doPost() automatically being called on submit). I want the widgets to remain active and not disappear after one use, so maybe formpanel/submitbutton won't work anyway - or isn't the right way to do it?
Down to business:
At any rate, what I've tried is to put the regular button and text box in a flowpanel with the following code...
EDIT: I deleted my original content here and reposted this section...
// Google Sites and UIBuilder (GUIB) - kgingeri (Karl)
// - this script is embedded in a GSite page via: Insert -> Apps Script Gadget.
//
// Withing GUIB I have defined:
// - a FlowPanel named 'pnlMain'
// - inside that a textBox named 'tbxQuery' and a button called 'btnSearch'
// - for btnSearch, I have defined (in the Events subsection) a callback function
// btnSearchHandler (see it below doGet() here. I expanded the [+] beside that
// and entered 'tbxQuery'
//
// the GUIB compnent tree looks like this...
//
// [-] testGui
// [-] pnlMain
// btnSearch
// tbxQuery
//
// btnSearch Event section looks something like this...
//
// Events
// On Mouse Clicks
// [X][btnSearchHandler][-]
// [tbxQuery ]<--'
// [Add Server]
// ...
//
// So...
// 1) when the page is opened, the doGet() function is called, showing the defined UI
// 2) when text is entered into the textBox and the button is clicked
// 3) the data from tbxQuery is **SUPPOSED TO BE** returned as e.parameter.tbxQuery
// in the function 'btnSearchHandler(e)' **BUT IS NOT** :v(
//
// (this functionality appears to work in a spreadsheet?! - weird?!)
//
// [ predefined function --- Google calls on page open ]
//
// ...this works 'as advertised' ;v)
//
function doGet(e) {
var app = UiApp.createApplication();
app.add(app.loadComponent("testGui")); // ...the title that shows in G/UIBuilder
return app;
}
//
// [ callBack for when a button is clicked ]
//
// ...I always get 'Resp: [Undefined]' returned in the View -> Logs menu?!
// ...I also tried to put 'pnlMain' in the Event [+] param, no go :v(
//
function btnSearchHandler(e) {
var resp = e.parameter.tbxQuery // ...the data I want in the textBox widget
Logger.log('Resp: [' + e.parameter.tbxQuery + ']');
// ...more code to come, once this works!
}
I've also tried adding code to manually set handlers etc in doGet(), and not use GUIB Event settings, but to no avail either.
Conclusion?
What Gives? Do I have to hand-code the GUIs and not use GUIB? I know it's a simple one this time, but if I can get this working I can sure see being much nicer to build other apps with GUIB! Can anyone give me or point me to a clear example?!
Thanks for reading!
here is a shared spreadsheet with an example of GUI builder
when you're in the GUI builder look at the properties of the element you want to trigger a function, at the end of the parameter list there is an 'EVENT' properties where you can add the function name and the callbackElements as well. !
Hoping it's clear enough,
cheers,
Serge
EDIT : if you want to have a look at a more complex example please open this one (create a copy of it to make it editable) or see it working here, I think you might be convinced that the GUI builder is a really powerfull tool .
Many thanks to Serge Insas!!
The answer is as shown below - I had missed two things:
the small [+] beside the On Mouse Click server handler - to add
a parameter to return
the Name is what is used NOT ID - set in
Input Fields section of tbxQuery
(NOTE: non-data elements don't have names - so fplMain has only an ID, but still works)
So, here is the resulting code, and comments describing GUIB settings:
// Google Sites and UIBuilder (GUIB) - kgingeri (Karl)
// - this script is embedded in a GSite page via: Insert -> Apps Script Gadget.
//
// Withing GUIB I have defined:
// - a FlowPanel named 'fplMain'
// - inside that, a textBox named 'tbxQuery' (see Input Fields section - this in NOT ID)
// and a button called 'btnSearch'
// - for btnSearch, I have defined (in the Events subsection) a callback function
// btnSearchHandler (see it below doGet() here). I expanded the [+] beside that,
// and entered "fplMain" as the return param (it will return all data elements)
//
// the GUIB compnent tree looks like this...
//
// [-] SearchGui
// [-] fplMain
// btnSearch
// tbxQuery
//
// "tbxQuery" Input Fields param, "Name"... **THIS MUST BE SET!
//
// Input Fields
// ...
// Name
// [tbxQuery ]
//
// "btnSearch" Event section looks like this...
//
// Events
// On Mouse Clicks
// [X][btnSearchHandler][-]
// [fplMain ]<--'
// [Add Server]
// ...
//
// So...
// 1) when the page is opened, the doGet() function is called, showing the defined UI
// 2) when text is entered into the textBox and the button is clicked
// 3) the data from tbxQuery is returned as e.parameter.tbxQuery (as would be any other
// params under the flow panel "fplMain") in the function 'btnSearchHandler(e)'
//
// [ predefined function --- Google calls on page open ]
//
function doGet(e) {
var app = UiApp.createApplication();
app.add(app.loadComponent("SearchGUI")); // ...the title you choose in G/UIBuilder
return app;
}
//
// [ callBack for when a button is clicked ]
//
function btnSearchHandler(e) {
var resp = e.parameter.tbxQuery // ...the data in the textBox widget
Logger.log('Resp: [' + e.parameter.tbxQuery + ']');
//
// ...more code goes here, to do something with the returned data
//
}