Conditionally lock / unlock cells in google sheets - google-sheets

I have a column A "Mood" with values = Happy or Sad.
I have a column B "Why are you sad?" with values = Stubbed toe, Hungry, Mourning
I want to lock the cell in column B, and unlock it if A = Sad
How?
Thanks!

I think I found the answer via some extra Google searching. This is a YouTube video on how it works, followed by a link to the code in the video:
https://www.youtube.com/watch?annotation_id=annotation_705816535&feature=iv&src_vid=RkpBms7DKgo&v=rW9T4XZy-7U
https://script.google.com/d/1-U7dOHp6V-mEYqgQPRKDp5Vwx4RCeaWKLO62xVsetNnLmocjHYO80-9_/edit?usp=sharing
What the author does is create some ranges in a different sheet as lookup values, and then populate the select box with the values from the matching column.
function depDrop_(range, sourceRange){
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(sourceRange, true).build();
range.setDataValidation(rule);
}
function onEdit (){
var aCell = SpreadsheetApp.getActiveSheet().getActiveCell();
var aColumn = aCell.getColumn();
if (aColumn == 1 && SpreadsheetApp.getActiveSheet()){
var range = SpreadsheetApp.getActiveSheet().getRange(aCell.getRow(), aColumn + 1);
var sourceRange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName(aCell.getValue());
depDrop_(range, sourceRange);
}
else if (aColumn == 2 && SpreadsheetApp.getActiveSheet()){
var range = SpreadsheetApp.getActiveSheet().getRange(aCell.getRow(), aColumn + 1);
var sourceRange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName(aCell.getValue());
depDrop_(range, sourceRange);
}

Related

Jump to a specific cell based on Active Cell

I'm completely new to Java Scipt although over the years I've messed around with VBA & Macros in Excel. I am now using Google Sheets almost exclusively, hence needing to learn Java.
I'm trying to jump to a specific cell (E5) if the current cell is (C14). This is what I've put together so far using scripts 'borrrowed' from others.
ie On entry of data in Cell C13 and pressing Enter, focus goes to Cell C14. The next data is to go into Cell E5.
function onSelectionChange(e) {
var sheetNames = ["Score Input"]; // Set the sheet name.
var ranges = ["C14"]; // Set the range to run the script.
var range = e.range;
var sheet = range.getSheet();
var check = ranges.some(r => {
var rng = sheet.getRange(r);
var rowStart = rng.getRow();
var rowEnd = rowStart + rng.getNumRows();
var colStart = rng.getColumn();
var colEnd = colStart + rng.getNumColumns();
return (range.rowStart >= rowStart && range.rowEnd < rowEnd && range.columnStart >= colStart && range.columnEnd < colEnd);
});
if (check) {
jumpToDetails();
}
};
function jumpToDetails() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Score Input");
var goToRange = sheet.getRange('c14').getValue();
//sheet.getRange(goToRange).activate();
SpreadsheetApp.getActive().getRange('E5').activate();
}
It worked, or did until I inserted a row in the sheet, and even though I have changed the associated cell addresses, it now doesn't work?
Two questions. 'Why has it stopped working'? and 'Is there a simpler way to do it'?
I prefer using onEdit(e) on range C13 and jump to E5
For instance here is a script that jump from one cell to the following in addresses'list
function onEdit(event){
var sh = event.source.getActiveSheet();
var rng = event.source.getActiveRange();
if (sh.getName() == 'Score Input'){ // adapt
var addresses = ["C13","E5","E10","H10","E13","H13","E16"]; // adapt
var values = addresses.join().split(",");
var item = values.indexOf(rng.getA1Notation());
if (item < addresses.length - 1){
sh.setActiveSelection(addresses[item + 1]); // except last one
}
}
}
if you want to be able to add rows and columns, play with named ranges (for instance ranges names 'first', 'second, 'third')
function onEdit(event){
var sh = event.source.getActiveSheet();
var rng = event.source.getActiveRange();
if (sh.getName() == 'Score Input'){
var addresses = ["first","second","third"];
var values = addresses
.map(ad => myGetRangeByName(ad))
.join().split(",");
var item = values.indexOf(rng.getA1Notation());
if (item < addresses.length - 1){
sh.setActiveSelection(addresses[item + 1]); // except last one
}
}
}
function myGetRangeByName(n) {
return SpreadsheetApp.getActiveSpreadsheet().getRangeByName(n).getA1Notation();
}
reference
class NamedRange

getRange variable range

I am trying to use a google sheet to rank a list of elements. This list is continually updated, so it can be troublesome to update the list if i already have hundreds of elements ranked and need to rank 10 new ones. Rather than having to re-rank some of the previously ranked elements every time (whether manually or using formulas), i thought it easier to write a macro that would re-rank for me.
1 - element A
2 - element B
3 - element C
new element: element D
For instance if i wanted element D to be ranked 2nd, i would need to change element B to 3 and element C to 4. This is tedious when doing hundreds of elements.
Here is my code so far but I get stuck with the getRange lines. Rankings are in column A.
function RankElements() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getActiveSheet();
var r = s.getActiveCell();
var v1 = r.getValue();
var v2 = v1 + 1
var v3 = v2 + 1
var lastRow = s.getLastRow();
s.getRange(1,v2).setValue(v2);
s.getRange(1,v3).autoFill(s.getRange(1,v3+":"+1,lastRow), SpreadsheetApp.AutoFillSeries.DEFAULT_SERIES);
s.getRange(1,v3+":"+1,lastRow).copyTo(s.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
s.getFilter().sort(1, true);
};
You can do the following:
Iterate through all values in column A.
For each value, check if (1) ranking is equal or below the new one, and (2) it's not the element that is being added.
If both these conditions are met, add 1 to the current ranking.
It could be something like this:
function RankElements() {
const sheet = SpreadsheetApp.getActiveSheet();
const cell = sheet.getActiveCell();
const row = cell.getRow();
const newRanking = sheet.getActiveCell().getValue();
const firstRow = 2;
const columnA = sheet.getRange(firstRow, 1, sheet.getLastRow() - 1).getValues()
.map(row => row[0]); // Retrieve column A values
for (let i = 0; i < columnA.length; i++) { // Iterate through column A values
if (columnA[i] >= newRanking && (i + firstRow) != row) {
sheet.getRange(firstRow + i, 1).setValue(columnA[i] + 1); // Add 1 to ranking
}
}
sheet.getFilter().sort(1, true);
};

Dependent Dropdown styled, filtered sum advanced annual budget tracker

I've been trying to look for Dependent drop-down scripts to use, but I cannot find one that works. I ran out of ideas with this.
Things I need help with
I am trying to make a part in my budget sheet where when you select a category from a data validation drop-down list, the next cell will have a subcategory drop-down list based on the category that was just chosen. In the sheets "EJan," "EFeb", etc.
I want to add everything relevant to the Subcategory in "EJan", and paste the Sum of it in the desired Subcategory and month in "expenses."
Here is a link to a copy of my google spreadsheet
I quite often use this script by Jason Jurotich.
You can find it in the description on his YouTube channel:
DYNAMIC DEPENDENT DROP DOWN LISTS IN GOOGLE SPREADSHEETS
Please have a look at a copy of your sheet I prepared and is using the above mentioned script.
https://docs.google.com/spreadsheets/d/1JvJunjYBYdgRY66Nx75UBjHjGDiYL0KascwV3YGuGRs/edit?usp=sharing
This is an exact copy of the above mentioned script:
function depDrop_(range, sourceRange) {
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(sourceRange, true).build();
range.setDataValidation(rule);
}
function onEdit() {
var aCell = SpreadsheetApp.getActiveSheet().getActiveCell();
var aColumn = aCell.getColumn();
if (aColumn == 1 && SpreadsheetApp.getActiveSheet()) {
var range = SpreadsheetApp.getActiveSheet().getRange(aCell.getRow(), aColumn + 1);
var sourceRange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName(aCell.getValue());
depDrop_(range, sourceRange);
} else if (aColumn == 2 && SpreadsheetApp.getActiveSheet()) {
var range = SpreadsheetApp.getActiveSheet().getRange(aCell.getRow(), aColumn + 1);
var sourceRange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName(aCell.getValue());
depDrop_(range, sourceRange);
}
//if (aColumn == 8 && SpreadsheetApp.getActiveSheet()){
//var range = SpreadsheetApp.getActiveSheet().getRange(aCell.getRow(), aColumn + 1);
//var sourceRange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName(aCell.getValue());
//depDrop_(range, sourceRange);
//}
//else if (aColumn == 9 && SpreadsheetApp.getActiveSheet()){
//var range = SpreadsheetApp.getActiveSheet().getRange(aCell.getRow(), aColumn + 1);
//var sourceRange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName(aCell.getValue());
//depDrop_(range, sourceRange);
//}
}

Conditional formatting script - set iteration

I have a custom formula for conditional formatting rules. I am trying to write a script that checks a number of values (around 50) on a column (column B on 'Mine' sheet) and if a cell is equal to a specific string (M1, M2 or M3) then the specified formula for conditional formatting is applied to the "Calendar view" sheet. The code I currently have is:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Calendar View");
sheet.getRange("C4:NC50").clearFormat();
var range = sheet.getRange("C4:NC4");
var rule = SpreadsheetApp.newConditionalFormatRule()
.whenFormulaSatisfied('=AND(indirect("Mine!$B5")="M1", C$2>=indirect("Mine!$C5"), C$2<indirect("Mine!$D5"))')
.setBackground("#FF0000")
.setRanges([range])
.build();
var rules = sheet.getConditionalFormatRules();
rules.push(rule);
sheet.setConditionalFormatRules(rules);
}
How can I enter an iteration method on the .whenFormulaSatisfied, such as:
.whenFormulaSatisfied('=AND(indirect("Mine!$B6")="M1", C$2>=indirect("Mine!$C6"), C$2<indirect("Mine!$D6"))')
.whenFormulaSatisfied('=AND(indirect("Mine!$B7")="M1", C$2>=indirect("Mine!$C7"), C$2<indirect("Mine!$D7"))')
.whenFormulaSatisfied('=AND(indirect("Mine!$B8")="M1", C$2>=indirect("Mine!$C8"), C$2<indirect("Mine!$D8"))')
............
This is the sheet I'm working on:
https://docs.google.com/spreadsheets/d/1Af84aHaG0VjXmtaWc0-uAdGFrX1LozRNLQLMatSOqgU/edit?usp=sharing
There are some challenges to the questioner's methodology - first, the dynamic identification of the start and end dates for each property, and second, the creation of up to 50 separate Conditional Formatting rules. It's well known that spreadsheet performance is affected by numbers of Conditional Formatting rules.
I'm suggesting a slightly different approach.
1) Take the data on Mine and build the Calendar.
2) Place values in the booked date fields.
3) Apply a single Conditional Formatting rule for Calendar.
The methodology for identifying which dates are booked is to insert a nominal value in the respective cells. Then the rule .whenCellNotEmpty() is applied rather than specifying a specific value. In addition, the code formats both the background as well as the font colour so that any data is hidden.
Also to note: at the beginning of the script, the code removes both the content as well as the formatting.
function so_53185335() {
// build the spreadsheet app and set source and target sheets
var ss = SpreadsheetApp.getActiveSpreadsheet();
var calSheet = ss.getSheetByName("Calendar View");
var dataSheet = ss.getSheetByName("Mine");
// get the last rows and start rows for both sheets
var lrMine = dataSheet.getLastRow();
var lrCal = calSheet.getLastRow();
var dataRowStart = 5;
var calRowStart = 4;
// clear formats and data from Calendar
calSheet.getRange(calRowStart, 2, lrCal, 366).clear({
contentsOnly: true,
formatOnly: true
});
// get Mine rows with data, define the range and get data
var dataRows = lrMine - dataRowStart + 1;
//Logger.log("Mine: number of data rows "+dataRows);// DEBUG
var dataRange = dataSheet.getRange(dataRowStart, 2, dataRows, 3);
//Logger.log("data range is "+dataRange.getA1Notation());// DEBUG
var dataValues = dataRange.getValues();
//set some variables for use in loop
var i = 0; // counter
var z = 0; // counter
var calstartCol = 3; // equals first day of the year
var calrow = 0; // counter row for Calendar sheet
var calArray = [];
var masterArray = [];
// loop through the rows in Mine
for (i = 0; i < dataRows; i++) {
// test for value
if (dataValues[i][0] === "M1" || dataValues[i][0] === "M2" || dataValues[i][0] === "M3") {
//Logger.log("Match: i="+i+", value = "+dataValues[i][0]);//DEBUG
calArray = [];
masterArray = [];
calrow = calrow + 1;
// calculate the start day (as a day in the year)
var now = new Date(dataValues[i][1]);
var start = new Date(now.getFullYear(), 0, 0);
var diff = (now - start) + ((start.getTimezoneOffset() - now.getTimezoneOffset()) * 60 * 1000);
var oneDay = 1000 * 60 * 60 * 24;
var startday = Math.floor(diff / oneDay);
// calculate the end day (as a day in the year)
var fnow = new Date(dataValues[i][2]);
var fstart = new Date(fnow.getFullYear(), 0, 0);
var fdiff = (fnow - fstart) + ((fstart.getTimezoneOffset() - fnow.getTimezoneOffset()) * 60 * 1000);
var foneDay = 1000 * 60 * 60 * 24;
var endday = Math.floor(fdiff / foneDay);
var nod = endday - startday + 1;
// assign the value for the Property
var cell = calSheet.getRange(calstartCol + calrow, 2);
cell.setValue(dataValues[i][0]);
// create an array of values for booked dates; just insert the number "1"
for (z = 1; z < nod + 1; z++) {
calArray.push(1);
}
masterArray.push(calArray);
// Assign the values for booked dates
var cell = calSheet.getRange(calstartCol + calrow, startday + 2, 1, nod);
cell.setValue(masterArray);
}
}
// create and apply a single Conditional forma rule for the data range on Calendar
var range = calSheet.getRange(calRowStart, calstartCol, calstartCol + calrow, 366);
var rule = SpreadsheetApp.newConditionalFormatRule()
.whenCellNotEmpty()
.setFontColor("#FF0000")
.setBackground("#FF0000")
.setRanges([range])
.build();
var rules = calSheet.getConditionalFormatRules();
rules.push(rule);
calSheet.setConditionalFormatRules(rules);
}
The Calendar looks like this.

Copy Row of data based on two conditions to a sheet which is opened by ID or URL

I have a code that helps me copy a row of data based on a condition in a given column. I have a sheet called "Master" which has around 1000 rows of data. I want to move a row of data to a sheet called "Master Responses" if column 1 of "Master" has the word "Positive" or "Negative" in it. I used the or function (||) in the IF STATEMENT to select the condition (that is if "Positive" is entered or "Negative") but the row is only copied when I type "Positive" in the first column. When I type "Negative" in the first column the row is not copied. Also, I wanted to know how the code should be modified if I had to call the "Master Responses" sheet by using ".openByID or .openByURL". I have attached the code, please feel free to edit it. I am new to scripting and have been stuck on this for over a month. Any help would be appreciated. Thanks in advance.
function onEdit()
{
var sheetNamesToWatch = ["Master"];
var columnNumberToWatch = 1;
var valuesToWatch = ["Positive"];
var valuesToWatch1 = ["Negative"];
var targetSheetsToMoveTheRowTo = ["Master Responses"];
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveCell();
if(sheetNamesToWatch.indexOf(sheet.getName()) != -1 &&
valuesToWatch.indexOf(range.getValue()) != -1 ||
valuesToWatch1.indexOf(range.getValue()) != -1 && range.getColumn()
==columnNumberToWatch)
{
var targetSheet = ss.getSheetByName(targetSheetsToMoveTheRowTo[valuesToWatch.indexOf(range.getValue())]);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sheet.getRange(range.getRow(), 1, 1,
sheet.getLastColumn()).copyTo(targetRange);
}
}
I'm not sure about .openByID or .openByURL, however this should fix the problem with "Negative".
I also added extra parenthesis on the first if statement so that it was clear to me what it was checking on.
function onEdit()
{
var sheetNamesToWatch = ["Master"];
var columnNumberToWatch = 1;
var valuesToWatch = ["Positive"];
var valuesToWatch1 = ["Negative"];
var targetSheetsToMoveTheRowTo = ["Master Responses"];
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveCell();
if((sheetNamesToWatch.indexOf(sheet.getName()) != -1) && ((valuesToWatch.indexOf(range.getValue()) != -1) ||
(valuesToWatch1.indexOf(range.getValue()) != -1)) && (range.getColumn() ==columnNumberToWatch))
{
if (valuesToWatch.indexOf(range.getValue()) != -1)
{
var targetSheet = ss.getSheetByName(targetSheetsToMoveTheRowTo[valuesToWatch.indexOf(range.getValue())]);
}
else
{
var targetSheet = ss.getSheetByName(targetSheetsToMoveTheRowTo[valuesToWatch1.indexOf(range.getValue())]);
}
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sheet.getRange(range.getRow(), 1, 1,
sheet.getLastColumn()).copyTo(targetRange);
}
}

Resources