Creating an Apps Script function that takes other functions as arguments - google-apps-script

I want to create a function in APPSCRIPT that takes as argument another APPSCRIPT function.
I tried this:
function mainFunction(spreadsheetRange, secondaryFunction) {
// values = array of values retrieved from range
for (var i = 0; i < values.length; i = i+1) {
values[i] = secondaryFunction(values[i]);
}
// update the range with the new values
}
function function1() {
//something
}
function function2() {
//something
}
and running (after importing all these functions) in a google sheet cell the following formula:
=mainFunction(validrange, function2)
But this error appears:
TypeError: fun is not a function.
The same happens with=mainFunction(validrange, function2())
How can I solve this problem?

Although I'm not sure whether I could correctly understand your goal, the following modified script is your expected result?
Modified script:
function mainFunction(spreadsheetRange, secondaryFunction) {
// values = array of values retrieved from range
for (var i = 0; i < values.length; i = i+1) {
values[i] = this[secondaryFunction](values[i]); // <--- Modified
}
// update the range with the new values
}
In this modification, for example, when you put =mainFunction(validrange, "function2") to a cell, the function function2 is used with values[i] = this[secondaryFunction](values[i]).
But when I saw your function of function2, no arguments are used. So, in this case, values[i] is not given to function2. Please be careful about this.
In this case, please use "function2" of =mainFunction(validrange, "function2") as the text value. Please be careful about this.

I see no problem to pass a function as an argument:
function main() {
function mainFunction(func, arg) {
return func(arg);
}
function function1(num) { return num * 2 }
function function2(num) { return num * 3 }
var value = mainFunction(function1, 2);
console.log(value) // output ---> 4
var value = mainFunction(function2, 2);
console.log(value) // output ---> 6
}
You can try it right here:
function mainFunction(func, arg) {
return func(arg);
}
function function1(num) { return num * 2 }
function function2(num) { return num * 3 }
var value = mainFunction(function1, 2);
console.log(value) // output 4
var value = mainFunction(function2, 2);
console.log(value) // output 6

Related

How do can I use the setvalue() in a foreach() loop?[Google sheet Script]

It's my first post and I am a newbee in google sheets script programing.
I want to my script when I run it, to go first through a list of clients(row[0]). when the client has been found, test for the row[2] if it is a "yes" or "no" string object and when it is a "yes" option, rewrite it as a "no".
another question that i have is how can I know which ligne (row) i am testing when i am using the foreach function.
function cleanpassagepage(NomClient)
{
const passageclientsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("relevé chez client");
const numlastrowIS = passageclientsheet.getLastRow();
const dataIS = passageclientsheet.getRange(2,1,numlastrowIS,3).getValues();
dataIS.forEach(function(row)
{
if (row[0]==NomClient)
{
if(row[2]=="non")
{
row[2]= setValue("oui");
}
}
});
}
PS : the script doesn't give me an error to understand where did I messed up.
thank you
Assuming you call this function only once the use of setValue() is okay. If you are calling this many times you should investigate using setValues() instead.
function cleanpassagepage(NomClient) {
const passageclientsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("relevé chez client");
const numlastrowIS = passageclientsheet.getLastRow();
const dataIS = passageclientsheet.getRange(2,1,numlastrowIS,3).getValues();
var i = 0;
for( i=0; i<dataIS.length; i++ ) {
if( dataIS[i][0] === NomClient ) {
if( dataIS[i][2] === "non" ) {
passageclientsheet.getRange(i+1,3).setValue("oui");
}
return;
}
}
}
setValue is for range
you can try this (I have also added the num of row)
function cleanpassagepage(NomClient)
{
const passageclientsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("relevé chez client");
const numlastrowIS = passageclientsheet.getLastRow();
const dataIS = passageclientsheet.getRange(2,1,numlastrowIS,3).getValues();
var lignes=[]
dataIS.forEach(function(row,i)
{
if (row[0]==NomClient)
{
if(row[2]=="non")
{
row[2]= "oui"
lignes.push(i+2)
}
}
});
passageclientsheet.getRange(2,1,numlastrowIS,3).setValues(dataIS)
if (lignes.length>0) Browser.msgBox('Ligne(s) modifiée()s : '+lignes.join(','))
}
reference
setValue()

Doesn´t recognice as equal (==)

Can someone tell me why these variables marked with red are not recognized as equal (==).
Google Apps Script is Javascript-based. In Javascript, you can not compare two arrays using ==.
One method is to loop over both arrays and to check that the values are the same. For example you can include the function:
function compareArrays(array1, array2) {
for (var i = 0; i < array1.length; i++) {
if (array1[i] instanceof Array) {
if (!(array2[i] instanceof Array) || compareArrays(array1[i], array2[i]) == false) {
return false;
}
}
else if (array2[i] != array1[i]) {
return false;
}
}
return true;
}
And then update the line in your code from if (responsables == gestPor) { to if (compareArrays(responsables, gestPor)) {
For other methods of comparing arrays in Javascript, see this answer.
It is because you are comparing arrays. If you are just getting a single cell value, use getValue() instead of getValues()
To make things work, change these:
var gestPor = hojaActivador.getRange(i,13,1,1).getValues();
var responsables = hojaConMails.getRange(1,n,1,1).getValues();
to:
var gestPor = hojaActivador.getRange(i,13).getValue();
var responsables = hojaConMails.getRange(1,n).getValue();
Do these to all getValues() where you're only extracting 1 cell/value.
See difference below:

GoogleScript Spreadsheet Custom Function Handling a range of cells and getting their values

I have a Goggle Spreadsheet with some data, and I want to write a custom function to use in the sheet, which accepts a range of cells and a delimiter character, takes each cell value, splits it by the delimiter, and counts the total.
For example
Column A has the following values in rows 1-3: {"Sheep","Sheep,Dog","Cat"}
My function would be called like this: =CountDelimitedValues(A1:A3;",");
It should return the value: 4 (1+2+1)
The problem I am having is in my custom script I get errors like
"TypeError: cannot get function GetValues from type Sheep"
This is my current script:
function CountArrayList(arrayList, delimiter) {
var count = 0;
//for (i=0; i<array.length; i++)
//{
//count += array[i].split(delimiter).length;
//}
var newArray = arrayList.GetValues();
return newArray.ToString();
//return count;
}
I understand that the parameter arraylist is receiving an array of objects from the spreadsheet, however I don't know how to get the value out of those objects, or perhaps cast them into strings.
Alternatively I might be going about this in the wrong way? I have another script which extracts the text from a cell between two characters which works fine for a single cell. What is it about a range of cells that is different?
That's something you can achieve without using script but plain old formula's:
=SUM(ARRAYFORMULA(LEN(A1:A3)-LEN(SUBSTITUTE(A1:A3; ","; "")) + 1))
Credit goes here: https://webapps.stackexchange.com/q/37744/29140
something like this works :
function CountArrayList(arrayList) {
return arrayList.toString().split(',').length
}
wouldn't it be sufficient ?
edit Oooops, sorry I forgot the user defined delimiter, so like this
function CountArrayList(arrayList,del) {
return arrayList.toString().split(del).length
}
usage : =CountArrayList(A1:C1;",")
NOTE : in this example above it would be dangerous to use another delimiter than "," since the toString() joins the array elements with commas... if you really need to do so try using a regex to change the commas to what you use and apply the split on that.
try like this :
function CountArrayList(arrayList,del) {
return arrayList.toString().replace(/,/g,del).split(del).length
}
Another solution I have was that I needed to implicitly cast the objects in the array being passed as a string.
For example this function accepts the array of cells, and outputs their contents as a string with del as the delimiter (similar to the String.Split() function). Note the TrimString function and that it is being passed an element of the array.
function ArrayToString(array,del) {
var string = "";
for (i=0; i < array.length; i++) {
if (array[i] != null) {
var trimmedString = TrimString(array[i]);
if (trimmedString != "") {
if (string.length > 0) {
string += del;
}
string += trimmedString;
}
}
}
return string;
}
Below is the TrimString function.
function TrimString(string) {
var value = "";
if (string != "" && string != null) {
var newString = "";
newString += string;
var frontStringTrimmed = newString.replace(/^\s*/,"");
var backStringTrimmed = frontStringTrimmed.replace(/\s*$/,"");
value = backStringTrimmed;
}
return value;
}
What I found is that this code threw a TypeError unless I included the declaration of the newString variable, and added the array element object to it, implicitly casting the array element object as a string. Otherwise the replace() functions could not be called.

How can I do this asyn feature in nodejs

I have a code to do some calculation.
How can I write this code in an asyn way?
When query the database, seems we can not get the results synchronously.
So how to implement this kind of feature?
function main () {
var v = 0, k;
for (k in obj)
v += calc(obj[k].formula)
return v;
}
function calc (formula) {
var result = 0;
if (formula.type === 'SQL') {
var someSql = "select value from x = y"; // this SQL related to the formula;
client.query(someSql, function (err, rows) {
console.log(rows[0].value);
// *How can I get the value here?*
});
result = ? // *How can I return this value to the main function?*
}
else
result = formulaCalc(formula); // some other asyn code
return result;
}
Its not possible to return the result of an asynchronous function, it will just return in its own function scope.
Also this is not possible, the result will always be unchanged (null)
client.query(someSql, function (err, rows) {
result = rows[0].value;
});
return result;
Put a callback in the calc() function as second parameter and call that function in the client.query callback with the result
function main() {
calc(formula,function(rows) {
console.log(rows) // this is the result
});
}
function calc(formula,callback) {
client.query(query,function(err,rows) {
callback(rows);
});
}
Now if you want the main to return that result, you also have to put a callback parameter in the main and call that function like before.
I advice you to check out async its a great library to not have to deal with this kind of hassle
Here is a very crude way of implementing a loop to perform a calculation (emulating an asynchronous database call) by using events.
As Brmm alluded, once you go async you have to go async all the way. The code below is just a sample for you to get an idea of what the process in theory should look like. There are several libraries that make handling the sync process for asynch calls much cleaner that you would want to look into as well:
var events = require('events');
var eventEmitter = new events.EventEmitter();
var total = 0;
var count = 0;
var keys = [];
// Loop through the items
calculatePrice = function(keys) {
for (var i = 0; i < keys.length; i++) {
key = keys[i];
eventEmitter.emit('getPriceFromDb', {key: key, count: keys.length});
};
}
// Get the price for a single item (from a DB or whatever)
getPriceFromDb = function(data) {
console.log('fetching price for item: ' + data.key);
// mimic an async db call
setTimeout( function() {
price = data.key * 10;
eventEmitter.emit('aggregatePrice', {key: data.key, price: price, count: data.count});
}, 500);
}
// Agregate the price and figures out if we are done
aggregatePrice = function(data) {
count++;
total += data.price;
console.log('price $' + price + ' total so far $' + total);
var areWeDone = (count == data.count);
if (areWeDone) {
eventEmitter.emit('done', {key: data.key, total: total});
}
}
// We are done.
displayTotal = function(data) {
console.log('total $ ' + data.total);
}
// Wire up the events
eventEmitter.on('getPriceFromDb', getPriceFromDb);
eventEmitter.on('aggregatePrice', aggregatePrice);
eventEmitter.on('done', displayTotal);
// Kick of the calculate process over an array of keys
keys = [1, 2, 3]
calculatePrice(keys);

How can I use a custom function with FILTER?

I have a custom function defined that extracts part of an address from a string:
/*
* Return the number preceding 'N' in an address
* '445 N 400 E' => '445'
* '1083 E 500 N' => '500'
*/
function NorthAddress(address) {
if (!address) return null;
else {
var North = new RegExp('([0-9]+)[\\s]+N');
var match = address.match(North);
if (match && match.length >= 2) {
return match[1];
}
return null;
}
}
I want to use this function as one of the conditions in a call to FILTER(...) in the spreadsheet where I have these addresses stored:
=FILTER('Sheet 1'!A:A, NorthAddress('Sheet 1'!B:B) >= 450))
But when I call NorthAddress like this, it gets an array of all the values in column B and I can't for the life of me find any documentation as to how I need to handle that. The most obvious way (to me) doesn't seem to work: iterate over the array calling NorthAddress on each value, and return an array of the results.
What does my function need to return for FILTER to work as expected?
When a custom function is called passing a multi-cell range, it receives a matrix of values (2d array), it's doesn't matter if the range is a single column or a single row, it's always a matrix. And you should return a matrix as well.
Anyway, I would not use a custom function to this, as there is already the native spreadsheet formulas: RegexMatch, RegexExtract and RegexReplace formulas. To get the "if match" behavior, just wrap them in a IfError formula.
It doesn't work because address is, if you pass only one cell as arg a string, a range, a matrix of string.
So you return a string, FILTER use a boolean array to filter data, so the condition of your filter is string < number.
You just have to convert the string to a number when you returning a value
/*
* Return the number preceding 'N' in an address
* '445 N 400 E' => '445'
* '1083 E 500 N' => '500'
*/
function NorthAddress(address) {
if(typeof address == "string"){
if (!address) return "#N/A";
else {
var North = new RegExp('([0-9]+)[\\s]+N');
var match = address.match(North);
if (match && match.length >= 2) {
return parseInt(match[1]);
}
return "#N/A";
}
} else {
var matrix = new Array();
for(var i = 0; i<address.length; i++){
matrix[i] = new Array();
for(var j = 0; j<address[i].length; j++){
var North = new RegExp('([0-9]+)[\\s]+N');
var match = address[i][j].match(North);
if (match && match.length >= 2) {
matrix[i].push(parseInt(match[1]));
}
}
}
return matrix;
}
}
Hope this will help.
I will add this as an answer, because I found the custom function returns an error if numerical values are passed in the referenced cell or range when toString() is not invoked:
function NorthAddress(address) {
if (!address) return null;
else {
if (address.constructor == Array) {
var result = address;
}
else {
var result = [[address]];
}
var north = new RegExp('([0-9]+)[\\s]+N');
var match;
for (var i = 0; i < result.length; i++) {
for (var j = 0; j < result[0].length; j++) {
match = result[i][j].toString().match(north);
if (match && match.length >= 2) {
result[i][j] = parseInt(match[1]);
}
else {
result[i][j] = null;
}
}
}
return result;
}
}