Iterating over cells in Google Spreadsheet Custom Function - javascript

I am trying to build a custom function in Google Spreadsheet, that would basically do this:
- say custom function called func() is placed in cell D2 and invoked as =func(B2)
- provided a particular cell reference (say B2) as a starting point it would iterate over all fields that follow B2 down the column (so B3, B4, B5) while the value of those fields equals to a particular symbol (say pipe |).
- For each iteration where this condition succeeds (i.e B3 == '|') it could add up/aggregate values from the cell it was placed, down the column. So if cells B3, B4,B5 contain | and then B6 doesn't it would return value of D3+D4+D5.
So for example if in this spreadsheet:
In the cells B10 the function should produce value of 8 (1+3+4) and in the cell B15 the function should produce value of 11 (5+6).
I've came up with something like this:
function sumByPipe(startRange) {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange(startRange)
var sum = 0;
for (var row_num = 1; row_num < 128; row_num ++) {
var cell = range.getCell(row_num, 1);
var cellValue = cell.getValue();
if (cellValue == '|') {
sum += 1;
}
}
return sum;
}
and got stuck in 2 places really:
Function seems to work in the debugger, but when I invoke it from the spreadsheet it fails on the getRange() function call saying no such range exist. If I replace it with static call of say getRange('A2') that part works but then it fails on the getCell() saying index out of range.
How do I actually get the value of the next cell down the column from where the function itself is placed?
Really quite lost on these two and would appreciate any advice. Thank you!

This works. I tested it:
function sumByPipe(startRange) {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange(startRange)
var sum = 0;
var startColumn = range.getColumn();
var startRow = range.getRow();
for (var row_num = startRow; row_num < startRow+128; row_num++) {
var cellWithPipe = sheet.getRange(row_num, startColumn-1).getValue();
var cellValue = sheet.getRange(row_num, startColumn).getValue();
if (cellWithPipe === '|') {
sum += cellValue;
} else {
//If pipe is no longer present, stop and return sum
return sum;
}
}
}

Related

Google Apps Script - Insert values to column B based on values on column A, when there is thousands of rows

I have a script that I need to improve. The script goes through all the rows on column A. Then, it inserts a value on the next cell based on value. For example: If the value on cell A2 is 4,9, then it will insert UNDER10 to the cell B2. It works. But, it works so slowly. If I have thousands of rows on column A sometimes the script times out. Does anybody know a way to make this script faster?
Below is my script:
function myFunction() {
const ss = SpreadsheetApp.getActive().getActiveSheet()
const lastRow = ss.getLastRow();
for (var i = 1; i < lastRow +1; i++) {
var value = ss.getRange(i,1).getValue();
var newValue = ss.getRange(i,2);
if (value < 10) {
newValue.setValue("UNDER10");
} else if (value < 20) {
newValue.setValue("UNDER20");
} else if (value > 20) {
newValue.setValue("OVER20");
}
}
}
This improvement should work. Note that I assumed column A include numbers (google sheet refer 4,9 as string. therefor, the statement if(value < 10) is not realy valid).
To test my code, I used 4.9, 14.9, etc.
function myFunction() {
const ss = SpreadsheetApp.getActive().getActiveSheet()
const lastRow = ss.getLastRow();
// get all the range at once
let range = ss.getRange(2, 1, lastRow -1, 2);
// get all the values in 2D array
let values = range.getValues();
// for each pair of values [price, custom value], calculate the custom value
values.forEach((value)=> {
// NOTE that i parse float out of price.
// Google sheet refer 4,9 as string (i assume you ment 4.9)
value[0] = parseFloat(value[0])
if (value[0] < 10) {
value[1] = "UNDER10"
} else if (value[0] < 20) {
value[1] = "UNDER20";
} else if (value[0] > 20) {
value[1] = "OVER20";
}
})
// set the new values into the spreadsheet
range.setValues(values)
}
If you ment to compare each number in each row (for example, in 'A2' cell: if(4 < 10 && 9 < 10)) please comment and I'll fix accordingly.

Google Sheets Custom Script iterate through a range and print values in a determined order

UPDATE WITH a-change 's response and code
I am working on a function that will let me select a range in a sheet in Google Sheets and then paste the values that I am interested in into a specific order on another sheet.
Suppose RawData (Sheet1) looks like this:
I want to grab the range RawData!A1:L15, so basically everything that is that picture.
Afterwards I want to print it in another sheet (Sheet2 called Analysis) like so:
So far this is the code:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("RawData");
var targetSheet = ss.getSheetByName("Analysis");
var range = sheet.getDataRange();
var values = range.getValues();
for (var i in values) {
var row = values[i];
var column = 1;
for (var j in row) {
var value = row[j];
if (value !== "X") {
targetSheet.getRange(parseInt(i) + 1, column).setValue(value);
column++;
}
}
}
}
This code results in values being pasted in the 'Analysis' with the same order as in the 'RawData' sheet. The idea is for the data to be able to be pasted in a trio format, with no spaces between values. So the first trio would be: A1 = 1, B1 = 2, C1 = 3, A2 = 4, B2 = 5, C2 = 6, and so on.
A couple of things:
for (var row in values) { — here row is an index of an element, not the element itself. So it'll always be not equal to "X". Better to put it this way:
for (var i in values) {
var row = values[i];
}
Then you need to iterate over row to get to a single element and compare it with "X":
for (var i in values) {
var row = values[i];
for (var j in row) {
var value = row[j];
if (value !== "X") {
}
}
}
Next thing is pasting the value to your target sheet. The reason you are getting the same number in all the cells is that you're calling setValue on the whole A1:C8 cells range instead of one particular cell.
for (var i in values) {
var row = values[i];
var column = 1;
for (var j in row) {
var value = row[j];
if (value !== "X") {
targetSheet.getRange(parseInt(i + 1), column).setValue(value);
column++;
}
}
}
targetSheet.getRange(i, j) here gives you a single-cell precision.
So alltogether your code would look something like:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("RawData");
var targetSheet = ss.getSheetByName("Analysis");
var range = sheet.getDataRange();
var values = range.getValues();
for (var i in values) {
var row = values[i];
var column = 1;
for (var j in row) {
var value = row[j];
if (value !== "X") {
targetSheet.getRange(parseInt(i) + 1, column).setValue(value);
column++;
}
}
}
}
See how the target sheet is set as a variable instead of using a range on the source sheet — it gives you more readability and freedom
It seems that when iterating like for (var i in row) i is considered to be a string so the parseInt call
column variable is needed to make sure there are no empty cells in the target sheet
I've also changed sheet.getRange(1,1,15) to sheet.getDataRange() to make sure your code gets all the data in the sheet
The approach of setting values into single cells separately is not optimal. It should work for you in your case as the data range seems pretty small but as soon as you get to hundreds and thousand of rows, you'll need to switch to setValues, so you'll need to build a 2D-array before pasting the values. The tricky thing is that your resulting rows may have a variable number of items (depending on how many Xs are in a row) while setValues expects all the rows to be of the same length — it's possible to get round it of course.

Simple for loop, if statement and output message

I think this one is pretty simple, but I am new to this, so I am not sure where to go from here :)
I have a Google Sheet, with some data (pretty large sheet). I want a script that check whether the number in a cell (column I) is larger than the number in the same row, but another column (column D).
Imagine two columns with 5 rows: Column D = (3, 3, 3, 3, 3) and Column I = (2, 2, 7, 2, 2)
SO in this example, I want that the script to tell me that I have problem in "Row 3", because the number in Column I, Row 3 is larger than the number in Column D, Row 3 :)
This is what I have:
function Check() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getActiveSheet();
var lr = sheet.getLastRow();
var Summary;
for (var i=6; i<lr; i++) {
if (sheet.getRange(i,9) > sheet.getRange(i,4)){
summary.setvalue("Problem")
}
}
}
I want it to start in row 6, because my data starts here. I am only checking column I (therefore 9) and column D (therefore 4)
I am not sure what to do with my for loop and If statement from here? SO now the code is checking every row in column 4 (D) and column I (9), but how do I store store the value, whenever the number in column 9 is larger than the number in column 4? And I also want an output, with all rows where we have a problem, when the code is done? If we don't have any problem, I want a message saying: "no problem"
Can anybody guide me from here?
If your output can be set in the sheet (let say in column "J"), you can use this:
function Check() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getActiveSheet();
var maxRows = sheet.getMaxRows(); // get the number of rows
var startRow = 6;
var diffCol1 = 'D';
var diffCol2 = 'I';
var outputCol = 'J';
var outputStrOK = 'no problem';
var outputStrKO = 'problem';
var outputRange = []; // stored values
for (var i = startRow; i <= maxRows; i++) {
var valueA = sheet.getRange(diffCol2+i).getValue();
var valueB = sheet.getRange(diffCol1+i).getValue();
// add controls on values then
if (valueA > valueB) {
outputRange.push([outputStrKO]);
} else {
outputRange.push([outputStrOK]);
}
}
// setting a range of values is much faster than fulfilling cell by cell in loop
sheet.getRange(outputCol+startRow+':'+outputCol+maxRows).setValues(outputRange);
}
you should have this as a result:
#|D|I|J
6|9|4|problem
7|4|9|no problem

Loop through a column and change value of another column based on cell values in Google Apps Script

The following script should change cell values in column K based on values in column D. However, the comparator is not working, it always returns "NO".
Is range.toString the correct syntax? I think I'm not comparing Apples to Apples.
function Test1() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName("report");
var numRows = SpreadsheetApp.getActiveSheet().getLastRow();
var range;
/* Loop through Column D and find cells equal to order payment
and set Column K value based on it */
for (var i = 1; i <= numRows; i++) {
range = s.getRange('D' + i );
if (range.toString() == "Order Payment") {
range.offset(0, 6).setValue("YES");
}
else {
range.offset(0,6).setValue("NO");
}
}
}
range.getValue() == "Order Payment" is the correct syntax instead of range.toString()

Sum cells if they are not bold

I'm confused with my Google Apps script which is purposed to calculate the sum of the cells only if these cells are bold.
Here is the source:
function SumIfNotBold(range, startcol, startrow){
// convert from int to ALPHANUMERIC
// - thanks to Daniel at http://stackoverflow.com/a/3145054/2828136
var start_col_id = String.fromCharCode(64 + startcol);
var end_col_id = String.fromCharCode(64 + startcol + range[0].length -1);
var endrow = startrow + range.length - 1
// build the range string, then get the font weights
var range_string = start_col_id + startrow + ":" + end_col_id + endrow
var ss = SpreadsheetApp.getActiveSpreadsheet();
var getWeights = ss.getRange(range_string).getFontWeights();
var x = 0;
var value;
for(var i = 0; i < range.length; i++) {
for(var j = 0; j < range[0].length; j++) {
if(getWeights[i][j].toString() != "bold") {
value = range[i][j];
if (!isNaN(value)){
x += value;
}
}
}
}
return x;
Here is the formula:
=(SumIfNotBold(K2:K100,COLUMN(K2), ROW(K2)))*1
I have three major concerns:
When I set up a trigger to launch this script on any edits I accidentally receive an email from Google Apps stating that
TypeError: Cannot read property "length" from undefined. (line 7, file
"SumIfNotBold")
Thus, how can I fix it? Are there any ways to ignore these automatically delivered notifications?
The formula doesn't calculate the sum of cells if they are on the other list. For example, if I put the formula on B list but the cells are located on A list then this script doesn't work properly in terms of deriving wrong calculations.
When the cell values are updated the formula derivation is not. In this case I'm refreshing the formula itself (i.e., changing "K2:K50" to "K3:K50" and once back) to get an updated derivation.
Please, help me with fixing the issues with this script. Or, if it would be better to use a new one to calculate the sum in non-bold cells then I'll be happy to accept your new solution.
Here is a version of this script that addresses some of the issues you raised. It is invoked simply as =sumifnotbold(A3:C8) or =sumifnotbold(Sheet2!A3:C8) if using another sheet.
As any custom function, it is automatically recalculated if an entry in the range to which it refers is edited.
It is not automatically recalculated if you change the font from bold to normal or back. In this case you can quickly refresh the function by delete-undo on any nonempty cell in the range which it sums. (That is, delete some number, and then undo the deletion.)
Most of the function gets a reference to the passed range by parsing the formula in the active cell. Caveat: this is based on the assumption that the function is used on its own, =sumifnotbold(B2:C4). It will not work within another function like =max(A1, sumifnotbold(B2:C4).
function sumifnotbold(reference) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var formula = SpreadsheetApp.getActiveRange().getFormula();
var args = formula.match(/=\w+\((.*)\)/i)[1].split('!');
try {
if (args.length == 1) {
var range = sheet.getRange(args[0]);
}
else {
sheet = ss.getSheetByName(args[0].replace(/'/g, ''));
range = sheet.getRange(args[1]);
}
}
catch(e) {
throw new Error(args.join('!') + ' is not a valid range');
}
// everything above is range extraction from the formula
// actual computation begins now
var weights = range.getFontWeights();
var numbers = range.getValues();
var x = 0;
for (var i = 0; i < numbers.length; i++) {
for (var j = 0; j < numbers[0].length; j++) {
if (weights[i][j] != "bold" && typeof numbers[i][j] == 'number') {
x += numbers[i][j];
}
}
}
return x;
}

Categories