I have a script that triggers on form submission, checks the row before last row and should write a TRUE value to the 9th column of the checked row IF all three cells on its left have a TRUE value.
My problem is that the script always writes a FALSE value, even if the 3 cells on the left are all TRUE.
function onFormSubmit() {
var s = SpreadsheetApp.getActiveSpreadsheet();
var rcore = s.getSheetByName("test");
var lastrow = rcore.getLastRow();
var trgt = rcore.getRange(lastrow-1,9);
if(trgt.getValue() === ""){
if(trgt.offset(0, -3) == "TRUE" && trgt.offset(0, -2) == "TRUE" && trgt.offset(0, -1) == "TRUE"){
trgt.setValue("TRUE");
} else {
trgt.setValue("FALSE");
}
}
}
(My language is set to hungarian so that's why you see "IGAZ" for "TRUE" values and "HAMIS" for "FALSE" values)
The 3 TRUE/FALSE values are generated by ARRAYFORMULA. Maybe that is also important
SO FAR
I have tried several variations:
-tried to change the if to check with offset (0, -4) if its equal to 2 and not check anything else, but still I got FALSE values.
-I also tried to check with different if statements but it always gives FALSE.
-tried to check what happens if I also offset row to -1
I simply cant get a TRUE value. The last time I was able to get a TRUE value was when there was no other if statement, only one that checks if the cell is empty or not.
As Rubén pointed out, you need to use .getValue() to read the value in a range. You should also use the Boolean values true and false instead of the text strings "TRUE" and "FALSE", like this:
function onFormSubmit() {
const ss = SpreadsheetApp.getActive();
const rcore = ss.getSheetByName('test');
const lastRow = rcore.getlastRow();
const trgt = rcore.getRange(lastRow - 1, 9);
if (trgt.getValue() === '') {
if (trgt.offset(0, -3).getValue() === true && trgt.offset(0, -2).getValue() === true && trgt.offset(0, -1).getValue() === true) {
trgt.setValue(true);
} else {
trgt.setValue(false);
}
}
}
...or more concisely:
function onFormSubmit() {
const rcore = SpreadsheetApp.getActive().getSheetByName('test');
const trgt = rcore.getRange(rcore.getlastRow() - 1, 9);
if (trgt.getValue() === '') {
trgt.setValue(trgt.offset(0, -3, 1, 3).getValues().flat().every(value => value === true));
}
}
Related
Column A in my sheet has checkboxes. I'm writing a simple script to find the checkbox that is checked (cell value = TRUE), make it unchecked (change it to FALSE), and then check the next checkbox in the column (make that cell value = TRUE).
Here's my code:
function nextCheckbox() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet()
var checkmarks = ss.getRangeByName("update_checkmarks").getValues().flat(); //this range is column A
Logger.log(checkmarks.indexOf(true)); //this logs 8.0, which is the correct row for the checked box in column A
var rowNum = checkmarks.indexOf(true);
Logger.log(rowNum); // this logs 8.0, as expected
var cell = sheet.getRange(rowNum,1);
cell.setValue(false); //nothing happens here...
var cell = sheet.getRange(rowNum + 1,1);
cell.setValue(true); //nothing happens here...
}
Logging logs the expected row number (8.0). But nothing happens when I use setValue. What am I doing incorrectly?
Regarding getting/setting values for checkboxes
Instead of using setValue consider to use check() / uncheck()
The above because checkboxes might use custom values for checked / unchecked states.
Regarding the use of indexOf / getRange
indexOf will return the index using 0 based numbering, this means 0 corresponds to the first value, 1 for the second and so on.
SpreadsheetApp.Sheet.Range.getRange(row,column) requires 1 based indexes, this means that the first row is 1, the second is 2.
Considering the above replace
var rowNum = checkmarks.indexOf(true);
by
var rowNum = checkmarks.indexOf(true) + 1;
Resources
https://developers.google.com/apps-script/reference/spreadsheet/range#check
https://developers.google.com/apps-script/reference/spreadsheet/range#uncheck
Your array starts at zero while your rows start at one. Currently your setting row 7 to be false(which it already is) and row 8 to be true, which it also already is.
Change this line in your code:
var rowNum = checkmarks.indexOf(true);
to be
var rowNum = checkmarks.indexOf(true)+1;
and you should get your expected results.
function onEdit(e) {
const sh = e.range.getSheet();
if(sh.getName() == 'Sheet1' && e.range.rowStart > 1 && e.range.rowStart < 12 && e.range.columnStart == 1 && e.value == "TRUE") {
e.range.setValue("FALSE");//reset
e.source.toast(e.range.getA1Notation());
}
}
Demo:
I am trying to write a Google App script that looks through the values in column A on my spreadsheet. If it finds an empty value, it should check the cell directly to the right, in column B. If the value in the cell directly to the right == "Planned" then it should ignore and continue to the next empty cell in Column A.
This should loop through all empty cells in Column A.
If the value in Column B to the right of the empty cell in Column A == "Open" then it should set the value of the empty cell in Column A to "X".
It should loop through every empty cell in Column A and every corresponding cell in Column B until it has confirmed that there are no cases where Column A contains an empty value and Column B contains an "Open" value.
Thank you in advance for any and all help!
Sample data:
Output after running script:
This code is pretty self descriptive. Kindly check the code comments.
If you have any questions, feel free to comment below
function updateSheet() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var rows = sheet.getLastRow() - 1;
var colARange = sheet.getRange(2, 1, rows, 1);
var colBRange = sheet.getRange(2, 2, rows, 1);
// 2d array -> 1d array for easier data accessibility
var colAValues = colARange.getValues().flat();
var colBValues = colBRange.getValues().flat();
for(var i = 0; i < rows; i++) {
// If A is blank and B is 'Open', set A to 'X'
if(!colAValues[i] && colBValues[i] == 'Open') {
sheet.getRange(i + 2, 1).setValue('X');
}
}
}
function checkEmptyCells() {
const range = SpreadsheetApp.getActiveSheet().getRange('A:B')
// Get the value of column A & B
const values = range.getValues()
const length = values.length
let r = -1
// Loop through checking for empty "" cells in column A
while (++r < length) {
if (values[r][0] == '') {
// assign value "X" if A is an empty cell and B is "Open"
values[r][0] = values[r][1] === 'Open' ? 'X' : ''
}
}
range.setValues(values)
}
function MyFunction() {
for(x=4;x<=45;x++)
{
var sheet = SpreadsheetApp.getActiveSheet().getRange(x, 7).getValue();
var box = SpreadsheetApp.getActiveSheet().getRange(x, 3).getValue();
var box2 = SpreadsheetApp.getActiveSheet().getRange(x, 4).getValue();
if(box == "TRUE" && box2 == "TRUE")
var sum = sheet+50;
else if(box == "TRUE" && box2 == "FALSE")
var sum = sheet+20;
else if(box == "FALSE" && box2 == "TRUE")
var sum = sheet+30;
else
var sum = sheet;
SpreadsheetApp.getActiveSheet().getRange(x, 16).setValue(sum);
}
}
columns 3 and 4 contain checkBoxes the column 7 contain integer values, the idea is to add +50 if both checkBoxes on its row are checked, +20 if only the first one is checked, +30 if the second one is checked and keep the integer column value if none is checked.
You want to retrieve the values from the columns "C", "D" and "G".
The columns "C" and "D" are the checkboxes.
The column "G" is the numbers.
You want to achieve the following conditions.
When the columns "C" and "D" are true and true, you want to add 50 to the value of column "G".
When the columns "C" and "D" are true and false, you want to add 20 to the value of column "G".
When the columns "C" and "D" are false and true, you want to add 30 to the value of column "G".
When the columns "C" and "D" are false and false, you want to use the value of column "G".
You want to put the result values to the column "P".
You want to achieve this using Google Apps Script.
Your issue is that sum is always sheet which is the value of the column "G".
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Pattern 1:
In this pattern, your script is modified.
Modification point:
When the value from a checkbox is retrieved with getValue(), the value is the boolean type. In your script, the string values of TRUE and FALSE are used at the if statement. By this, var sum = sheet; is always run. I think that this is the reason of your issue.
Modified script:
When your script is modified, please modify as follows.
From:
if(box == "TRUE" && box2 == "TRUE")
var sum = sheet+50;
else if(box == "TRUE" && box2 == "FALSE")
var sum = sheet+20;
else if(box == "FALSE" && box2 == "TRUE")
var sum = sheet+30;
else
var sum = sheet;
To:
var sum = sheet;
if(box === true && box2 === true)
sum = sheet+50;
else if(box === true && box2 === false)
sum = sheet+20;
else if(box === false && box2 === true)
sum = sheet+30;
Note:
At Google Apps Script, even when the variable of sum is declared in the if statement, sum can be retrieved at the outside of the if statement. So the script works. But from the scope of variable, I recommend to declare the variable of sum at the out of the if statement like above modified script.
Pattern 2:
In this pattern, your goal is achieved by reducing the process cost from your script. In this case, the values from the columns "C" to "G" are retrieved with getValues(), and the retrieved values are processed. Then, the result values are put to the column "P" with setValues(). In your situation, when getValues(), setValues() and map() are used instead of getValue(), setValue() and the for loop, the process cost can be reduced. Ref1, Ref2
Modified script:
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var values = sheet.getRange("C4:G45").getValues();
var numbers = values.map(function([c, d,,,g]) {return [
(c === true && d === true) ? g + 50 :
(c === true && d === false) ? g + 20 :
(c === false && d === true) ? g + 30 : g
]});
sheet.getRange(4, 16, numbers.length, 1).setValues(numbers);
}
References:
getValues()
setValues(values)
Benchmark: Reading and Writing Spreadsheet using Google Apps Script
Benchmark: Loop for Array Processing using Google Apps Script
Destructuring assignment
I am new to Google Apps Script, trying to set values to a column based on its current value and a flag.
If Flag = Y then floor the value in C1:
C1 column value =23.9895
Expected value=23
If Flag = N then round the existing value in C1:
C1 column value =23.9895
Expected value=24
If the flag is either Y or N then write 0:
C1 column value=empty cell
Expected Value=0
I already implemented the below code. It was working partially. The first two scenarios works fine but the third scenario fails.
When I try to set zero, I am getting #NUM! error instead of zero. Not sure how to do it.
...
do {
sRange = "Q" + iCt;
if ((gDecimalInPrice == "Y") && (!isNaN(sheet.getRange(sRange).getValue()))) {
sheet.getRange(sRange).setValue(Math.abs(parseInt(sheet.getRange(sRange).getValue())));
} else if ((gDecimalInPrice == "N") && (!isNaN(sheet.getRange(sRange).getValue()))) {
sheet.getRange(sRange).setValue(Math.abs(Math.round(sheet.getRange(sRange).getValue())));
} else {
sheet.getRange(sRange).setValue(sheet.getRange(sRange).getValue());
}
iCt = iCt + 1;
} while (iCt <= gRowCt);
It's much faster to do this via batch operations (and follows official best practices). These read the values into a "2D" JavaScript array (an array of arrays of values), and then you can do all your logic in memory, rather than repeatedly requesting data from the slow Spreadsheet interface.
function foo() {
const wb = SpreadsheetApp.getActive();
const sheet = wb.getSheetByName("the sheet name");
if (!sheet) throw new Error("Sheet with that name is missing");
const lastRow = sheet.getLastRow();
const flags = sheet.getRange("A1:A" + lastRow).getValues();
const valueRange = sheet.getRange("Q1:Q" + lastRow);
const newValues = valueRange.getValues().map(function (row, i) {
return row.map(function (value) {
var flag = flags[i][0];
if (!flag || (value && isNaN(value))) // No "Y" or "N", or value is non-nullstring non-number, so return value as-is
return value;
else if (flag === "Y")
return value ? Math.floor(parseFloat(value)) : 0;
else if (flag === "N")
return value ? Math.round(parseFloat(value)) : 0;
else // Unknown flag value
return value;
});
});
// Write all processed values at once
valueRange.setValues(newValues);
}
As always, you should monitor macro and triggered functions for errors by reviewing your Stackdriver logs (accessible via the Script Editor's "View" menu).
Array#map
I have a column of check boxes:
.
If a box is checked it sets a value to a cell in another sheet.
If I check box no.1 ,it turns true and the remaining still false
then if I check box no.2 it also turns true long with box no.1 and the remaining still false. This is the normal operation but I need that, when I check a box it turns true and all the other boxes turn false, either they are checked or not.In other words, I want one box to be checked at a time.
Can I do that?
This is my code to set a value if the box is checked:
var hasValue = sheet.getRange("B2:B").getValues();
for (var i = 0; i < hasValue.length; i++) {
if (hasValue[i][0] == true) {
var transfer = sheet2.getRange(2, 2, 1, 1).setValue(i + 1);
}
}
This kind of behavior is known as a "radio button".
The simplest method to achieve it is to bind the simple edit trigger:
inspect the edited range to determine if it was to your checkbox region and quit if not.
set all checkboxes to false
set the edited cell to the appropriate value from the event object
if required, perform the update
An extremely minimal sample which you will have to configure, and which is only configured for single-cell edits.
function onEdit(e) {
if (!e || e.value === undefined)
return; // The function was run from the Script Editor, or a multi-cell range was edited.
const edited = e.range;
const s = edited.getSheet();
if (s.getName() !== "some name")
return; // A cell on the wrong sheet was edited
if (isCheckboxRow_(edited.getRow()) && isCheckboxCol_(edited.getColumn())) {
// The cell edited was in a row and a column that contains a checkbox
updateCheckboxes_(s, edited, e);
}
}
function isCheckboxRow_(row) {
// Assumes checkboxes are only in rows 5, 6, 7, 8, 9, and 10
return row >= 5 && row <= 10;
}
function isCheckboxCol_(col) {
// Assumes checkboxes are in column A
return col === 1;
}
function updateCheckboxes_(sheet, editRange, eventObject) {
if (!sheet || !edit || !eventObject)
return; // Make sure all required arguments are defined (i.e. this was called and not run from the Script Editor)
const cbRange = sheet.getRange("A5:A10"); // location of the checkboxes in a radio group.
cbRange.setValue(false);
editRange.setValue(eventObject.value);
// Reference some other sheet
const targetSheet = eventObject.source.getSheetByName("some other sheet name")
if (!targetSheet)
return; // the sheet name didn't exist in the workbook we edited.
// Reference a cell in the same row as the cell we edited, in column 1
const targetCell = targetSheet.getRange(editRange.getRow(), 1);
if (eventObject.value) {
// when true, give the target cell the value of the cell next to the edited checkbox
targetCell.setValue(editRange.offset(0, 1).getValue());
// do other stuff that should be done when a checkbox is made true
} else {
// the checkbox was toggled to false, so clear the target cell
targetCell.clear();
// do other stuff that should be done when a checkbox is made false
}
}
The above hints at some suggested practices, such as using helper functions to encapsulate and abstract logic, resulting in easier to understand functions.
Review:
Simple Triggers
Event Objects
Spreadsheet Service
As I mentioned I would us an onEdit(event) to monitor which checkbox has been checked and loop through the column and only set one checkbox to true. Note that in your code snippet, getRange("B2:B") could be 999 rows. I use getDataRange() to limit to only the rows that are used. And I use getCriteriaType() to check that it is a checkbox not some other data type. And I'm assuming on your sheet2 you want to record which box was last checked true. tehhowch's answer is more generic and maybe more than what you need so here is a limited specific answer.
function onEdit(event) {
try {
var sheet = event.range.getSheet();
// Limit the following code to a particular sheet
if( sheet.getName() === "Sheet5" ) {
// Limit the following code to column B
if( event.range.getColumn() === 2 ) {
var range = sheet.getRange(2,2,sheet.getLastRow()-1,1);
var checks = range.getValues();
var valid = range.getDataValidations();
for( var i=0; i<checks.length; i++ ) {
if( valid[i][0].getCriteriaType() === SpreadsheetApp.DataValidationCriteria.CHECKBOX ) checks[i][0] = false;
}
// Assuming there are no formulas in this range
range.setValues(checks);
event.range.setValue(event.value);
if( event.value === true ) {
event.source.getSheetByName("Sheet6").getRange(2,2,1,1).setValue(event.range.getRow());
}
}
}
}
catch(err) {
SpreadsheetApp.getUi().alert(err);
}
}