Standalone Script - Google Drive Picker - "is not a function" - google-apps-script

We are using google sheets for expense claims.
At the end of every month each employee fills out their expense claim and attaches their receipts on google drive through the sheet and then submits the data via email.
This has been already implemented and works well.
However we currently have a copy of the script attached to each sheet that we share with the employees so if there is an update to the code we have to do the update on every single document which is time consuming and error prone.
I would like to use one standalone script that is accessed from all of the google sheets shared with individuals.
I am having trouble with getting the drive picker working in the Standalone google script.
here is my picker.html file
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<script>
// IMPORTANT: Replace the value for DEVELOPER_KEY with the API key obtained
// from the Google Developers Console.
var DEVELOPER_KEY = 'purposely removed';
var DIALOG_DIMENSIONS = {width: 600, height: 425};
var pickerApiLoaded = false;
/**
* Loads the Google Picker API.
*/
function onApiLoad() {
gapi.load('picker', {'callback': function() {
pickerApiLoaded = true;
}});
}
/**
* Gets the user's OAuth 2.0 access token from the server-side script so that
* it can be passed to Picker. This technique keeps Picker from needing to
* show its own authorization dialog, but is only possible if the OAuth scope
* that Picker needs is available in Apps Script. Otherwise, your Picker code
* will need to declare its own OAuth scopes.
*/
function getOAuthToken() {
google.script.run.withSuccessHandler(createPicker)
.withFailureHandler(showError).getOAuthToken();
}
/**
* Creates a Picker that can access the user's spreadsheets. This function
* uses advanced options to hide the Picker's left navigation panel and
* default title bar.
*
* #param {string} token An OAuth 2.0 access token that lets Picker access the
* file type specified in the addView call.
*/
function createPicker(token) {
if (pickerApiLoaded && token) {
var picker = new google.picker.PickerBuilder()
// Instruct Picker to display only spreadsheets in Drive. For other
// views, see https://developers.google.com/picker/docs/#otherviews
.addView(google.picker.ViewId.SPREADSHEETS)
// Hide the navigation panel so that Picker fills more of the dialog.
.enableFeature(google.picker.Feature.NAV_HIDDEN)
// Hide the title bar since an Apps Script dialog already has a title.
.hideTitleBar()
.setOAuthToken(token)
.setDeveloperKey(DEVELOPER_KEY)
.setCallback(pickerCallback)
.setOrigin(google.script.host.origin)
// Instruct Picker to fill the dialog, minus 2 pixels for the border.
.setSize(DIALOG_DIMENSIONS.width - 2,
DIALOG_DIMENSIONS.height - 2)
.build();
picker.setVisible(true);
} else {
showError('Unable to load the file picker.');
}
}
/**
* A callback function that extracts the chosen document's metadata from the
* response object. For details on the response object, see
* https://developers.google.com/picker/docs/result
*
* #param {object} data The response object.
*/
function pickerCallback(data) {
var action = data[google.picker.Response.ACTION];
if (action == google.picker.Action.PICKED) {
var doc = data[google.picker.Response.DOCUMENTS][0];
var id = doc[google.picker.Document.ID];
var url = doc[google.picker.Document.URL];
var title = doc[google.picker.Document.NAME];
document.getElementById('result').innerHTML =
'<b>You chose:</b><br>Name: <a href="' + url + '">' + title +
'</a><br>ID: ' + id;
} else if (action == google.picker.Action.CANCEL) {
document.getElementById('result').innerHTML = 'Picker canceled.';
}
}
/**
* Displays an error message within the #result element.
*
* #param {string} message The error message to display.
*/
function showError(message) {
document.getElementById('result').innerHTML = 'Error: ' + message;
}
</script>
</head>
<body>
<div>
<button onclick='getOAuthToken()'>Select a file</button>
<p id='result'></p>
</div>
<script src="https://apis.google.com/js/api.js?onload=onApiLoad"></script>
</body>
</html>
I then get this:
Google Drive Picker
but when I click the Select a file button i get this:
Uncaught TypeError: google.script.run.withSuccessHandler(...).withFailureHandler(...).getOAuthToken is not a function
at getOAuthToken (userCodeAppPanel:20)
at HTMLButtonElement.onclick (userCodeAppPanel:1)
my Code.gs contains this function along with functions to process the selected files.
function getOAuthToken() {
DriveApp.getRootFolder();
return ScriptApp.getOAuthToken();
}
Let me know if you need further information.
I already tried this solution and it didn't work:
Cannot call Google Script API Functions from Web App (TypeError: Cannot read property 'run' of undefined)
Any help is much appreciated.
Thank you
How to reproduce the problem:
go to: https://script.google.com/
Create a new script.
In the Code.gs file put this:
function saonopen() {
var ui = SpreadsheetApp.getUi();
// add some functions to the UI
ui.createMenu('Menu')
.addItem('Add Attachments', 'tsc.showPicker')
.addToUi();
}
function showPicker() {
var html = HtmlService.createHtmlOutputFromFile('picker.html')
.setWidth(600)
.setHeight(425)
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
SpreadsheetApp.getUi().showModalDialog(html, 'Select a file');
}
function processFilesChosen(ids, titles, urls) {
var targetSheetCost = switchTargetMonth('sheet').cost;
// record the files on the target cost sheet
for (var i in titles) {
i = parseInt(i)
targetSheetCost.getRange("H"+(i+3)).setFormula('=HYPERLINK("'+urls[i]+'","'+titles[i]+'")');
targetSheetCost.getRange("I"+(i+3)).setValue(ids[i]);
// Because of Google security update, we can not use this setSharing.
// DriveApp.getFileById(ids[i]).setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.COMMENT);
}
}
function getOAuthToken() {
DriveApp.getRootFolder();
return ScriptApp.getOAuthToken();
}
Add a new html file, call it picker.html and put the code posted earlier under picker.html.
Publish the script as a API Executable and note the API ID
create a new google sheet and import the standalone script as a library from resources menu on the script view using the API ID noted earlier. It will as for an identifier - put:
tsc
In the Code.gs file put:
function onOpen() {
tsc.saonopen()
}
Finally enable Picker and Drive API Access for both projects.
Drive & Picker API Access

Related

Update the card when a Dialog is closed

I want to known is there any way to update the card when I close the dialog
//this function is called when click a button in the card
function showDialog() {
const html = HtmlService.createHtmlOutputFromFile('dialog.html')
.setWidth(600)
.setHeight(425)
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
SpreadsheetApp.getUi().showModalDialog(html, 'Select the spreadsheets');
}
//this function is called by client code
function callback(text) {
var card = CardService.newCardBuilder().build();
var navigation = CardService.newNavigation().updateCard(card);
return CardService.newActionResponseBuilder()
.setNavigation(navigation)
.setNotification('Import Succeed')
.build();
}
dialog.html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<script>
function onApiLoad() {
gapi.load('picker', {'callback': function() {
......
}});
}
function createPicker(token) {
if (pickerApiLoaded && token) {
var picker = new google.picker.PickerBuilder()
.addView(new google.picker.DocsView(google.picker.ViewId.SPREADSHEETS)
.setMode(google.picker.DocsViewMode.LIST)
.setCallback(pickerCallback)
.........
.build();
picker.setVisible(true);
} else {
showError('Unable to load the file picker.');
}
}
function pickerCallback(data) {
var action = data[google.picker.Response.ACTION];
if (action == google.picker.Action.PICKED) {
google.script.run.callback('');
google.script.host.close();
} else if (action == google.picker.Action.CANCEL) {
google.script.host.close();
}
}
</script>
</head>
<body>
<script src="https://apis.google.com/js/api.js?onload=onApiLoad"></script>
</body>
</html>
I can write the text in the current spreadsheet developer metadata, and check the developer metadata in a dead loop in the card like the below code, but if user didn't close the dialog in time, google will throw timeout error in the card
//this function is called when click a button in the card
function showDialog() {
const html = HtmlService.createHtmlOutputFromFile('dialog.html')
.setWidth(600)
.setHeight(425)
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
SpreadsheetApp.getUi().showModalDialog(html, 'Select the spreadsheets');
//But I must return a response within a certain time since google has time limit for response
var metadataList = SpreadsheetApp.getActive().getDeveloperMetadata();
//find the text in the metadataList, and return the response
}
//this function is called by client code
function callback(text) {
//updat the card with text
SpreadsheetApp.getActive().addDeveloperMetadata('text', text);
}
Tl;Dr There is no way to update a card when a dialog is closed.
While an Editor add-on and Workspace add-on might share the same Google Apps Script project / Google Cloud project they are still two different add-ons. At this time there is no way to directly communicate two add-ons.
Workspace add-ons don't have triggers for file edit / changes, only for user clicking a card button.
For your current approach, I think that the only thing that you can do is to provide instructions to the user to reopen the card.
Related
Update Google Calendar UI after changing visability setting via Workspace Add-On
How to fully refresh Google Addon Card (Google Sheets) which includes a drop down menu populated using sheet data when data changes?
Is it possible to refresh the Google Workspace Add-on sidebar card from installable trigger function?

How can I open the File > Import function in Google Sheets with a script?

I wanted to specify that I've got the code below as the starting point of a much longer script. This works as intended, but requires the user to know the name of the file in Drive in order to type it in. I want to replace this part of my script with a different piece that instead allows the user to look at the files in their drive and select the text file to import and then pass on to the next section of script.
I have the code below that I've put together and it asks the user for a file name, but I'd rather open the File > Import dialog and allow the user to select the file that it loads into the script for scrubbing. I've tried using the File Picker code to process this but it opens the file in a smaller dialog window and I'm not sure if it's possible to pass it into the script to scrub it. And when I used that dialog it generated an error when attempting to open a .csv/.txt and stated that Google failed to connect.
I've played with the Picker script function that I've found elsewhere which looks like it could work, but I'm not sure how to take the file selected and then continue on to the next part of my script.
Picker script
/**
* Creates a custom menu in Google Sheets when the spreadsheet opens.
*/
function onOpen() {
SpreadsheetApp.getUi().createMenu('Picker')
.addItem('Start', 'showPicker')
.addToUi();
}
/**
* Displays an HTML-service dialog in Google Sheets that contains client-side
* JavaScript code for the Google Picker API.
*/
function showPicker() {
var html = HtmlService.createHtmlOutputFromFile('dialog.html')
.setWidth(600)
.setHeight(425)
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
SpreadsheetApp.getUi().showModalDialog(html, 'Select a file');
}
function doSomething(data){
Logger.log('Your sheet ID selected ' + data.docs[0].id);
Logger.log(data);
SpreadsheetApp.openById(id)
}
/**
* Gets the user's OAuth 2.0 access token so that it can be passed to Picker.
* This technique keeps Picker from needing to show its own authorization
* dialog, but is only possible if the OAuth scope that Picker needs is
* available in Apps Script. In this case, the function includes an unused call
* to a DriveApp method to ensure that Apps Script requests access to all files
* in the user's Drive.
*
* #return {string} The user's OAuth 2.0 access token.
*/
function getOAuthToken() {
DriveApp.getRootFolder();
return ScriptApp.getOAuthToken();
}
HTML Script
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<script>
// IMPORTANT: Replace the value for DEVELOPER_KEY with the API key obtained
// from the Google Developers Console.
var DEVELOPER_KEY = 'My key is here';
var DIALOG_DIMENSIONS = {width: 600, height: 425};
var pickerApiLoaded = false;
/**
* Loads the Google Picker API.
*/
function onApiLoad() {
gapi.load('picker', {'callback': function() {
pickerApiLoaded = true;
}});
}
/**
* Gets the user's OAuth 2.0 access token from the server-side script so that
* it can be passed to Picker. This technique keeps Picker from needing to
* show its own authorization dialog, but is only possible if the OAuth scope
* that Picker needs is available in Apps Script. Otherwise, your Picker code
* will need to declare its own OAuth scopes.
*/
function getOAuthToken() {
google.script.run.withSuccessHandler(createPicker)
.withFailureHandler(showError).getOAuthToken();
}
/**
* Creates a Picker that can access the user's spreadsheets. This function
* uses advanced options to hide the Picker's left navigation panel and
* default title bar.
*
* #param {string} token An OAuth 2.0 access token that lets Picker access the
* file type specified in the addView call.
*/
function createPicker(token) {
if (pickerApiLoaded && token) {
var picker = new google.picker.PickerBuilder()
// Instruct Picker to display only spreadsheets in Drive. For other
// views, see https://developers.google.com/picker/docs/#otherviews
.addView(google.picker.ViewId.DOCS)
// Hide the navigation panel so that Picker fills more of the dialog.
.enableFeature(google.picker.Feature.NAV_HIDDEN)
// Hide the title bar since an Apps Script dialog already has a title.
.hideTitleBar()
.setOAuthToken(token)
.setDeveloperKey(DEVELOPER_KEY)
.setCallback(pickerCallback)
.setOrigin(google.script.host.origin)
// Instruct Picker to fill the dialog, minus 2 pixels for the border.
.setSize(DIALOG_DIMENSIONS.width - 2,
DIALOG_DIMENSIONS.height - 2)
.build();
picker.setVisible(true);
} else {
showError('Unable to load the file picker.');
}
}
/**
* A callback function that extracts the chosen document's metadata from the
* response object. For details on the response object, see
* https://developers.google.com/picker/docs/result
*
* #param {object} data The response object.
*/
function pickerCallback(data) {
console.log(data);
var action = data[google.picker.Response.ACTION];
if (action == google.picker.Action.PICKED) {
var doc = data[google.picker.Response.DOCUMENTS][0];
var id = doc[google.picker.Document.ID];
var url = doc[google.picker.Document.URL];
var title = doc[google.picker.Document.NAME];
document.getElementById('result').innerHTML =
'<b>You chose:</b><br>Name: <a href="' + url + '">' + title +
'</a><br>ID: ' + id;
google.script.run.doSomething(data);
} else if (action == google.picker.Action.CANCEL) {
document.getElementById('result').innerHTML = 'Picker canceled.';
}
}
/**
* Displays an error message within the #result element.
*
* #param {string} message The error message to display.
*/
function showError(message) {
document.getElementById('result').innerHTML = 'Error: ' + message;
}
</script>
</head>
<body>
<div>
<button onclick='getOAuthToken()'>Select a file</button>
<p id='result'></p>
</div>
<script src="https://apis.google.com/js/api.js?onload=onApiLoad"></script>
</body>
</html>
You can use Google Picker to achieve what you desire, please follow the steps in this link:
File-open dialogs
Let Know if it works for you
Edit
After doing all previous steps, in your Apps Script code create a function with any name you want and an argument
function doSomething(data){
Logger.log('Your Sheet ID selected ' + data.docs[0].id);
Logger.log(data);
// with the id you could use SpreadsheetApp.openById("ID");
// and then do all you want to do
}
Then call it in your pickerCallback function in your html using the class google.script.run:
google.script.run.doSomething(data);
The pickerCallback function will look like this:
function pickerCallback(data) {
console.log(data);
var action = data[google.picker.Response.ACTION];
if (action == google.picker.Action.PICKED) {
var doc = data[google.picker.Response.DOCUMENTS][0];
var id = doc[google.picker.Document.ID];
var url = doc[google.picker.Document.URL];
var title = doc[google.picker.Document.NAME];
document.getElementById('result').innerHTML =
'<b>You chose:</b><br>Name: <a href="' + url + '">' + title +
'</a><br>ID: ' + id;
// Pass Values to your script
google.script.run.doSomething(data);
} else if (action == google.picker.Action.CANCEL) {
document.getElementById('result').innerHTML = 'Picker canceled.';
}
}
Notice
As this part of the code in the html says
// Instruct Picker to display only spreadsheets in Drive. For other
// views, see https://developers.google.com/picker/docs/#otherviews
.addView(google.picker.ViewId.SPREADSHEETS)
You need to change those parameters if you want to open other types of files

Google picker API error: Server IP address n-utbhwlxa4witl3o3ev4uschzd37oxgge2qt73xy-0lu-script.googleusercontent.com was not found

I have a problem with running Google picker API.
I'm trying to make a custom menu function for my Google spreadsheet that I can choose a file with (obtain file ID on Google Drive) and then my apps-script will automatically rename it according to result in some cell in sheet and then it will also move the file to a proper folder on Google Drive. I think I'm able to do everything mentioned above except to run Google picker API.
To do task described above I think I need Google Picker API. Please correct me if I'm wrong!!! I just need a dialog window which will let me choose a file and let give me file ID of the chosen file.
So basically I have followed instructions for File-open dialogs here and here. I've tried it many many times. I've tried every help I could find on youtube or google, but the result is still the same:
When I open my spreadsheet, I see my new custom menu, I open it and click on Start which runs the script. The script opens a blank window with "Select a file". After a while I get this statement:
The statement means "Server IP address n-utbhwlxa4witl3o3ev4uschzd37oxgge2qt73xy-0lu-script.googleusercontent.com was not found" (Address looks like Client ID, but it's definiteliy not the ID i see in web console.)
I've enabled Google Picker Api and Google drive API in developers console, I've also copied API_KEY to the code. I think I've done everything described in instuctions right. But I've done nothing more. I'm afraid that I'm missing some basic step which is obvious for skilled developer, but I've no clue. (Like if I have to specify Authorized JavaScript origins for OAuth 2.0 client IDs etc.)
Anybody any idea or direction, please?
EDIT:
My .gs code is (copy-pasted from https://developers.google.com/apps-script/guides/dialogs):
/**
* Creates a custom menu in Google Sheets when the spreadsheet opens.
*/
function onOpen() {
SpreadsheetApp.getUi().createMenu('Picker')
.addItem('Start', 'showPicker')
.addToUi();
}
/**
* Displays an HTML-service dialog in Google Sheets that contains client-side
* JavaScript code for the Google Picker API.
*/
function showPicker() {
var html = HtmlService.createHtmlOutputFromFile('filepicker.html')
.setWidth(600)
.setHeight(425)
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
SpreadsheetApp.getUi().showModalDialog(html, 'Select a file');
}
/**
* Gets the user's OAuth 2.0 access token so that it can be passed to Picker.
* This technique keeps Picker from needing to show its own authorization
* dialog, but is only possible if the OAuth scope that Picker needs is
* available in Apps Script. In this case, the function includes an unused call
* to a DriveApp method to ensure that Apps Script requests access to all files
* in the user's Drive.
*
* #return {string} The user's OAuth 2.0 access token.
*/
function getOAuthToken() {
DriveApp.getRootFolder();
return ScriptApp.getOAuthToken();
}
and my html code (also copy-pasted from the same source) is as follows (I have not changed anything except Developers key):
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<script>
// IMPORTANT: Replace the value for DEVELOPER_KEY with the API key obtained
// from the Google Developers Console.
var DEVELOPER_KEY = 'I put my own key here';
var DIALOG_DIMENSIONS = {width: 600, height: 425};
var pickerApiLoaded = false;
/**
* Loads the Google Picker API.
*/
function onApiLoad() {
gapi.load('picker', {'callback': function() {
pickerApiLoaded = true;
}});
}
/**
* Gets the user's OAuth 2.0 access token from the server-side script so that
* it can be passed to Picker. This technique keeps Picker from needing to
* show its own authorization dialog, but is only possible if the OAuth scope
* that Picker needs is available in Apps Script. Otherwise, your Picker code
* will need to declare its own OAuth scopes.
*/
function getOAuthToken() {
google.script.run.withSuccessHandler(createPicker)
.withFailureHandler(showError).getOAuthToken();
}
/**
* Creates a Picker that can access the user's spreadsheets. This function
* uses advanced options to hide the Picker's left navigation panel and
* default title bar.
*
* #param {string} token An OAuth 2.0 access token that lets Picker access the
* file type specified in the addView call.
*/
function createPicker(token) {
if (pickerApiLoaded && token) {
var picker = new google.picker.PickerBuilder()
// Instruct Picker to display only spreadsheets in Drive. For other
// views, see https://developers.google.com/picker/docs/#otherviews
.addView(google.picker.ViewId.SPREADSHEETS)
// Hide the navigation panel so that Picker fills more of the dialog.
.enableFeature(google.picker.Feature.NAV_HIDDEN)
// Hide the title bar since an Apps Script dialog already has a title.
.hideTitleBar()
.setOAuthToken(token)
.setDeveloperKey(DEVELOPER_KEY)
.setCallback(pickerCallback)
.setOrigin(google.script.host.origin)
// Instruct Picker to fill the dialog, minus 2 pixels for the border.
.setSize(DIALOG_DIMENSIONS.width - 2,
DIALOG_DIMENSIONS.height - 2)
.build();
picker.setVisible(true);
} else {
showError('Unable to load the file picker.');
}
}
/**
* A callback function that extracts the chosen document's metadata from the
* response object. For details on the response object, see
* https://developers.google.com/picker/docs/result
*
* #param {object} data The response object.
*/
function pickerCallback(data) {
var action = data[google.picker.Response.ACTION];
if (action == google.picker.Action.PICKED) {
var doc = data[google.picker.Response.DOCUMENTS][0];
var id = doc[google.picker.Document.ID];
var url = doc[google.picker.Document.URL];
var title = doc[google.picker.Document.NAME];
document.getElementById('result').innerHTML =
'<b>You chose:</b><br>Name: <a href="' + url + '">' + title +
'</a><br>ID: ' + id;
} else if (action == google.picker.Action.CANCEL) {
document.getElementById('result').innerHTML = 'Picker canceled.';
}
}
/**
* Displays an error message within the #result element.
*
* #param {string} message The error message to display.
*/
function showError(message) {
document.getElementById('result').innerHTML = 'Error: ' + message;
}
</script>
</head>
<body>
<div>
<button onclick='getOAuthToken()'>Select a file</button>
<p id='result'></p>
</div>
<script src="https://apis.google.com/js/api.js?onload=onApiLoad"></script>
</body>
</html>
Fortunately, Amit Agarwal wrote a tutorial on this blog about using Picker API in Apps Script.
Here's a snippet:
function onOpen() {
SpreadsheetApp.getUi().createMenu('Google Picker')
.addItem('Choose Folder', 'showPicker')
.addToUi();
}
/**
* Displays an HTML-service dialog in Google Sheets that contains client-side
* JavaScript code for the Google Picker API.
*/
function showPicker() {
var html = HtmlService.createHtmlOutputFromFile('Picker.html')
.setWidth(600)
.setHeight(425)
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
SpreadsheetApp.getUi().showModalDialog(html, 'Select Folder');
}
function getOAuthToken() {
DriveApp.getRootFolder();
return ScriptApp.getOAuthToken();
}

To perform this action, you need authorization

Tell me please what changed the Google script, all scripts with HtmlService, after a new publication or when viewing the latest version of the code give an error - To perform this action, you need authorization. Where to make authorizations. So far, everything worked well.
According to this documentation, scripts are made file-open_dialogs
CODE.GS
/**
* Creates a custom menu in Google Sheets when the spreadsheet opens.
*/
function onOpen() {
SpreadsheetApp.getUi().createMenu('Picker')
.addItem('Start', 'showPicker')
.addToUi();
}
/**
* Displays an HTML-service dialog in Google Sheets that contains client-side
* JavaScript code for the Google Picker API.
*/
function showPicker() {
var html = HtmlService.createHtmlOutputFromFile('Picker.html')
.setWidth(600)
.setHeight(425)
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
SpreadsheetApp.getUi().showModalDialog(html, 'Select a file');
}
/**
* Gets the user's OAuth 2.0 access token so that it can be passed to Picker.
* This technique keeps Picker from needing to show its own authorization
* dialog, but is only possible if the OAuth scope that Picker needs is
* available in Apps Script. In this case, the function includes an unused call
* to a DriveApp method to ensure that Apps Script requests access to all files
* in the user's Drive.
*
* #return {string} The user's OAuth 2.0 access token.
*/
function getOAuthToken() {
DriveApp.getRootFolder();
return ScriptApp.getOAuthToken();
}
PICKER.HTML
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<script>
// IMPORTANT: Replace the value for DEVELOPER_KEY with the API key obtained
// from the Google Developers Console.
var DEVELOPER_KEY = 'ABC123 ... ';
var DIALOG_DIMENSIONS = {width: 600, height: 425};
var pickerApiLoaded = false;
/**
* Loads the Google Picker API.
*/
function onApiLoad() {
gapi.load('picker', {'callback': function() {
pickerApiLoaded = true;
}});
}
/**
* Gets the user's OAuth 2.0 access token from the server-side script so that
* it can be passed to Picker. This technique keeps Picker from needing to
* show its own authorization dialog, but is only possible if the OAuth scope
* that Picker needs is available in Apps Script. Otherwise, your Picker code
* will need to declare its own OAuth scopes.
*/
function getOAuthToken() {
google.script.run.withSuccessHandler(createPicker)
.withFailureHandler(showError).getOAuthToken();
}
/**
* Creates a Picker that can access the user's spreadsheets. This function
* uses advanced options to hide the Picker's left navigation panel and
* default title bar.
*
* #param {string} token An OAuth 2.0 access token that lets Picker access the
* file type specified in the addView call.
*/
function createPicker(token) {
if (pickerApiLoaded && token) {
var picker = new google.picker.PickerBuilder()
// Instruct Picker to display only spreadsheets in Drive. For other
// views, see https://developers.google.com/picker/docs/#otherviews
.addView(google.picker.ViewId.SPREADSHEETS)
// Hide the navigation panel so that Picker fills more of the dialog.
.enableFeature(google.picker.Feature.NAV_HIDDEN)
// Hide the title bar since an Apps Script dialog already has a title.
.hideTitleBar()
.setOAuthToken(token)
.setDeveloperKey(DEVELOPER_KEY)
.setCallback(pickerCallback)
.setOrigin(google.script.host.origin)
// Instruct Picker to fill the dialog, minus 2 pixels for the border.
.setSize(DIALOG_DIMENSIONS.width - 2,
DIALOG_DIMENSIONS.height - 2)
.build();
picker.setVisible(true);
} else {
showError('Unable to load the file picker.');
}
}
/**
* A callback function that extracts the chosen document's metadata from the
* response object. For details on the response object, see
* https://developers.google.com/picker/docs/result
*
* #param {object} data The response object.
*/
function pickerCallback(data) {
var action = data[google.picker.Response.ACTION];
if (action == google.picker.Action.PICKED) {
var doc = data[google.picker.Response.DOCUMENTS][0];
var id = doc[google.picker.Document.ID];
var url = doc[google.picker.Document.URL];
var title = doc[google.picker.Document.NAME];
document.getElementById('result').innerHTML =
'<b>You chose:</b><br>Name: <a href="' + url + '">' + title +
'</a><br>ID: ' + id;
} else if (action == google.picker.Action.CANCEL) {
document.getElementById('result').innerHTML = 'Picker canceled.';
}
}
/**
* Displays an error message within the #result element.
*
* #param {string} message The error message to display.
*/
function showError(message) {
document.getElementById('result').innerHTML = 'Error: ' + message;
}
</script>
</head>
<body>
<div>
<button onclick='getOAuthToken()'>Select a file</button>
<p id='result'></p>
</div>
<script src="https://apis.google.com/js/api.js?onload=onApiLoad"></script>
</body>
</html>
If I delete these functions from the code, it starts displaying HTML.
function onOpen() {
SpreadsheetApp.getUi().createMenu('Picker')
.addItem('Start', 'showPicker')
.addToUi();
}
function onApiLoad() {
gapi.load('picker', {'callback': function() {
pickerApiLoaded = true;
}});
}
What's interesting is if these functions are commented out, it does not help to correctly display HTML, and if it's to remove it starts to display.
https://script.google.com/macros/s/AKfycbybeJehYuPwQvhbaNViClAflBZVg6AV0Dh60Z-4RYA/dev

Google Picker - Return the File ID to my Google Script

I have a fairly basic spreadsheet that uses some Google Scripts to accomplish various tasks. I was trying to cleanup the interface for the end user, and decided to implement the Google Picker. Originally the user had to manually import a CSV into the spreadsheet. The new goal here is to select the CSV via the Google Picker, upload it, import it, then delete it. I already have all the code working to import it and delete it. I just worked up the code for the picker, and it seems to work fine. However, and I think I'm just missing something small, how do I pass the File ID back from the Picker.html to my Google Scripts in order to continue my process?
If it helps, I'm using the basic callback provided in the Google documentation right now. I'm assuming this is where the change will be made. Just not sure what to do.
function pickerCallback(data) {
var action = data[google.picker.Response.ACTION];
if (action == google.picker.Action.PICKED) {
var doc = data[google.picker.Response.DOCUMENTS][0];
var id = doc[google.picker.Document.ID];
var url = doc[google.picker.Document.URL];
var title = doc[google.picker.Document.NAME];
document.getElementById('result').innerHTML =
'<b>You chose:</b><br>Name: ' + title + '<br>ID: ' + id;
} else if (action == google.picker.Action.CANCEL) {
document.getElementById('result').innerHTML = 'Picker canceled.';
}
}
This should probably work:
In your pickerCallback(data) function:
if (data.action == google.picker.Action.PICKED) {
var fileId = data.docs[0].id;
google.script.run
.withSuccessHandler(useData) // this will call the google apps script function in your Code.gs file
.doSomething(fileId); // this is a function in your JavaScript section where you will do something with the code you got from your apps script function
}
function useData(data) {
// do something with the data
}
In Code.gs, create a function to handle the input from the picker:
function doSomething(fileId) {
// do an operation in Drive with the fileId
var file = DriveApp.getFileById(fileId);
var fileName = file.getName();
return fileName;
}
First of all, open the chrome developer console when you are running this so you can see any errors that happen client side (when the picker is active). You can also use console.log to report any variable values in the Chrome console.
secondly, the call to the server works asynchronously, so it means that in your code, you'll get your message 'script was run', when it fact it hasn't yet. All that's happened is that google.script.run has asked for your server side function to execute.
That's why you have withSuccessHandler and withFailureHandler.
so you should do
google.script.run
.withSuccessHandler (function (response) {
document.getElementById('result').innerHTML = 'it worked'
})
.withFailureHandler (function (err) {
document.getElementById('result').innerHTML = err;
})
.justatest (fileId);
and back in the server script
function justatest(fileId) {
Logger.log (fileId);
}
If you then go back and look in the script log file, you should see the fileId.