Is it possible to access google closure library functions from google app scripts via HtmlService? The html files in the google scripts seems to be filtering out anything related to closure library.
project: I am exploring DOM manipulation utilities from Google Closure library from within the google app scripts using HtmlService. I intend to run this as a stand alone web app.
The closure functions work when directly loaded into the browser from its local client environment - but they dont work when injected from GAS app via the HtmlService utility.
Here is the code I am using in the GAS.
html file
<html>
<head>
<script src="http://closure-library.googlecode.com/svn/trunk/closure/goog/base.js"></script>
<script>
goog.require('goog.dom');
function c_sayHi() {
var newHeader = goog.dom.createDom('h1', {'style': 'background-color:#EEE'},'Hello world!');
goog.dom.appendChild(document.body, newHeader);
}
</script>
</head>
<script>
function c_updateButton(date, button) {
button.value = "clicked at " + date;
}
</script>
<body onload="c_sayHi()">
<input type='button' value='Never Clicked'
onclick='google.script.run.withSuccessHandler(c_updateButton).withUserObject(this).s_getCurrentDate()'>
<input type='button' value='Never Clicked'
onclick='google.script.run.withSuccessHandler(c_updateButton).withUserObject(this).s_getCurrentDate()'>
</body>
</html>
Google Script file
function s_getCurrentDate() {
return new Date().toString();
}
function doGet(e) {
return HtmlService.createTemplateFromFile('hello').evaluate();
}
I have prefixed c_ to client side functions and s_ for server side fns. When running this as a web app,
Function c_sayHi has no effect - I am not sure if it is even invoked.
Functions s_getCurrentDate and c_updateButton work fine as described in google's documentation https://developers.google.com/apps-script/html_service.
Is there a way to get closure library working from the web apps as attempted above?
Couple of things here -
All .gs files is JavaScript that runs on the server side. So the DOM is not really relevant there.
You can run client side JavaScript by returning code in HtmlService. This is what I believe you want to do. However, jQuery is the best supported library on this approach. Closure might end up working but the team does not specifically test against that library.
The problem is that Closure's dependency structure is executing before the window load event, otherwise it will not work. So any require and provide statements are taken care of way before window load. When you inject them through the HTML Service, you are forcing their execution at a different stage then required, which causes everything to fail.
If you would be using a COMPILED Closure Library source, you will not have any problems with running Closure. Learn how to use the Compiler and Builder to make Closure Work properly. Also, you can use lazy loading to simulate your HTML Service.
With that, you can make javascript load dynamically onclick, onload or whatever the hell you want. This is called lazy-loading and it is used as a standard practice for all large web applications. Monitor the Network tab of Firebug when browsing through Gmail or Facebook.
Arun Nagarajan is right, jQuery is the easier solution but if you are doing something proper that requires breadth, scale and speed, jQuery is a toy for kids.
Related
I have the basic shell of a Chrome extension done and have come to the point where I am trying to inject an HTML signature into Gmail using code hosted on an unindexed page on my site. The reason I want to do this is to be able to include web fonts, something that for the life me I can't figure out why Gmail hasn't allowed you to do from their font library.
In any regard, as I said, I have a right-click context menu option ready to trigger a script from my js function page and the extension loads without errors. I need to figure out the best way to inject the HTML into the email and without losing any of the formatting that has been done on the page.
I have created the extension manifest, set the permissions on the context menu and created a function to call back to the js page that will inject the signature.
var contextMenus = {};
contextMenus.createSignature =
chrome.contextMenus.create(
{"title": "Inject Signature",
"contexts": ["editable"]},
function (){
if(chrome.runtime.lastError){
console.error(chrome.runtime.lastError.message);
}
}
);
chrome.contextMenus.onClicked.addListener(contextMenuHandler);
function contextMenuHandler(info, tab){
if(info.menuItemId===contextMenus.createSignature){
chrome.tabs.executeScript({
file: 'js/signature.js'
});
}
}
The end result is nothing enters the page and get massive errors related to cross-site because the domain is not the same obviously. This has obviously been solved as there are numerous signature extensions out there. I would probably use one of theirs but a) I want to build it on my own, b) they all want you to use their templates, none of them that I have seen will let you just use your own code.
So, any ideas?
I run the doGet() function. It creates a modal dialog on a spreadsheet. It will show a "Close" and "Make Copy" button where the latter will run a server-side function, doSomething(), that makes a copy of a template. Regardless of whether I attach the function to a button or run it straight in a script tag, it refuses to run. Is there anyway to fix or at the least debug this?
Code.gs
function doGet() {
return SpreadsheetApp.getUi().showModalDialog(HtmlService.createHtmlOutputFromFile('Index'), 'Report');
}
function doSomething() {
var file = template.makeCopy();
file.setName('NEW FILE NAME')
google.script.host.close()
}
Index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
google.script.run.doSomething()
</script>
</head>
<body>
<input type="button" value="Close" onclick="google.script.host.close()"/>
<input type="button" value="Make Copy" onclick="google.script.run.doSomething();" />
</body>
</html>
doGet() is a reserved word for web apps.
SpreadsheetApp.getUi only could be used on bounded projects
google.script.host.close() is a client-side method that only works for dialogs and sidebars not for web apps.
Suggestions:
As your project is a bounded project,
change the name of the doGet() function.
Remove google.script.host.close() from doSomething()
Remove
<script>
google.script.run.doSomething()
</script>
Once you make the above changes add menu to call your renamed function. If still doesn't work look for errors at the browser console for client-side errors and to Stackdriver logs for server-side errors.
Quotes
doGet(e) runs when a user visits a web app or a program sends an HTTP GET request to a web app.
google.script.host is an asynchronous client-side JavaScript API that can interact with dialogs or sidebars in Google Docs, Sheets, or Forms that contain HTML-service pages. To execute server-side functions from client-side code, use google.script.run. For more information, see the guide to communicating with server functions in HTML service.
References
https://developers.google.com/apps-script/guides/triggers/
https://developers.google.com/apps-script/guides/html/reference/host#close
In Apps Script, the doGet() and doPost() functions are strictly for sending HTTP requests to GAS-based web apps. Spreadsheet-bound scripts can be published as web apps - however, according to the docs
To create a web app with the HTML service, your code must include a
doGet() function that tells the script how to serve the page. The
function must return an HtmlOutput object, as shown in this example.
In your case, the showModalDialog() method returns 'void'. Also,
Unlike a web app, a script that creates a user interface for a
document, spreadsheet, or form does not need a doGet() function
specifically, and you do not need to save a version of your script or
deploy it. Instead, the function that opens the user interface must
pass your HTML file as an HtmlOutput object to the showModalDialog())
or showSidebar() methods of the Ui object for the active document,
form, or spreadsheet.
Long story short, you don't need to deploy your script as a web app. Instead, you should put the UI building code directly into your main function and, finally, tie that function to the button.
I'm trying to create an add-on for Google Docs using a modal dialog with the HTML Service but the time between running my script and something happening in the dialog window is pretty slow.
Here's a really simple example. (It's a little hack-y because calling foo from Example.html overwrites the first log)
// Code.gs
function openDialog() {
var html = HtmlService.createHtmlOutputFromFile('Example')
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
Logger.log("hi");
var temp = Logger.getLog();
DocumentApp.getUi()
.showModalDialog(html, temp);
}
function foo() {
Logger.log("bye");
}
and
// Example.html
<script>google.script.run.foo();</script>
If I run this there's a ~4 second difference between the first log and the second log. Is this just the way it is?
Short answer: Yes. Your approach is among the best ways to call a server-side function.
Long answer: Per documentation, try loading data asynchronously. It says -
Templated HTML can be used to quickly build simple interfaces, but its use should be limited to ensure your UI is responsive. The code in templates is executed once when the page is loaded, and no content is sent to the client until the processing is complete. Having long-running tasks in your scriptlet code can cause your UI to appear slow.
You could also try providing spinners / pre-loaders in the UI too and that should help with improving UX.
It also recommends us to Use the HTML5 document type declaration i.e.
If your page is served using the newer IFRAME sandbox mode, make sure to include the following snippet of code at the top of you HTML file.
<!DOCTYPE html>
Hope this helps.
Here's the thing.
A two-way communication (RPC-style) is needed between JavaScript on HTML pages provided by a web server online (with session-management and whatnot) and a windows EXE application running on the PC of the website visitor.
Both are parts of the same 'package' and should be able to communicate.
There is the use of a custom protocol for sure, but some browsers like Chrome & Safari sometimes have issues with custom protocol handling, so it is not reliable enough ...
Another possibility is to build a minimal web-server inside the EXE, so the communication would work with all browsers.
It is possible to develop an extension / plugin for each browsers separately, but it's a daunting task..
The usage of flash / java seems not possible for this task because of sandboxing, but I'm not sure about this ??
Do you have any other ideas ?
You can use an embedded ActiveX (COM) object and communicate between both platforms. I've done it (and would not have believed it possible had I not). It's nasty but it works. In the project I used it on I had no choice (which is about the only reason to ever do this). I built the COM object in C#.net and exposed an interface to COM for use on the page. It goes something like this:
function doSomethingInteresting() {
// in your js:
var obj = document.getElementById('yourObjectId');
obj.MethodNameDefinedOnYourCOMObject("someParameterValue");
}
// and your HTML looks like this; note that you can even catch events thrown from the COM object in Js...
<body>
<form>
<object id="yourObjectId" height="0" width="0" classid="clsid:99999999-9999-9999-9999-999999999999" onerror="oError()" VIEWASTEXT></object>
<script for="yourObjectId" event="ThisIsTheJavaScriptEventHandlerMethod(parameterName)" language="javascript">
// event handling here for the COM object
function yourObjectId::ThisIsTheJavaScriptEventHandlerMethod(parameterName) {
// you can process the parameterName passed from the object here
}
</script>
</form>
</body>
Happy coding!
How do hosted services like UserVoice embed their content on other web sites?
I see that it is via including a JavaScript file from the service provider on your own page, however, what I'm interested in are the building blocks for creating a service like that.
For example, do they use a library like jQuery, mooTools, or prototypejs and how do they avoid namespace clashes?
Also wondered if there were any books, articles, blog posts that go over this specific use of JavaScript (not looking for general resources on JavaScript).
Regards and thanks in advance,
Eliot
Here is a great tutorial I found on How to build a web widget (using jQuery)
Generally, what you are describing is called a "Javascript Widget" (UserVoice's just happens to show up on the side of the page).
There is a good tutorial about creating Javascript Widgets that you can check out.
The basic structure of such an embeddable service would be:
If the service doesn't mandate that the script is to be included at the bottom of the page, hook the body onload event, without stepping on the toes of any existing handlers (by intercepting the existing handler function, which could in turn be chained to other functions).
Inject new HTML elements into the document. The HTML code would most likely be inlined into the script as string literals as setting innerHTML on a single injected element would be easier and faster than direct DOM manipulation using a flurry of function calls.
The entire script should live inside a closure to avoid name clashes.
A JS framework may or may not be used; caution is required when including a framework since it could clash with a pre-existing, different framework, or a different version of the same framework.
EDT: Generally you'll make your client/customer/friend include a script in their page, then via that script you can do following:
In pure JS you can load scripts from remote location (or not so remote) dynamically via
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'your/remote/scripts/path.js';
document.getElementsByTagName('body')[0].appendChild(script);
// $.getScript('your/remote/scripts/path.js'); in jquery but you'll be sure jQuery loaded on remote site
Then script you loaded can perform different actions like creating elements like this
var body = document.getElementsByTagName('body')[0];
var aDiv = document.createElement('script');
/* here you can modify your divs properties and look */
body.appendChild(aDiv);
// $('').appendTo('body'); for jQuery
For deeper look into JavaScript you can read for example Javascript: The Good Parts or Definitive Guide To Javascript.