Google Spreadsheet conditional on three cells - google-apps-script

I've been trying to implement a conditional on my spreadsheet, basically a check-sheet with three conditional cells with "Yes" or "No" in them. All I want to achieve (using onEdit) is one all three cells contain "Yes", enter the next column with the date the final Yes was entered. I've managed to create other scripts which work fine, but this one has me stumped.
Thanks

Since the cells can be edited individually, your onEdit will always need to check all of your conditional cells' values, and write the timestamp only when all are "Yes".
function onEdit(event) {
var conditionalCells = [ "B1", "B2", "B3" ]; // Array of monitored conditionals
var inList = false; // assume edit was outside of the conditionals
var allYes = true; // and that all values are "Yes".
var sheet = event.source; // Sheet that was edited
var cell = event.range.getA1Notation(); // get range description
// Loop through all conditionals checking their contents.
// Verify that the edit that triggered onEdit() was in one
// of our conditional cells, setting inList true if it was.
for (var i = 0; i < conditionalCells.length && allYes; i++) {
if (cell == conditionalCells[i]) inList = true;
allYes = (sheet.getRange(conditionalCells[i]).getValue() == "Yes");
};
// If this was our final Yes, record the date.
// By validating inList, we ensure we record only the first time
// all conditionals are "Yes".
if (inList && allYes) sheet.getRange("C1").setValue(new Date());
}

Related

UPDATE: Hide named ranges (columns) with dropdown in same sheet

I am attempting to create a script for a dropdown selection in cell B2, that when selected for one, will hide columns corresponding two the other choices in the list sheet.
The dropdown in cell B2 is has as its data validation criteria the following list:
"NORMAL," "HARD," "MAX POINTS"
And the following column ranges in the sheet correspond to the selections in parentheses:
Columns D-K ("NORMAL"), Columns L-S ("HARD"), Columns T-AA ("MAX POINTS")
I would like the script to work such that selection of one of the dropdown choices will hide the column ranges that correspond to the two other dropdown choices (i.e., if you select "HARD" it will hide Columns D-K as well as T-AA).
What am I doing wrong here? I'm sure quite a bit of course.
link
MODIFIED SCRIPT: I got this to work properly for each when run separately, but it requires me to to unhide after each time, otherwise it compounds what is hidden. I have it set to trigger on edit. It's so close, is there something about the trigger or perhaps I need to somehow add something that resets it to unhide all before I can change the selection? (not sure how though)
var ss=SpreadsheetApp.getActive();
var value1 = "NORMAL";
var value2 = "HARD";
var value3 = "MAX POINTS";
var activeSheet = ss.getActiveSheet();
var cell = activeSheet.getRange("B2").getValue();
function HideColumn() {
if(cell == value1) {
activeSheet.hideColumns(12, (27-7+1));
}
else if(cell == value2) {
activeSheet.hideColumns(4, (14-7+1));
activeSheet.hideColumns(21, (14-7+1));
}
else if(cell == value3) {
activeSheet.hideColumns(4, (22-7+1));
}
}
You have a dropdown in Cell B2.
There are three options: "Normal", "Hard", and "Max Points".
Depending on the value selected, you want to show the columns for the selected value and hide the columns for the other options.
The script uses the switch statement Doc ref as an alternative to an IF statement.
To run this answer:
copy to the Project editor,
create an installable onEdit() trigger. (enables the script to run when the dropdown cell is edited; and enables Event objects to be used)
// running as an installable onEdit() trigger
// watching cell B2
function showHideColumns(e){
var sheetName = "Today's Matchups"
// Logger.log(JSON.stringify(e)) // DEBUG
if (e.range.columnStart ==2 && e.range.rowStart ==2 && e.range.getSheet().getName() == sheetName){
// correct sheet and correct cell
// Logger.log("DEBUG: correct sheet and correct cell")
}
else{
// not the correct sheet/cell
// Logger.log("DEBUG: not the correct sheet/cell")
return
}
var ss=SpreadsheetApp.getActiveSpreadsheet()
var sheet = ss.getSheetByName(sheetName)
var value1 = "NORMAL";
var value2 = "HARD";
var value3 = "MAX POINTS";
var value1ColStart = 4
var value2ColStart = 12
var value3ColStart = 20
var valueSetNumCols = 8
// Columns D-K ("NORMAL"),
// Columns L-S ("HARD"),
// Columns T-AA ("MAX POINTS")
var cell = e.value
// Logger.log("DEBUG: dropdown value = "+cell)
switch (cell) {
case value1:
// NORMAL
// show NORMAL, hide Hard & Max Points
// show all columns (including NORMAL)
sheet.showColumns(value1ColStart,24)
// hide Hard
sheet.hideColumns(value2ColStart, valueSetNumCols)
// hide MaxPoints
sheet. hideColumns(value3ColStart, valueSetNumCols)
break
case value2:
// HARD
// show HARD, hide Normal & Max Points
// show all Columns
sheet.showColumns(value1ColStart,24)
// hide Normal
sheet.hideColumns(value1ColStart, valueSetNumCols)
// hide MaxPoints
sheet. hideColumns(value3ColStart, valueSetNumCols)
break
default:
// Max Points
// show Max Points, hide Normal & Hard
// show all Columns
sheet.showColumns(value1ColStart,24)
// hide Normal
sheet.hideColumns(value1ColStart, valueSetNumCols)
// hide Hard
sheet. hideColumns(value2ColStart, valueSetNumCols)
}
}

How to reject changes to non-null cells generated by a query

A sheet populated with results of a query, from a separate workbook. User can select values from a drop down for any cell. When onEdit called, value is transferred to the main workbook, which then replicates back to the current cell in the active sheet via the query. Trying to prevent cells with existing values from being edited - ie - user can only put a value in a currently blank cell, and not change an existing. Because the query repopulates with each change in the master, locations of empty cells constantly change as they are filled or if new rows are added in the master.
(code below is a mashup from this video and one similar about unique ID's..(https://www.youtube.com/watch?v=ucLO4iHP2Kw))
Tried reading the current range of data into an array, and chugging through to set protection on any non-null cell. This works, but a 3x60 range of cells takes over 2 minutes to set protection on the non-blanks. Not a very good wait time before the user can move on to edit another cell!
Following is current code triggered on onEdit() The section after the "else" is where I want things to bail out by rejecting the user selection/input.
Any attempt to enter something different or change a value in a non-empty cell results in the query generating an error because it doesn't want to overwrite the cell. Hitting the "Del" key clears the cell, and the query then will refresh everything. Optimally, it would be great if the section after "else" just did the same - hit the "Del" key!
function syncData(e){
var src = e.source.getActiveSheet();
var r = e.range;
if (e.oldValue == null){
r.clear();
let id = src.getRange(r.rowStart,1).getValue();
var db = SpreadsheetApp.openById("SPREADSHEET ID").getSheetByName("SHEET NAME");
var ids = db.getRange("A:A").getValues();
let row = 0;
for (row; row < ids.length; row++){
if (ids[row][0] === id)
break;
}
row++;
db.getRange(row,r.columnStart).setValue(e.value);
}
else{
e.setValue(e.oldValue);
}
}
Of course I came to a solution after posting here!
If the rest worked, all I needed to do was post e.oldValue to the main spreadsheet, rather than e.value. That would repopulate the view sheet with the original value, preventing the user from making a change.
Here's the modified code:
function NewsyncData(e){
var src = e.source.getActiveSheet();
var r = e.range;
r.clear();
let id = src.getRange(r.rowStart,1).getValue();
var db = SpreadsheetApp.openById("SPREADSHEET ID").getSheetByName("SHEET NAMES");
var ids = db.getRange("A:A").getValues();
let row = 0;
for (row; row < ids.length; row++){
if (ids[row][0] === id)
break;
}
row++;
if (e.oldValue == null){
db.getRange(row,r.columnStart).setValue(e.value);
}
else{
db.getRange(row,r.columnStart).setValue(e.oldValue);
}
}

Apps Script to update Timestamp when data is inserted automatically in google sheet

This code works fine when data is edited in Column 3 or being copy-pasted but if the cursor remains at column 1 at the time of the whole row being copy/pasted, it won't update and secondly, if salesforce sends data to column 3, it doesn't work that time too, please help me here.
function onEdit() {
var s = SpreadsheetApp.getActiveSheet();
var sName = s.getName();
var r = s.getActiveCell();
var row = r.getRow();
var ar = s.getActiveRange();
var arRows = ar.getNumRows()
// Logger.log("DEBUG: the active range = "+ar.getA1Notation()+", the number of rows = "+ar.getNumRows());
if( r.getColumn() == 3 && sName == 'Sheet1') { //which column to watch on which sheet
// loop through the number of rows
for (var i = 0;i<arRows;i++){
var rowstamp = row+i;
SpreadsheetApp.getActiveSheet().getRange('F' + rowstamp.toString()).setValue(new Date()).setNumberFormat("MM/dd/yyyy hh:mm"); //which column to put timestamp in
}
}
}//setValue(new Date()).setNumberFormat("MM/dd/yyyy hh:mm:ss");
Explanation:
Three important things to know:
As it is also stated in the official documentation, the onEdit triggers are triggered upon user edits. This function won't be triggered by formula nor another script. If salesforce or any other service except for the user, edits column C the onEdit trigger is not going to be activated. Workarounds exist, but these workarounds depend on the context of your specific problem. I would advice you to search or ask a specific question about it.
Regarding the other issue you have, you should get rid of active ranges and take advantage of the event object. This object contains information regarding the edit/edits user made.
As it is recommended by the Best Practices you should not set values in the sheet iteratively but you can to that in one go by selecting a range of cells and set the values. In your case, you want to set the same value in all of the cells in the desired range, hence setValue is used instead of setValues. But the idea is to get rid of the for loop.
Solution:
function onEdit(e) {
var s = e.source.getActiveSheet();
var sName = s.getName();
var ar = e.range;
var row = ar.getRow();
var arRows = ar.getNumRows()
if( ar.getColumn() == 3 && sName == 'Sheet1') {
s.getRange(row,6,arRows).setValue(new Date()).setNumberFormat("MM/dd/yyyy hh:mm");
}
}
Note:
Again, onEdit is a trigger function. You are not supposed to execute it manually and if you do so you will actually get errors (because of the use of the event object). All you have to do is to save this code snippet to the script editor and then it will be triggered automatically upon edits.

Only one TRUE checkbox

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

Changing A Cell Value in a Range Only When It Meets A Condition(Google Sheets App Scripts)

I am working on a function for a Google Sheets script which would check to see if a certain value in a cell is equal to "Choice Cube", then override that cell if it returns true. The current function is as follows:
function removeCubeRewards(){
var ss2=SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Rewards")
var range = ss2.getRange("F2:F20")
var data = range.getValues()
var conditional = 0
for(var i=0;i<data.length; i++){
if(data[i][0] == "Choice Cube"){
data[i][0] = "Reward Claimed"
conditional++
}
if(conditional == 6){
break;
}
}
range.setValues(data)
}
The way it's currently set-up is that range will override all cells in the specified range with the values in data. My problem, is that I have certain cells in the range of my Google Spreadsheet set-up with an if-condition like so:
=IF(E11="X","Choice Cube", "LOCKED")
This would mean when I execute range.setValues(data) those IF conditions are overridden by whatever value is currently displayed.
I am wondering what I could do so that only the cells that are matched in
if(data[i][0] == "Choice Cube")
are overridden.
I understand you want your script to not affect the cells with an =IF formula (or perhaps any kind of a formula). To do so, use getFormulas along with getValues:
var data = range.getValues();
var formulas = range.getFormulas();
// ...
if (data[i][0] == "Choice Cube" && !formulas[i][0]){
// do something
}
The above version excludes any cells with a formula from consideration.
If you wanted to only exclude =if, use, for example, a regular expression (for case-insensitive substring match)
if (data[i][0] == "Choice Cube" && !/^=if\(/i.test(formulas[i][0])){