how to call a function stored in an array - google-apps-script

I have this simple example that works
const func1 = function (i) { return (i) }
const func2 = function (i) { return (i * 2) }
const func3 = function (i) { return (i * 3) }
const func4 = function (i) { return (i * 4) }
const funcs = ['func1', 'func2', 'func3', 'func4']
function myFunction() {
let item = 3
let i = 2
let result = eval(`${funcs[item]}(${i})`)
console.log(result)
// what about call_user_func in google app script
}
Since eval() is not recommended, what will be the google app script equivalent of call_user_func (php)?

I'm unfamiliar with Google Apps Script but in standard JS you should just be able to call it like let result = funcs[item](i);
For this you also need to put your actual function variables into the array, like
const funcs = [func1, func2, func3, func4], not like const funcs = ['func1', 'func2', 'func3', 'func4']
Functions are first-class citizens in JS, which means you can pass them around and call them like any other variable

In google apps script you can also call functions by name
function executeFunctionByName(func) {
this[func]();
}

Related

can I have 2 functions in google.script.run?

Can I have multiple apps script function in google.script.run like:
google.script.run.withSuccessHandler(function).scriptfunction1(variable).scriptfunction2(variable);
No. The .scriptfunction1(variable) function cannot return a script runner. It can only return a value, an object or array that ultimately contains primitives, or undefined.
But you can do something like this (from the documentation):
const myRunner = google.script.run.withFailureHandler(onFailure);
const myRunner1 = myRunner.withSuccessHandler(onSuccess);
const myRunner2 = myRunner.withSuccessHandler(onDifferentSuccess);
myRunner1.doSomething();
myRunner1.doSomethingElse();
myRunner2.doSomething();

Finding ImportJSON Query Path in Google Sheets

Using Google Sheets with the custom IMPORTJSON function, I'm trying to parse the results of this URL so that it returns only the nodes with "highs".
Using the below formula, where cell A1 is the above URL, returns a #REF! error:
=ImportJSON(A1, "/data//highs", "noInherit, noTruncate")
I've also tried other variations of the /data//highs query (which would usually work with the IMPORTXML function) in the formula with the same result.
DESIRED OUTPUT
The query should result in displaying the records under "highs", i.e., both /data/nasdaq/highs and /data/nyse/highs.
The below image is the output for /data/nasdaq/highs. I'm looking to combine that query result with that for /data/nyse/highs in a single call.
Using ImportJSON:
One option could be to just separate both queries by a comma:
=ImportJSON(A1, "/data/nasdaq/highs,/data/nyse/highs", "noInherit, noTruncate")
This has several potential downsides, though: it won't automatically detect any additional ticket that has highs, but just nasdaq and nyse. If you wanted others, you'd have to edit your query.
Also, it will return nasdaq and nyse values in multiple columns, which I assume is not what you want.
Writing an alternative function:
Alternatively, since importJson cannot handle queries like //data/*/highs, I'd suggest writing a different custom function to handle this. To do this, select Extensions > Apps Script and copy the following function (see inline comments):
function altImportJson(url, query) {
const jsondata = UrlFetchApp.fetch(url); // Get data
let object = JSON.parse(jsondata.getContentText());
const levels = query.split("/").filter(String); // Get query levels
for (let i = 0; i < levels.length; i++) { // Iterate through query levels
const current = levels[i];
const last = levels[i-1];
const next = levels[i+1];
if (current != "*" && last != "*") object = object[current]; // Regular level, not "*"
else if (current == "*") { // Handle "*"
object = Object.values(object)
.filter(o => o[next])
.map(o => o[next])
.flat();
}
};
const headers = [...new Set(object.map(item => Object.keys(item)).flat())];
const data = object.map(item => { // Transform object to 2D array
let row = new Array(headers.length);
const entries = Object.entries(item);
entries.forEach(entry => {
const columnIndex = headers.indexOf(entry[0]);
if (columnIndex > -1) row[columnIndex] = entry[1];
});
return row;
});
data.unshift(headers);
return data;
}
After copying this function and saving your script, you can now call it:
=altImportJson(A1, "//data/*/highs")

How do I create a pop up authorization for my Google Web App

I'm new to Web Scripts and deploying web apps, I have an issue where I require users to authorize permission to use the web app, but nothing pops up to request for said authorization.
I found this post here and it seems to be what I needed but I'm unsure how would I use it. I tried calling onOpen function at the start of my doGet but nothing happens. I also don't see where/when the authorize function is being called. My Web App is also using sheets, so I deleted any lines of code dealing with DocumentApp.
Here is my modified version of the code
function onOpen() {
if(!UserProperties.getProperty('author')){
var html = HtmlService.createHtmlOutputFromFile('index1')
.setTitle("Install Menu").setWidth(400);
}else{
var html = HtmlService.createHtmlOutputFromFile('index2')
.setTitle("Mailmerge Menu").setWidth(400);
}
}
function authorize(){
UserProperties.setProperty('author','yes');
var html = HtmlService.createHtmlOutput('Authorization complete<br>Thanks<br><br>please refresh your browser').setTitle("Confirmation").setWidth(400);
}
This is my doGet function just in case I was not utilizing the functions correctly.
function doGet(e)
{
onOpen();
var result;
var sheets = SpreadsheetApp.getActiveSheet();
var values = [];
// Get row 1 data
for( var i = 3; i <= 16; ++i)
{
var char = String.fromCharCode('A'.charCodeAt() + i) // Increment char
values.push(sheets.getRange(char + 1).getValue());
}
// Get row 2 data
for( var i = 3; i <= 16; ++i)
{
var char = String.fromCharCode('A'.charCodeAt() + i) // Increment char
values.push(sheets.getRange(char + 2).getValue());
}
var result = {result:values};
var JsonValue = JSON.stringify(result);
return ContentService.createTextOutput(JsonValue.toString()).setMimeType(ContentService.MimeType.JSON);
}
I know I can set the web app to execute as me by anyone, which does work, but I would like be able to allow other users to authorize themselves to execute the app.
Edit:
I'm using Unity's WebRequest class to call the Get from my web app.
_webRequest = UnityWebRequest.Get("web app url here");
_webRequest.SendWebRequest();
And on receiving the response I will parse it and use the data in "result" in a separate class
if (_webRequest != null && _webRequest.isDone)
{
result = JsonUtility.FromJson<WebResponsePacket>
(_webRequest.downloadHandler.text);
Debug.Log("Successful Request");
return true;
}

Get last non-empty cell from range input

Up until now, I was using an answer from SF to fetch the last non empty row from a range:
=INDEX( FILTER( A4:D ; NOT( ISBLANK( A4:A ) ) ) ; ROWS( FILTER( A4:A ; NOT( ISBLANK( A4:A ) ) ) ) )
Since I started using it quite often, I opted for a script instead:
/* E.g. GETLASTNECELL(A4:A) */
function GETLASTNECELL(input) {
if (!Array.isArray(input)) {
throw new Error("Must be range")
}
if (input.map(col => col.length).sort()[0] !== 1) {
throw new Error("Range must be a single column")
}
const col = input
.map(row => row[0]) // get value
.filter(val => typeof val !== 'undefined' && val.toString().length > 0) // empty
return col[col.length - 1]
}
The issue is that the function is really slow...Is it just a by-product of Google Scripts lifecycle ? The native formulas approach displays the result in an instant. (Tested on a tiny 40x40 spreadsheet)
Read:
https://stackoverflow.com/a/8161172/2124535
How to grab all non-empty cell data in row - Google Sheets Script Editor
Yes, custom functions / scripts are slower than their equivalent built-in functions.
Most built-in functions runs on client-side while custom functions / scripts runs on server-side.
Try this approach of passing the range as a string instead and let me know if it works faster for you as it does for me:
function GETLASTNECELL(input) {
const sh = SpreadsheetApp.getActive().getActiveSheet();
const lrow = sh.getLastRow();
const Avals = sh.getRange(input+lrow).getValues();
const value = Avals[Avals.reverse().findIndex(c=>c[0]!='')]
return value;
}
Usage:
Regular function:
function myFunction() {
const input = "A1:A";
const sh = SpreadsheetApp.getActive().getActiveSheet();
const lrow = sh.getLastRow();
const Avals = sh.getRange(input+lrow).getValues();
const value = Avals[Avals.reverse().findIndex(c=>c[0]!='')]
return value;
}
and execute it from the script editor (click on the run button) or create a macro menu to execute it from the spreadsheet itself.

How to define global variable in Google Apps Script

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");
}