I have a project set up like this:
TestProject
-- Code.gs
-- 14885.gs
-- 23546.gs
I want to create a variable and set its value in "Code.gs" file. Then I should be able to access it in 14885.gs and 23456.gs files.
In Code.gs file, I used the PropertiesServices and created a variable that I wanted to access across other "gs" files under the project.
In Code.gs file
function onEdit(e){
totalHoursLoggedForStory_today = Number(e.range.getSheet().getRange("H3").getValue()) + Number(e.range.getSheet().getRange("H4").getValue()) + Number(e.range.getSheet().getRange("H5").getValue());
var userProperties = PropertiesService.getUserProperties();
var newProperties = {'hoursLogged': totalHoursLoggedForStory_today };
userProperties.setProperties(newProperties);
onEditOfH3(e);
}
In 23546.gs file
function onEditOfH3(e){
if (e.range.getA1Notation() == "H3")
{
var temp= userProperties.getProperty('hoursLogged');
sh.alert(temp);
}
Expected: In the 23546.gs file, I should be able to alert the property that I set in "Code.gs" file.
Actual: I am seeing this error: ReferenceError: userProperties is not defined
You want to use userProperties with the value, which is given in the function of onEdit, at the function of onEditOfH3, when onEdit is run.
You want to understand about the specification of the files in one GAS project.
If my understanding is correct, how about this answer? Please think of this as just one of several answers.
About all files in one GAS project:
At the project of Google Apps Script, all files in the project are used as one project. Namely, for example, when the following sample script is run in the file of Code.gs,
function myFunction() {
for (var i in this) {
if (typeof this[i] == "function") {
Logger.log(i)
}
}
}
all functions in all files in the project are returned. From this, it is found that when a global variable is declared at the file of Code.gs, this variable can be used at other file in the same project. At the following pattern 1, this is used.
Pattern 1:
In this pattern, userProperties is declared as the global variable.
Modified script:
Code.gs
var userProperties; // This is declared as the global variable.
function onEdit(e){
totalHoursLoggedForStory_today = Number(e.range.getSheet().getRange("H3").getValue()) + Number(e.range.getSheet().getRange("H4").getValue()) + Number(e.range.getSheet().getRange("H5").getValue());
userProperties = PropertiesService.getUserProperties(); // Modified
var newProperties = {'hoursLogged': totalHoursLoggedForStory_today };
userProperties.setProperties(newProperties);
onEditOfH3(e);
}
23546.gs
This is not required to be modified.
Pattern 2:
In this pattern, userProperties is added to the object of e. And the value is used as e.userProperties in the function of onEditOfH3. If you don't want to use the global variable, how about this? Also, userProperties can be sent as another argument.
Modified script:
Code.gs
function onEdit(e){
totalHoursLoggedForStory_today = Number(e.range.getSheet().getRange("H3").getValue()) + Number(e.range.getSheet().getRange("H4").getValue()) + Number(e.range.getSheet().getRange("H5").getValue());
var userProperties = PropertiesService.getUserProperties();
var newProperties = {'hoursLogged': totalHoursLoggedForStory_today };
userProperties.setProperties(newProperties);
e.userProperties = userProperties; // Added
onEditOfH3(e);
}
23546.gs
function onEditOfH3(e){
// var sh = SpreadsheetApp.getUi(); // In your whole script, this might be declared at elsewhere.
if (e.range.getA1Notation() == "H3") {
var temp = e.userProperties.getProperty('hoursLogged'); // Modified
sh.alert(temp);
}
}
Reference:
Script Projects
Doesn't this:
function onEditOfH3(e){
if (e.range.getA1Notation() == "H3") {
var temp= userProperties.getProperty('hoursLogged');
sh.alert(temp);
}
have to be like this:
function onEditOfH3(e){
if (e.range.getA1Notation() == "H3") {
var temp= PropertiesService.getUserProperties().getProperty('hoursLogged');
sh.alert(temp);
}
Related
I made a script using the advanced service API Google Drive. It's working fine when we stay into the app script GUI :
/** #OnlyCurrentDoc */
function GetListOfDrivesName() {
const ALL_AVALIBLE_DRIVE = Drive.Drives.list();
return ALL_AVALIBLE_DRIVE.items.map(driveData => driveData = driveData.name)
}
However, when it's called from cell into Google Sheet we got an error
Error message is like below : GoogleJsonResponseException: API call to drive.drives.list failed with error: Login Required.
I guess it's some authentification & authorisation to ask before using the sheet.
Unfortuantly I have no idea how to request that ! I read doc but it's sound like it will be asked promptly.
In addition, trigger have been added for this function but it neither worked
Any idea ? Thanks in advance !
Hi #Yuri Khristich you were right, using ui is a good work around , code turn up to work corectly
function onOpen() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var ui = SpreadsheetApp.getUi();
ui.createMenu('[FM Script for G Drive]')
.addItem('test','showAvaillableDrive')
.addToUi();
}
function showAvaillableDrive(){
var ui = SpreadsheetApp.getUi();
var resultat = ui.prompt(GetListOfDrivesName())
}
function GetListOfDrivesName() {
const ALL_AVALIBLE_DRIVE = Drive.Drives.list();
return ALL_AVALIBLE_DRIVE.items.map(driveData => driveData = driveData.name)
}
As suggested by the comments you can build a menu over the Sheet and run it to write down Drive files that I own.
I have this custom Sheet where you can run the "Drive" function over "Adv Menu"
I automatically get the information of the Sheet and get a list of my Drive files:
function onOpen () {
var ui= SpreadsheetApp.getUi();
ui.createMenu('Adv Menu').addItem('Drive', 'getMyFilesFromDrive').addToUi();
}
function getMyFilesFromDrive() {
var myFiles = DriveApp.searchFiles('"me" in owners');
var sheet = SpreadsheetApp.getActive().getSheetByName("Files");
sheet.clear();
var rows = [];
rows.push(["ID", "Name", "Url"]);
while(myFiles.hasNext()) {
var file = myFiles.next();
if(file != null) {
rows.push([file.getId(), file.getName(), file.getUrl()]);
}
}
sheet.getRange(1,1,rows.length,3).setValues(rows);
}
It would also write it directly to my Sheet. Feel free to review it and use it.
You can also Draw a button (Inserting a Google Drawing) over the Sheet and assign a script:
Reference:
https://developers.google.com/apps-script/guides/menus
I want to retrieve a list of files from google drive using GAS.
So I want the list of files based on the owner who is empty.
For files that we create on the share drive, the owner doesn't exist.
If we try to force the owner name, it will get an error.
For example like this:
file.getOwner().getName();
it cannot be retrieved from the file list.
I have tried adding a filter based on an empty owner, I would like to say Unknown.
function getfilesUnknown () {
var ss = SpreadsheetApp.getActiveSpreadsheet ();
var st = ss.getActiveSheet ();
var files = DriveApp.getFiles ();
var counter = 0;
while (files.hasNext () && counter <21) {
var file = files.next ();
var data = [
file.getName (),
file.getUrl (),
file.getOwner ()? file.getOwner (). getName (): 'Unknown'
];
var filter = data.filter (v => v [2] === 'Unknown');
counter ++;
st.appendRow (filter);
}
}
But the list of files won't retrieve.
It seems that the filter in while is not recommended.
Or maybe, folks here have another solution.
I am not sure if this is the best approach.
But since you are getting an error here file.getOwner().getName() when the owner does not exist, then try to use try...catch to catch the error.
Namely, if there is an error pass Unknown, otherwise pass the name of the file owner.
function getfilesUnknown() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var st = ss.getActiveSheet();
var files = DriveApp.getFiles();
var counter = 0;
var data = [];
while (files.hasNext() && counter<21) {
var file = files.next();
try{
data.push([file.getOwner().getName()]);
}
catch(e){
data.push(['Unknown']);
}
counter++;
}
st.getRange(st.getLastRow()+1,1,data.length,1).setValues(data);
}
Please note that your code is not very well optimized. You add information in the data array but then you filter on a particular value only. Therefore there is no need of using the other items as they will slow down your code.
Another note is that you are using appendRow iteratively which is not recommended. Instead use setValues outside of the for loop once.
So, I'm already trying this for a week, still errors. Can get the spreadsheet ID properly.
Currently I have this code:
function getSS(e,getSS) {
//If not yet authorized - get current spreadsheet
if (e && e.authMode != ScriptApp.AuthMode.FULL) {
var getSS = SpreadsheetApp.getActiveSpreadsheet();
}
//Else if authorized set/get property to open spreadsheet by ID for time-driven triggers
else {
if(!PropertiesService.getDocumentProperties().getProperty('SOURCE_DATA_ID')){
PropertiesService.getDocumentProperties().setProperty('SOURCE_DATA_ID', e.source.getId());
}
var getSSid = PropertiesService.getDocumentProperties().getProperty('SOURCE_DATA_ID');
var getSS = SpreadsheetApp.openById(getSSid);
}
return getSS;
}
var SS = getSS();
It's supposed to get active spreadsheet ID when the addon is not yet authorized, and get a spreadsheet ID from properties when it's authorized. However, when testing as installed, I always get an error that I don't have permission to use openById() or getDocumentProperties()
How do I keep SS as global variable without it being null in any authMode?
Note that global variables are constructed each and every time that Apps Script project is loaded / used. Also note that no parameters are passed to functions automatically - you have to designate a function as either a simple trigger (special function name) or an installed trigger before Google will send it an argument, and in all other cases you have to explicitly specify the argument.
The problem is then that you declare var SS = getSS(); in global scope, and do not pass it any parameters (there are no parameters you could pass it, either). Thus in the definition of getSS(), even if you have it as function getSS(e) {, there is no input argument to bind to the variable e.
Therefore this criteria if (e && ...) is always false, because e is undefined, which means your else branch is always executed. In your else branch, you assume that you have permissions, and your test never was able to even try to check that. Hence, your errors. You might have meant to write if (!e || e.authMode !== ScriptApp.AuthMode.FULL) which is true if either of the criteria is true. Consider reviewing JavaScript Logical Operators.
While you don't share how your code uses this spreadsheet, I'm quite certain it doesn't need to be available as an evaluated global. Any place you use your SS variable, you could have simply used SpreadsheetApp.getActiveSpreadsheet() instead.
Your getSS() function additionally force the use of a permissive scope by using openById - you cannot use the preferred ...spreadsheets.currentonly scope.
Example add-on code:
function onInstall(e) {
const wb = e.source;
const docProps = PropertiesService.getDocumentProperties();
docProps.setProperty('SOURCE_DATA_ID', wb.getId());
/**
* set other document properties, create triggers, etc.
*/
// Call the normal open event handler with elevated permissions.
onOpen(e);
}
function onOpen(e) {
if (!e || e.authMode === ScriptApp.AuthMode.NONE) {
// No event object, or we have no permissions.
} else {
// We have both an event object and either LIMITED or FULL AuthMode.
}
}
Consider reviewing the Apps Script guide to add-on authorization and setup: https://developers.google.com/apps-script/add-ons/lifecycle
So I made it this way:
//Because onInstall() only runs once and user might want to launch addon in different spreadsheets I moved getting ID to onOpen(),
function onInstall (e) {
getAuthUrl();
onOpen(e);
}
Cases for different AuthModes.
function onOpen(e) {
var menu = SpreadsheetApp.getUi().createAddonMenu();
if (e && e.authMode === ScriptApp.AuthMode.NONE) {
menu.addItem('Authorize this add-on', 'auth');
}
else {
//If addon is authorized add menu with functions that required it. Also get the id of the current spreadsheet and save it into properties, for use in other functions.
menu.addItem('Run', 'run');
var ssid = SpreadsheetApp.getActive().getId();
var docProps = PropertiesService.getDocumentProperties();
docProps.setProperty('SOURCE_DATA_ID', ssid);
}
menu.addToUi();
}
Function that pops authorization window:
function getAuthUrl() {
var authInfo,msg;
authInfo = ScriptApp.getAuthorizationInfo(ScriptApp.AuthMode.FULL);
msg = 'This addon needs authorization to work properly on this spreadsheet. Click ' +
'this url to authorize: <br><br>' +
'<a href="' + authInfo.getAuthorizationUrl() +
'" style="cursor:pointer;padding:5px;background: #4285f4;border:1px #000;text-align: center;margin-top: 15px;width: calc(100% - 10px);font-weight: 600;color: #fff">AUTHORIZE</a>' +
'<br><br> This spreadsheet needs to either ' +
'be authorized or re-authorized.';
//return msg;//Use this for testing
//ScriptApp.AuthMode.FULL is the auth mode to check for since no other authorization mode requires
//that users grant authorization
if (authInfo.getAuthorizationStatus() === ScriptApp.AuthorizationStatus.REQUIRED) {
return msg;
} else {
return "No Authorization needed";
};
console.info('Authorization window called');
}
I see most examples from Google is they use only functions in a single giant script.
e.g. https://developers.google.com/apps-script/quickstart/macros
But in our style, we usually write all functions under a single namespace, such as
MyCompany = (MyCompany || {});
MyCompany.init = function () {
Logger.log('init');
};
function onOpen() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var menus = [{
name: "Init",
functionName: MyCompany.init
}];
spreadsheet.addMenu("Test", menus);
};
However, when I run the code above, it return
"MyCompany is not defined."
How to solve?
You might be better off using the Properties Service as you can use these as a kind of persistent global variable.
click 'file > project properties > project properties' to set a key value, or you can use
PropertiesService.getScriptProperties().setProperty('mykey', 'myvalue');
The data can be retrieved with
var myvalue = PropertiesService.getScriptProperties().getProperty('mykey');
In GAS global variables are not what they are in other languages. They are not constants nor variables available in all routines.
I thought I could use global variables for consistency amongst functions and efficiency as well. But I was wrong as pointed out by some people here at SO.
Global variable will be evaluated at each execution of a script, so not just once every time you run your application.
Global variables CAN be changed in a script (so they are not constants that cannot be changed by accident), but will be reinitialized when another script will be invoked.
There is also a speed penalty on using global variables. If within a function you use the same global variable two or more times, it will be faster to assign a local variable and use that instead.
If you want to preserve variables between all functions in your application, it might be using a cacheService will be best.
I found out that looping through all files and folders on a drive takes a LOT of time. But you can store info about files and folders within cache (or even properties) and speed up at least 100 times.
The only way I use global variables now is for some prefixes and for naming widgets.
I'm using a workaround by returning a function with an object of my global variables:
function globalVariables(){
var variables = {
sheetName: 'Sheet1',
variable1: 1,
variable2: 2
};
return variables;
}
function functionThatUsesVariable (){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(globalVariables().sheetName);
}
Global variables certainly do exist in GAS, but you must understand the client/server relationship of the environment in order to use them correctly - please see this question:
Global variables in Google Script (spreadsheet)
However this is not the problem with your code; the documentation indicates that the function to be executed by the menu must be supplied to the method as a string, right now you are supplying the output of the function:
https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet#addMenu%28String,Object%29
function MainMenu_Init() {
Logger.log('init');
};
function onOpen() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var menus = [{
name: "Init",
functionName: "MainMenu_Init"
}];
spreadsheet.addMenu("Test", menus);
};
I needed something similar like the question, you can store and fetch from the cache https://developers.google.com/apps-script/reference/cache/cache
Example:
// call cache service
var cache = CacheService.getScriptCache();
// get an item from the cache
var cached = cache.get("somekey");
// if exists in the cache use it
if (cached != null) {
// use it whatever you like.
}else{
// calculate/assign your data to cache
cache.put("somekey","somevalueorobject");
// you can even put cache data on TTL (time to live) in seconds.
cache.put("somekey","somevalueorobject",60);
I use this: if you declare var x = 0; before the functions declarations, the variable works for all the code files, but the variable will be declare every time that you edit a cell in the spreadsheet
For constants I am using function arrow expressions.
The footprint is similar to a variable declaration. Just add the () => when declaring, and () when calling the (function) variable.
var currentSheet = () => SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var maxAttempts = () => 10;
function myFunction(){
var sheetName = currentSheet().getName();
for (var i=0; i< maxAttempts(); i++){
trySomething(i);
}
}
var userProperties = PropertiesService.getUserProperties();
function globalSetting(){
//creating an array
userProperties.setProperty('gemployeeName',"Rajendra Barge");
userProperties.setProperty('gemployeeMobile',"9822082320");
userProperties.setProperty('gemployeeEmail'," rajbarge#hotmail.com");
userProperties.setProperty('gemployeeLastlogin',"03/10/2020");
}
var userProperties = PropertiesService.getUserProperties();
function showUserForm(){
var templete = HtmlService.createTemplateFromFile("userForm");
var html = templete.evaluate();
html.setTitle("Customer Data");
SpreadsheetApp.getUi().showSidebar(html);
}
function appendData(data){
globalSetting();
var ws = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Data");
ws.appendRow([data.date,
data.name,
data.Kindlyattention,
data.senderName,
data.customereMail,
userProperties.getProperty('gemployeeName'),
,
,
data.paymentTerms,
,
userProperties.getProperty('gemployeeMobile'),
userProperties.getProperty('gemployeeEmail'),
Utilities.formatDate(new Date(), "GMT+05:30", "dd-MM-yyyy HH:mm:ss")
]);
}
function errorMessage(){
Browser.msgBox("! All fields are mandetory");
}
I found a script that will move my spreadsheet to a folder. How can I make it so that the functions runs when the spreadsheet is open. I will be applying this to a template so that everytime the template is opened, the "Copy of " spreadsheet file will automatically be moved to my Packing Lists folder.
Here's the code I have....
function MoveFileTo()
{
var docs=DocsList.find('Save Test');
var doc=docs[0]; /*Since we assume there is only one file with the name "Hello World", we take the index 0 */
var folders = doc.getParents();
var newFolder=DocsList.getFolder('Packing Lists');
doc.addToFolder(newFolder); // This will add the document to its new location.
var docParentFolder=folders[0];
doc.removeFromFolder(docParentFolder); // This will remove the document from its original location.
}
EDIT: I tried changing function MoveFileTo to onOpen() and onEdit() but it doesn't seem to work properly. Is there something I am missing?
Try use driveapp instead of doclist (it's deprecated). the impinball comment is really usefull, you should have a look at it.
Anyway here a working code:
function myFunction() {
var destFolder = DriveApp.getFolderById("FOLDER_ID_WHERE8YOU_WANT_TO_MOVE_YOUR_FILES");
var fileList = DriveApp.searchFiles("mimeType = 'application/vnd.google-apps.spreadsheet' and title contains 'FILE_TITLE'"); // change this search if necessary
var originalFileId="THE_ID_OF_THE_ACTUAL_SPREADSHEET_TO_BE_EXCLUDED_FROM_THE_SCRIPT_TREATMENT";
while(fileList.hasNext()){
var fileToMove = fileList.next();
if(fileToMove.getId()==originalFileId){
continue; // don't do anything if it's the original file
}
var papas = fileToMove.getParents();
destFolder.addFile(fileToMove); // do the job
while(papas.hasNext()){
papas.next().removeFile(fileToMove); // remove the actuals parents (can be more than one)
}
}
}