Looking for a way to create a custom menu on Google Apps Script available across ALL my Google Docs, not just the one for which I created the script. The function itself works properly, it is the setup/trigger rather that is giving me trouble. Not sure if this is possible, given I'm seeing info saying Custom Menus must be bound to a particular document (rather than the whole object), so open to other suggestions too - the goal is to be able to apply a specific template to a Google Doc when I'd like (they will always be created in the same folder).
function OnOpen(e) {
DocumentApp.getUi().createMenu('Template Options')
.addItem('Apply Customer Note Template', 'menuItem1')
.addToUi()
}
function menuItem1() {
var body = DocumentApp.getActiveDocument().getBody();
var OppTitle = body.appendParagraph("Opportunity Name");
OppTitle.setHeading(DocumentApp.ParagraphHeading.TITLE);
OppTitle.setAlignment(DocumentApp.HorizontalAlignment.CENTER);
...
DocumentApp.getUi().alert('Customer Template applied.');
}
Yes, you can do that, it is called Editor Addons. Just think about all the apps that exist in the Marketplace, they also create the app or function once, then package it for the Google Marketplace where any number of users can install the package and use it across all Spreadsheets.
Related
How do I add a custom function to Google Sheets from the Google Workspace add-on environment?
Note that this question is not about adding a custom functions for an Editor Add-on. This question is about building a Google Workspace add-on.
For reference, assume the custom function I want to add is as follows:
Custom function
function GETTAX( price, rate, ) {
const tax = price * rate;
const dollarUS = Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
});
const result = dollarUS.format( tax, );
return result;
}
How can I add a custom function to Sheets when my Google Workspace add-on is open?
Tl;Dr: It's not possible.
Custom functions works only in Editor add-ons, not in Workspace add-ons.
From the Official Docs
Google Developers > Google Apps Script > Guides > Sheets > Custom Functions
Specific page: https://developers.google.com/apps-script/guides/sheets/functions (links not included)
Creating a custom function
To write a custom function:
Create or open a spreadsheet in Google Sheets.
Select the menu item Extensions > Apps Script.
Delete any code in the script editor. For the DOUBLE function above, simply copy and paste the code into the script editor.
At the top, click Save save.
Now you can use the custom function.
Sharing
Custom functions start out bound to the spreadsheet they were created in. This means that a custom function written in one spreadsheet can't be used in other spreadsheets unless you use one of the following methods:
Click Extensions > Apps Script to open the script editor, then copy the script text from the original spreadsheet and paste it into the script editor of another spreadsheet.
Make a copy of the spreadsheet that contains the custom function by clicking File > Make a copy. When a spreadsheet is copied, any scripts attached to it are copied as well. Anyone who has access to the spreadsheet can copy the script. (Collaborators who have only view access cannot open the script editor in the original spreadsheet. However, when they make a copy, they become the owner of the copy and can see the script.)
Publish the script as a Google Sheets add-on.
Clarifications:
Google Sheets add-on refers to Editor add-on, not a Workspace add-on. To learn about Add-ons types see https://developers.google.com/apps-script/add-ons/concepts/types.
I have a call to a toast message within an installable onEdit trigger function that displays a message in the Google Sheets interface whenever an edit is made. The message shows up as expected for users that are logged in to their Google account, but it doesn't show up in the interface when the editor is anonymous.
I have a Sheets file that has anonymous editing enabled ("Anyone with the link"). There is a standalone Google Apps Script project that has installed an installable onEdit trigger. Everything in the function executes successfully for both anonymous and logged in users except for the toast message, which only shows up for logged in users.
The installable onEdit trigger is set up to execute the showMessage function.
Trigger Installed With:
ScriptApp.newTrigger('showMessage').forSpreadsheet('thefileid').onEdit().create();
showMessage Function:
function showMessage(e) {
var msg = 'Some msg';
var title = 'Some title';
var file = e.source;
var activeSheet = file.getActiveSheet();
file.toast(msg, title);
// do other things
}
The toast message appears for logged in users, not anonymous ones. The 'other things' in the function work as expected for everyone. I'm looking for a way to show anonymous users that message (or looking for some way to communicate automated messages to them). The script project is standalone and not container-bound, so I can't use the Ui class to notify them. Container bound scripts are not an option, as this script is substantial in size and gets run on multiple files.
You want to display a message when the cells of Spreadsheet are edited by anonymous.
The Spreadsheet is publicly shared for anonymous users as the editor.
If my understanding is correct, how about this answer? Unfortunately, even when the installed OnEdit event trigger is used, when anonymous users are edited, toast() and Class Ui cannot be used. So as one of several workaround, I would like to propose to use the images. Fortunately, insertImage() can be used for this situation. So I'm using this workaround. Please think of this as just one of several answers.
Before you use this script, please prepare an image for displaying.
Sample script:
Before you use this script, please set the file ID of the image. And please install the OnEdit event trigger for the function of showMessage().
function showMessage(e) {
var fileId = "###"; // Please set the file ID of the image.
var sheet = e.source.getActiveSheet();
var blob = DriveApp.getFileById(fileId).getBlob();
var image = sheet.insertImage(blob, 2, 3);
Utilities.sleep(3000);
image.remove();
}
In this sample script, when the cell is edited, the prepared image is displayed and waited for 3 seconds, and then, the image is removed.
Result:
Note:
Of course, you can create an image for displaying with the script. But in this case, the process cost will become high. As the result, the time until the image is displayed becomes long. So I proposed to use the image which was created in advance.
References:
toast()
Class Ui
Installable Triggers
insertImage()
If I misunderstood your question and this was not the direction you want, I apologize.
As you can see in the documentation:
Apps Script requires user authoritaztion to access private data from built-in Google Services or advanced Google services
That means you can share your script with anyone, but they need to log in to use the script.
After using the below code. App is launching the native android map showing the app with passed lat and long value.but my problem is after clicking on the nav option the 'from' is blank but 'to' should be coming as my passed value. But it is coming as blank value.
window.location = 'geo:40.765819,-73.975866'
If you are willing to use Cordova plugins, then I would suggest taking a look at the Launch Navigator plugin.
It allows you to do exactly what you want, but also allows you to launch other supported apps and even allows you to prompt the user with a list of applications to choose from.
There is an example in the documentation, showing how you can open a specific application, like Google Maps. For your convenience, I have also posted it below.
launchnavigator.isAppAvailable(launchnavigator.APP.GOOGLE_MAPS, function(isAvailable) {
var app;
if(isAvailable) {
app = launchnavigator.APP.GOOGLE_MAPS;
} else {
console.log("Google Maps not available - falling back to user selection");
app = launchnavigator.APP.USER_SELECT;
}
launchnavigator.navigate([40.765819, -73.975866], {
app: app
});
});
In this piece of code, the user will still be given a choice to pick another app, if Google Maps is not available.
There is also an AngularJS wrapper available for this called ngCordova, installation instructions are here and documentation about the wrapper for the Launch Navigator plugin can be found here.
Is it possible to show the documents from my drive on a webpage? I want the user to be able to click the document and download it, directly from my drive. How would I go about doing this? Thank you for your suggestions.
The fastest and easiest solution is to embed the folder using an iframe (no javascript needed). Obviously this is also the least flexible solution, although you can use CSS to change the layout of the iframe contents (see below).
Google Drive won't allow embedding of the url you would normally use. It has its X-Frame-Options header set to "SAMEORIGIN", preventing use in an iframe. So you have to use the following link, which will allow embedding:https://drive.google.com/embeddedfolderview?id=DOCUMENT_ID#VIEW_TYPE
DOCUMENT_ID is the id that is mentioned in the normal share link (which looks like https://drive.google.com/folderview?id=DOCUMENT_ID), so you can just copy that from there.
VIEW_TYPE should be either 'grid' or 'list', depending on your preference.
And if you need to change the style of the iframe content, take a look at this solution.
For HTML/JavaScript solution, look at the following links:
https://developers.google.com/drive/quickstart-js
https://www.youtube.com/watch?v=09geUJg11iA
https://developers.google.com/drive/web/auth/web-client
Here's the simplest way using JavaScript, most of the complexity is in
your WebApp authorization. The example below reads files IDs, names and description in a folder you specify.
- go to: https://cloud.google.com/console/project
and create a new project "xyz"
- Select "APIs & auth", disable the ones you don't need, enable "Drive API"
- Select "Credentials",
push "CREATE NEW CLIENT ID" button
x Web Application
Authorized Javascript origins: "https://googledrive.com/"
Authorized redirect URI: "https://googledrive.com/oauth2callback"
it will result in:
Client ID: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com
Email address: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx#developer.gserviceaccount.com
Client secret: xxxxxxxxxxxxxxxxxxxx
Redirect URIs: https://googledrive.com/oauth2callback
Javascript Origins: https://googledrive.com/
- in the code below, replace
CLIENT_ID with xxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com
FOLDER_ID with the ID you see in the folder address line,
https://drive.google.com/?tab=mo&authuser=0#folders/xxxxxxxxxxxxxxxxxxx
- run it, authorize
I don't know if you read JS, the code can be followed from bottom up, I made is as simple as possible.
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script>
var FOLDER_ID = '.xxxxxxxxxxxxxxxxxx'; // the folder files reside in
var CLIENT_ID = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com';
var SCOPE = //'https://www.googleapis.com/auth/drive';
[
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/drive.file', // for description,
];
function rsvpCB(resp) {
var picAlbumLst = '<ul>\n';
for (i=0; i<resp.items.length; i++)
picAlbumLst += (
' <li>'+resp.items[i].id+', '+resp.items[i].title+', '+resp.items[i].description+'</li>\n');
picAlbumLst += "</ul>\n";
$('#container').append(picAlbumLst);
}
function rqstCB() { //test # https://developers.google.com/drive/v2/reference/files/list
var rv = gapi.client.drive.files.list({
'q': '"'+FOLDER_ID+'" in parents and trashed = false',
'fields' : 'items(id,title,description)' //'items(id,title,description,indexableText)'
}).execute(rsvpCB);
}
// authorization server reply
function onAuthResult(authResult) {
var authButton = document.getElementById('authorizeButton');
authButton.style.display = 'none';
if (authResult && !authResult.error) { // access token successfully retrieved
gapi.client.load('drive', 'v2', rqstCB);
} else { // no access token retrieved, force the authorization flow.
authButton.style.display = 'block';
authButton.onclick = function() {
checkAuth(false);
}
}
}
// check if the current user has authorized the application.
function checkAuth(bNow) {
gapi.auth.authorize({'client_id':CLIENT_ID, 'scope':SCOPE, 'immediate':bNow}, onAuthResult);
}
// called when the client library is loaded, look below
function onLoadCB() {
checkAuth(true);
}
</script>
<script src="https://apis.google.com/js/client.js?onload=onLoadCB"></script>
<body style="background-color: transparent;">
<input type="button" id="authorizeButton" style="display: none" value="Authorize" />
<div id="container">
</div>
</body>
This should be done with Google API. You can search google drive php api list files on google. And also I found this and this on SO.
Here are some main points:
Do you want anyone with the URL to be able to see your document? You can share a document as public to anyone on the internet. Plus you can set read access to specific folders. Just right click a Google Doc file, and choose 'Share' from the short cut menu.
I'm assuming you want people to download your docs, even when you are not signed in. This is called 'Offline Access', and is one of many terms you'll need to figure out in order to do all of this with a program.
If you only want to give read access to the user, using JavaScript, jQuery, etc on the front end is a viable option. You can also do this in PHP, it's just a matter of personal preference.
To do all of this in code, you need to grant authorization to read your files. The oAuth2 process has multiple steps, and it's good to understand the basic flow. Setting up the code and the webpages to initially grant authorization, then retrieve and store the tokens can get confusing.
Your Google Project has a setting for where the origin of the authorization request is coming from. That is your website. But if you want to develop and test locally, you can set the Javascript Origins to http://localhost
How much time do you have, and how much programming experience? Would it be easier to give the user a few lines of instruction to "Manually" download your file, rather than program the authorization check?
Putting the document into your webpage is the easy part.
In order to embed a Google doc in your website, go to your Google Drive, open a document and choose File then Publish to Web, and you will be given an HTML iFrame Tag that can be embedded into you web page. You can change the height and width of the iFrame to match the document size. iFrame Instructions W3Schools
Downloading your document can be done very easily from the online version of a shared document just by choosing FILE and then DOWNLOAD AS from the menu.
To get up and running fast, just give the user a couple lines of instructions on how to download "Manually", then see if you can program the code.
Provide a link to your shared document instead of programming the button, and then work on the code.
Search Git Hub for Google Drive, you might find something there.
Some of the official Google code examples are way more complicated than you need, and will take a long time to figure out. The code examples in the documentation pages are simpler, but are almost never complete functioning code examples. You'll need to put lots of pieces of the puzzle together to make it work.
The scroller and other widgets in Palm's WebOS are commonly called like so:
this.controller.setupWidget(Mojo.Menu.appMenu, {}, this.appMenuModel);
within on of the JavaScript assistant files representing a 'scene'.
My application is dead simple and requires only one view, so I'm not using anything other than the stage-assistant file and an index.html file that contains links to various other JS and CSS files. Trying to call setupWidget like this results in an error (Uncaught TypeError: Cannot call method 'setupWidget' of object):
StageAssistant.prototype.setup = function () {
this.controller.setupWidget("widgetId",{},{});
}
In a sentence, I want to be able to initiate a Mojo HTML scroller widget from the stage assistant file.
The problem is there's no setupWidget method on the StageController class. You've got to have one scene or there's nothing to show on the stage. The whole Mojo widget system depends on this Stage/Scene hierarchy.