Google Script For Loop Issues - google-apps-script

So this question seems to be beaten to death on the boards, but with all my reading and googling, I just can't figure out what I'm doing wrong.
I'm trying to adapt the code from this link
How to loop a google spreadsheet column values and set result in column B?
Below is what I've adapted it to
function EquationIterationTest(){
var s = SpreadsheetApp.getActiveSpreadsheet();
var sht = s.getSheetByName('Heath, OH')
var drng = sht.getDataRange();
var rng = sht.getRange(13, 2, 111, 1)
//.getRange(13, 2, drng.getLastRow()-1, drng.getLastColumn())
var rngA = rng.getValues();//Array of input values
Logger.log(rngA);
for(var i = 0; i < rngA.length; i++) {
if(rngA[i][0] === 'subtotal'){
rng.offset(0,3).setFormula('=iferror(sum(filter(Invoices!$E:$E,Invoices!$F:$F=$B14,Invoices!$A:$A=$C$2)))');
}
else{
rng.offset(0,3).setValue('Dumb');
}
}
}
When I run this, rngA does get the first column of values (which in this instance starts at B13) however, it will not input the formula in the third column of values. Instead it moves right through the first if statement and executes the else statement. The only thing I can think is there's something wrong either with my if statement or my array.
I tried setting if(rngA[i][0] === 'subtotal') to if(rngA[i][1] === 'subtotal'), but that still returned "dumb" on every line.
Any help would be appreciated so I can stop being "dumb"!
Here's the link to my sheet.
https://docs.google.com/spreadsheets/d/1cDkwWThXDTssH89gJX7W1zKzsW86oLXO-FPfAIJvc-g/edit?usp=sharing
Thanks

The problem is not in your if condition, although if you use three equal signs you are making a strict comparison, so subtotal in your case should start with capital letter.
That said, your problem is happening when you assign a value or formula to rng.offset(0,3), because the result of that expression is a range with the same size as rng but offset 3 columns to the right. You can verify this by using: Logger.log(rng.offset(0,3).getA1Notation());, thus whenever you assign a value or formula there you are assigning it to the whole offset rng. Not what you want right?
You should use offset() from a single cell in your case, not a whole range.
Your function could be simplified to something like the following:
function EquationIterationTest(){
var s = SpreadsheetApp.getActiveSpreadsheet();
var sht = s.getSheetByName('Heath, OH')
var rng = sht.getRange(13, 2, 111, 1)
for(var i = 1; i <= rng.getNumRows(); i++) {
var cell = rng.getCell(i,1);
if(cell.getValue() === 'Subtotal'){
cell.offset(0,3).setFormula(
'=iferror(sum(filter(Invoices!$E:$E,Invoices!$F:$F=$B14,Invoices!$A:$A=$C$2)))'
);
}
else{
cell.offset(0,3).setValue('Dummy');
}
}
}

So I want to make sure I put this here because while the answer above was previously correct, I figured out a much faster way to do it using arrays. This function checks against the data range for a value (in this case "Subtotal") and then appends the equation to rows that that do not contain that value. It is easy to make it compare against the value though by changing the operator from != to ==.
function NewIterationTest(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var activeSheet = ss.getActiveSheet();
var data = activeSheet.getRange(13, 2, 112, 1).getValues();
for (var i = 0; i < data.length; i++){
var rowData = data[i];
var checkData = data;
var row = checkData[i];
var colB = row[0];
if(colB != 'Subtotal'{
activeSheet.getRange(13 + i, 5).setFormula('=iferror(sum(filter(Invoices!$E:$E,Invoices!$F:$F=$B14,Invoices!$A:$A=$C$2)))');
}
}
}
However, if anyone could tell me how to also eliminate compare against whether or not a the text is bold, that would be helpful. Not sure it can be done though since it's pulling against the array.

Related

Get Collection Of Cells With A Certain Value Google Sheets

I have a button that I want to click, which will scroll me to a certain position. I've done this in order to get me to row 100:
function ScrollMe(){
var file = SpreadsheetApp.getActiveSpreadsheet();
var sheet = file.getActiveSheet();
var row = 100;
file.setActiveCell(sheet.getRange(row,1));
}
What I want to do if a find a list of all cells that are in column 'B' that contain (REGEX=>"Version: [0-9]?[0-9]?[0-9][.]?[0-9]?[0-9]? [a-zA-Z]+"), and then go to the last value that this is like. So basically, go to the last cell in column 'B' that starts with "Version: " and then has a single, double, or triple-digit number, a decimal point, and then two numbers after, and then any amounts of letter text after the fact. I want it to look like this:
function ScrollMe(){
var file = SpreadsheetApp.getActiveSpreadsheet();
var sheet = file.getActiveSheet();
//C# lambda
var row = FindAll(a=> a.COLUMN == 'B' && a.VALUE.RegexMatch("Version: [0-9]?[0-9]?[0-9][.]?[0-9]?[0-9]? [a-zA-Z]+"));
file.setActiveCell(sheet.getRange(row,1));
}
I assume that you expect the script to find the last cell in the column B that match your regex. If that is the case, you can use this code:
function ScrollMe() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var data = sheet.getRange("B:B").getValues();
var regex = new RegExp(
'Version: [0-9]?[0-9]?[0-9][.]?[0-9]?[0-9]? [a-zA-Z]+');
for (var i = 0; i < data.length; i++) {
if (regex.test(data[i][0])) {
var lastMatch = i;
}
}
sheet.setActiveRange(sheet.getRange(lastMatch + 1, 2));
}
The previous code used your approach. It will first read the full column B, and after that will iterate to find the last cell that match the regex; and when it finds the cell, it will select it. Please, ask me if you have any doubts about the function.

How do I have Google Sheets automatically sort my rows based on the value in one of my columns?

I am working on a Google Sheets document(Link is at the end of this paragraph) which is meant to track accessories that people borrow. I have written a formula which updates the status of the entry as the following options: Good, Overdue, Returned, Returned Late, and Missing Info. My goal is to have the rows rearrange everyday to put the overdue items first, good status entries second, missing info third, and returned items last. I was able to something similar by using the built-in data filter. To do this I went to Data->Create Filter->Click on symbol on bottom right corner of status column->Sort Z-A. However this solution is manual as it isn't possible(to my knowledge) to run the filter function using the app script. Additionally, the order it sorts the rows isn't exactly what I need. I have written the following app script function to try to counter that however it doesn't seem to be working as imagined.
function swap(i, target, correctOrd){
var link = decVar();
var temp = 0;
temp = link.mainSheet.getRange(i, 1, 1, link.col).getValues();
var oneRowCopy = link.mainSheet.getRange(i,1,1, link.col);
var targetRow = link.mainSheet.getRange(target,1,1, link.col);
oneRowCopy.copyTo(targetRow);
temp.copyTo(targetRow);
}
function reArrange(){
var correctOrd = [].concat.apply([], getStatus());
var link = decVar(); var temp = 0;
for(var i = 2, j = 0; i<correctOrd.length+1; i++, j++){
if(i != correctOrd[j]){
swap(i, j, correctOrd);
correctOrd[j] = i;
}
}
}
The rest of the code can be found in the script part of the document shared.
https://docs.google.com/spreadsheets/d/15qaNHuMoVyEJmwR5pL9ruPFDeiNR60cKWLKxnfjaWko/edit?usp=sharing
Please let me know if you have any ideas on how something like this could be done or what I'm doing wrong with my code.
Thank you!
You need to define a custom comparator for the Array.prototype.sort() method. Your comparator needs to return negative for when the first object comes before the second object, 0 when the two are interchangeable, and positive when the first comes after the second object.
To avoid a lot of nasty case considerations, this can be easily done by defining a "lookup table" which returns a numeric value for a given possible input value:
// Comparison object. Objects to come first should be ranked lower.
var ordering = {
"Good": 5, // Will come last.
"Missing Info": 2,
"Overdue": 1, // Will come first.
"Returned": 3,
"Returned Late": 4,
};
var compareIndex = 1; // Column B data, for example
var sheetData = sheet.getRange(2, 1, sheet.getLastRow() - 1, sheet.getLastColumn()).getValues();
sheetData.sort(function (row1, row2) {
// If the compare value is not found in the comparison object, or is convertible to "false" (e.g. 0)
// then the "|| 400" portion makes it default to a value of 400.
var v1 = ordering[row1[compareIndex]] || 400;
var v2 = ordering[row2[compareIndex]] || 400;
return (v1 - v2);
});
sheet.getRange(2, 1, sheetData.length, sheetData[0].length).setValues(sheetData);
By changing the values in the comparison object, you can change the order in which the rows are sorted. If you wanted "Good" ratings to come first, you would want the value of "Good" to be the lowest.
See also related questions like this one.
Although inbuilt spreadsheet functions can be slow compared to Java script array functions, This custom built function can be used for your test case:
function OverdueGoodMissingReturned() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName("Main");
var lr = sh.getLastRow();
var lc = sh.getLastColumn();
var irng = sh.getRange(1,9,lr,1).getValues(); //get col I (StatusColumn)
var irngF = irng.map(flatten); //Flatten the 2D array
function flatten(e) {return e[0];}
var lr = irngF.indexOf(''); //Find actualLast Row (This is needed,because you filled all the rows with Data Validations)
var rng = sh.getRange(2,1,lr-1,lc); //getWholeRange
rng.sort(9); //Sort by the status Column 9,Default Sort is Good,Missing,Overdue,Returned
var rv = rng.getValues();
var irng = sh.getRange(1,9,lr,1).getValues();
var irngF = irng.map(flatten);
var O1 = irngF.indexOf('Overdue')+1; // Find first overdue
var O2 = irngF.lastIndexOf('Overdue')+1; //Find Last overdue
var mv = sh.getRange(O1+':'+O2); //getRange to move
sh.moveRows(mv,2); //Move Overdue to Top
}
Note: A lot of getRange calls could be cutoff ,If you don't fill the empty rows with data validations. rng.sort could also be cut short to sheet.sort()

converting nonadjacent columns to proper/title case in Google Sheets

I found something online and it works for changing text to title case; however, it only works on adjacent columns and I need to apply this to multiple nonadjacent columns. I will paste in the script. If you can give a fix to being able to take care of letting the script run on multiple nonadjacent columns that I specify, that would be great. I found some stuff online that says it can do that, but it is not clear to me.
Here is the script that works:
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var range = sheet.getRange("A_range");
range.activate();
var values = range.getValues();
if (values.map) {
range.setValues(values.map(function(row) {
return row.map(titleCase);
}));
}
else {
range.setValue(titleCase(values));
}
}
function titleCase(str) {
return str.toString().split(/\b/).map(function(word) {
return word ? word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() : '';
}).join('');
}
This was from other people on this concept:
The stuff to make it run on other columns is this:
var range1=sheet.getRange("A1:A19").getValues();
var range2=sheet.getRange("C1:C19").getValues();
var range=[],i=-1;
while ( range1[++i] ) {
range.push( [ range1[i][0], range2[i][0] ] );
}
where range will have content from both columns.
data = sheet.getRange("A1:C19").getValues();
for (i = 0; i < data[0].length; i++) {
// do something with data[0][i]
// do something with data[2][i]
}
I am not sure how to implement these 2 other ideas listed above. If you could be really specific, like actually put something into the first script that lets it run on Col. A and Col. D,for example, it would be much better than generalities, as I am really really new to this and have spent an enormous amount of time trying to learn it/get a handle on it. Thanks!
Because making as few calls as possible to the SpreadsheetApp API is better for speed, i'd prefer to simply take a Range of all the cells between the first and the last column, apply the transformation to selected columns and then write the whole lot back again. The only place to edit then if the columns change is a single pattern array.
var columnPattern = [1,5,6,7] // Equivalent to [A,E,F,G]
The script then runs a simple map over the two-dimensional array representing the sheet.
function transform() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
// Rows (Could be first row and last row, OP isn't clear.
var startRow = 1, endRow = sheet.getLastRow();
// An array with Column indexes for those you want in Title Case.
var columnPattern = [1,5,6,7];
var firstColumn = parseInt(columnPattern.slice(0,1));
var lastColumn = parseInt(columnPattern.slice(-1));
// The whole range
var range = sheet.getRange(startRow,
firstColumn,
endRow,
lastColumn - firstColumn)
// Apply Title Case to selected columns.
var data = range.getValues().map(function(row, i, rows) {
row = row.map(function(col, j, row) {
if(columnPattern.indexOf(j + firstColumn) >= 0) {
col = titleCase(col);
}
return col;
});
return row;
});
range.setValues(data);
}
The only point I'd perhaps clarify is the line where it identifies the columns to amend.
if(columnPattern.indexOf(j + firstColumn) >= 0) {
This just corrects for the columnPattern array not being the same dimension as your sheet. An alternative would be to have an array that did match the x-dimension with boolean values, but this would be less adaptable to your sheet changing size.
I'd resist putting this in an onEdit() function but it depends on your use case as to how often data changed.

Iterate through a range and set values

In Google Sheets, I'm trying to input numbers in sequential order after prompting the user for the starting range and number of rows from that range. If the user types the cell "C5" for example and inputs 5 rows, the result should be from cells C5 - C10 (1, 2, 3, 4, 5)
I found a way to input the same value in all the rows but I can't seem to iterate through the range the user gives and set different values in each cell.
function placeInCell() {
var mySheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = mySheet.getSheetByName('Second');
var ui = SpreadsheetApp.getUi();
var result = ui.prompt('What cell to start with?',
ui.ButtonSet.OK);
var result2 = ui.prompt('How many cells?', ui.ButtonSet.OK);
// Process the user's response.
var button = result.getSelectedButton();
var cell = result.getResponseText();
var button2 = result2.getSelectedButton();
var numCells = result2.getResponseText();
var cellRange = sheet.getRange(cell);
cellRange = cellRange.offset(0, 0, numCells);
//iterate through the range given by the user and set values
//in each row
for (var i = 0; i < numCells; i++){
cellRange.setValue(i);
};
}
Try it this way
var values = cellRange.getValues();
for (var i = 0; i < numCells; i++) {
values[i][0] = i;
}
cellRange.setValues(values);
In Google Apps Scripts, a Range is addressed with indices 1..n, unlike 0..n-1 for e.g. an Array.
Also, a Range cannot be addressed with [] like an Array: you need to use methods such as offset.
This is why, as you commented, "cellRange[i][0].setValue(i) throws an error. It says cellRange[i][0] is undefined".
Try #SpiderPig's solution, it should work for you. It uses the general good practice or minimizing the number of server-side calls such as getRange, setValue or offset, rather working locally (client-side) on Arrays instead: first use myRange.getValues, then work on your Array, finally myRange.setValues.

going through each cell in a column and reset its value if >0

When I open a google sheet, I'd like all values within one specific column to be reset to 0 if its value is >0.
Please note that some cells contain no data, I wouldn't like to mess up with these cells as they should stay empty.
At the moment I'm really trying to build up an MVP so if I have to hardcode the value of the column within the formula it's OK.
I've made some research and tried to find relevant examples here, I've tweaked one snippet which works for one specific cell, now I'd like to learn how to automate the execution of this script so I don't have to hardcode each cell....
Here is the script I have
function onOpen() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var cell = doc.getRange("B4");
var value = cell.getValue();
if (value > 0) {
cell.setValue("0");
}
else {
cell.setValue("0");
}
}
I'd really appreciate if you could help me a bit with this one by either poiting me in the right direction or showing me a working example.
Thanks
This code works:
//function onOpen() {
function makeZero() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var theSheet = ss.getSheetByName('Sheet 2');
var theLastRow = theSheet.getLastRow();
//Get all values from column A
var theColumnRng = theSheet.getRange(1, 1, theLastRow, 1);
//Get all the values from column A. The return from getValues()
//is a two dimensional array
var theColumnValues = theColumnRng.getValues();
var thisCellValue = "";
var i;
for (i=0;i<theColumnValues.length;i++) {
//Every inner array only has one element, therefore, index zero
thisCellValue = theColumnValues[i][0];
if (thisCellValue > 0) {
theColumnValues[i][0] = 0;
}; //No "else" condition needed. If not greater than zero, do nothing
};
//Reset the entire column's values all at once
theColumnRng.setValues(theColumnValues);
};
If you want to compare it with another column.
Please select sheet name, i (row ) number and col number accordingly (i,2) 2 is a col here. IF there is no col then put Val2=0 in your case. Also don't forget to edit the setValue also.
function ifelse_col_comparision(){
var ss= SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
for(var i=2;i<ss.getLastRow();i++){
var val1=ss.getRange(i,2).getValue();
var val2=ss.getRange(i,3).getValue();
if(val2 >= val1){ss.getRange(i,4).setValue('Bigger or 0');}
else{ss.getRange(i,4).setValue('lower or -1');}
}
}
Please ask if you are looking for something else.