Unchanged Cell after filling a condition in google spreadsheet - google-sheets

I want to create a conditional cell formation.
Say Column B has a value that is a live score:
In Column C, I want to create an if condition, like this:
if B>10, then C is YES.
However, as I mentioned, B column as live score, even if B falls below 10 afterward, I want C to remain unchanged. So essentially, I want C to remain at yes, once my live score condition hits.
Please let me know how to do it.

You can use the Apps Script editor with the code below:
function liveScorePass(){
var spreadsheet = SpreadsheetApp.getActiveSheet();
var i = 0;
Logger.log(i);
while (i == 0){
Logger.log('B1');
if (spreadsheet.getRange('B1').getValue() > 10){
spreadsheet.getRange('C1').setValue("YES");
Logger.log('C1');
i = 1;
}
else{
spreadsheet.getRange('C1').setValue("NO");
}
}
}
/* OPTIONAL FUNCTION*/
function onOpen(){
var ui = SpreadsheetApp.getUi();
// Or DocumentApp or FormApp.
ui.createMenu('Live Score Menu')
.addItem('Reset Script', 'liveScorePass') //creates a menu to start the script again
.addToUi();
var spreadsheet = SpreadsheetApp.getActiveSheet();
liveScorePass(); //starts the script whenever you open the spreadsheet.
}
It basically checks if your cell B1 is greater than 10. If it's not, the script keeps running indefinetly, but when it is, the value of C1 is set to YES and i is set to 1, breaking away from thw while loop, no longer changing the value of C1 independent of the value of B1.
Edit
To use it as a function rather than a tab on the toolbar menu, you can just call it as if it were a formula =LIVESCOREPASS(). However, this implicates three things:
You can't use any set*() methods, so you need to adapt you coding, leaving you with this:
function LIVESCOREPASS(){
var spreadsheet = SpreadsheetApp.getActiveSheet();
var i = 0;
Logger.log(i);
while (i == 0){
Logger.log('B1');
if (spreadsheet.getRange('B1').getValue() > 10){
var result1 = "YES";
Logger.log('C1');
i = 1;
return result1;
}
else{
var result2 = "NO";
return result2;
}
}
}
It won't allow changes in arbitrary cells other than the one you're using or its adjacents (not a problem here);
Every time you update your referenced cell (B1), the function is reset, so you'll lose the fix "YES" if it drops to 10 or below.
Here you can find the documentation on Custom Functions.

Related

Is there a way for a cell to keep track of max value ever recorded in another cell?

I have a sheet where I want to track the max number ever input in one cell. The number in this cell might go up or down over time. I tried taking the max of the cell I want to track and the cell I'm using to track the max over all time but I got an error (something like F5:=MAX(F5, F8) where F8 is the cell I want to track).
It would help to have a demo sheet shared with your desired result. With that being said, if it is an onEdit event...then this will work for that one cell.
function onEdit(e) {
var ss = e.source;
var tab = ss.getActiveSheet();
var sheetName = tab.getSheetName();
var maxValue = tab.getRange(5,6).getValue();
var value = e.value;
var range = e.range;
//checks if F8 is greater than F5. If so, it will replace F5 with the new value in F8
if(sheetName == 'Sheet1' && maxValue < value && range.getRow() == 8 && range.getColumn() == 6) {
tab.getRange(5,6).setValue(value);
}
}

Google Spreadsheet dynamic conditional formatting with merged dropdown

How my sheet works
I'm making a spreadsheet to show how much parts I have. By using a dropdown, am I able to show that I created a product. With conditional formatting I am showing that having 0 items isn't an issue when the product is created. Created products with 0 items change from red to purple. Purple means it doesn't matter to have 0 items from this product.
My issue
My issue starts with my dropdown. If I merge cells, The value will go into the upperleft cell. This means other cells inside the merged cell are blank. This gives me a problem with conditional formatting.
My conditional formatting code example:
=if($D2=0;$E2="Created")
I have to change this code for every cell because of the condition combined with a dropdown. Having more than 250 rows would be inhumanly hard to do by hand.
My questions
Are there ways to give all cells of a merged cell the value of the combined cell in an efficient way?
Is there a better way to make my conditional formatting code applyable to merged cells?
This is my sheet
Product items collected sheet link (Shows the problem and solution!)
Product items collected sheet image (Version 1)
Product items collected sheet image (Version 2)
At the heart of this question is the operation of merged cells. When a cell is merged, say over several rows, only the cell at the top left of the merged cell can contain data, respond to conditional formatting, and so on. In a manner of speaking the other cells cease to exist and values CANNOT be assign to them.
The questioner asks:
Q: Are there ways to give all cells of a merged cell the value of the combined cell in an efficient way?
A: No. Not just in an "efficient" way; it's just not possible.
Q: Is there a better way to make my conditional formatting code applicable to merged cells?
A: No and yes ;)
No. In so far as a merged cell is concerned, everything is driven by the value in the top cell of the merged range. There are no other options for the "rest" of the merged cell.
Yes. I'd create a "helper" cells in Column F as in this screenshot
The code to achieve this is dynamic - it will automatically adapt to adding more products, more items, etc.
The logic is fairly simple: Start in F2, test whether E2 has a value (that is, is it the top of the merged cell?). If yes, then assign the value of E2 to F2 AND put that value in a variable for the following cells. If no, the cell in Column E must be part of a merged cell, so assign the value for Column F to the variable that was saved earlier.
function so5270705902() {
// basic declarations
var ss = SpreadsheetApp.getActiveSpreadsheet();
// note this is going to work on the second sheet in the spreadsheet - this can be edited.
var sheet = ss.getSheets()[1];
// Column B contains no merged cells, and always contains data (it is the BOM for the Products).
// so we'll use it to established the last row of data.
var Bvals = sheet.getRange("B1:B").getValues();
var Blast = Bvals.filter(String).length;
// Row 1 is a header row, so data commences in Row 2 - this can be edited
var dataStart = 2;
// Logger.log("the last row in column D = "+Blast);// DEBUG
// set up to loop through the rows of Column F
var mergedcellvalue = "";
for (i = dataStart; i < (Blast + 1); i++) {
// set the range for the row
var range = sheet.getRange(i, 6);
//Logger.log("row#"+i+" = "+range.getA1Notation()); DEBUG
// get the value in column E
var ECell = range.offset(0, -1);
var ECellVal = ECell.getValue();
//Logger.log("offsetrange#"+i+" range value = "+ECellVal);
//Logger.log("Column E, row#"+i+", value = "+ECell.getA1Notation()+" range value = "+ECellVal);//DEBUG
// when a row is merged, on the top row contains any data
// so we'll evaluate to see whether there is any value in this row in Column E
if (ECell.isBlank()) {
//Logger.log("ECell is blank. We're in the middle of the Merged Cell"); ??DEBUG
// Set the value to the lastes value of "mergedcellvalue"
range.setValue(mergedcellvalue);
} else {
//Logger.log("ECell has a value. We're at the top of the merged cell");//DEBUG
// paste the ECellVal into this range
range.setValue(ECellVal);
// Update the "mergedcellvalue" variable so that it can be applied against lower cells of this merged cell
mergedcellvalue = ECellVal;
} // end of the if isblank
} // end of the loop through column F
}
UPDATE 22 October 2018
For development purposes, I used a small range of only 14 rows in Column E. However the questioner's data covers over 250 rows, so I expanded development testing to cover 336 rows (yeah, I know, but I was copy/pasting and I ended up with 336 and was too lazy to delete any rows. OK?). I found that the code took over 81 seconds to process. Not good.
The primary reason (about 80 seconds worth) for the long processing time is that there is a getValue statement within the loop - var ECellVal = ECell.getValue();. This costs about 0.2 seconds per instance. Including getValue in a loop is a classic performance mistake. My bad. So I modified the code to get the values of Column E BEFORE the loop
var Evals = sheet.getRange("e2:E").getValues();.
I was surprised when the execution time stayed around the same mark. The reason was that the isBlank evaluation - if (ECell.isBlank()) { which previously took no time at all, was now consuming #0.2 second per instance. Not good++. So after searching Stack Overflow, I modified this line as follows:
if (!Evals[(i-dataStart)][0]) {.
Including setValues in a loop is also asking for trouble. An option would have been to write the values to an array and then, after the loop, update the Column E values with the array. However in this case, the execution time doesn't seem to have suffered and I'm leaving the setValues inside the loop.
With these two changes, total execution time is now 1.158 seconds. That's a percentage reduction of , um, a LOT.
function so5270705903() {
// basic declarations
var ss = SpreadsheetApp.getActiveSpreadsheet();
// note this is going to work on the second sheet in the spreadsheet - this can be edited.
var sheet = ss.getSheets()[2];
// Column B contains no merged cells, and always contains data (it is the BOM for the Products).
// so we'll use it to established the last row of data.
var Bvals = sheet.getRange("B1:B").getValues();
var Blast = Bvals.filter(String).length;
// Row 1 is a header row, so data commences in Row 2 - this can be edited
var dataStart = 2;
// Logger.log("the last row in column D = "+Blast);// DEBUG
// set up to loop through the rows of Column F
var mergedcellvalue = "";
// get the values for Column E BEFORE the loop
var Evals = sheet.getRange("e2:E").getValues();
for (i = dataStart; i < (Blast + 1); i++) {
// set the range for the row
var range = sheet.getRange(i, 6);
//Logger.log("row#"+i+" = "+range.getA1Notation()); DEBUG
// get the value in column E
var ECell = range.offset(0, -1);
var ECellVal = Evals[(i - dataStart)][0];
//Logger.log("Column E, row#"+i+", value = "+ECell.getA1Notation()+" range value = "+ECellVal);//DEBU
// when a row is merged, on the top row contains any data
// so we'll evaluate to see whether there is any value in this row in Column E
// instead is isblank, which was talking 0.2 seconds to evaluate, this if is more simple
if (!Evals[(i - dataStart)][0]) {
//Logger.log("ECell is blank. We're in the middle of the Merged Cell"); //DEBUG
// Set the value to the lastes value of "mergedcellvalue"
range.setValue(mergedcellvalue);
} else {
//Logger.log("ECell has a value. We're at the top of the merged cell");//DEBUG
// paste the ECellVal into this range
range.setValue(ECellVal);
// Update the "mergedcellvalue" variable so that it can be applied against lower cells of this merged cell
mergedcellvalue = ECellVal;
} // end of the if isblank
} // end of the loop through column F
}
UPDATE 3 March 2019
The questioner made his final changes to the code. This code is the final solution.
function reloadCreatedCells() {
// Basic declarations.
var ss = SpreadsheetApp.getActiveSpreadsheet();
// Note this is going to work on the second sheet in the spreadsheet - this can be edited.
var sheet = ss.getSheets()[1];
// Column B contains no merged cells, and always contains data (it is the BOM for the Products).
// so we'll use it to established the last row of data.
var D_vals = sheet.getRange("D1:D").getValues();
var D_last = D_vals.filter(String).length;
// First row with data.
var dataStart = 2;
// Set up to loop through the rows of Column H - K.
var mergedcellvalue = "";
// Get the values for Column H - K BEFORE the loop.
var H_K_vals = sheet.getRange("H2:K").getValues();
// How many people we have.
var people = 4;
// The first vertical row.
var rowStart = 12;
// Horizontal rows.
for (var h = 0; h < people; h++) {
// Vertical rows.
for (var v = dataStart; v < D_last; v++) {
// Set the range for the row.
var range = sheet.getRange(v, rowStart + h);
// Logger.log(range.getA1Notation()); //DEBUG
// Get the value in column H - K.
var H_K_Cell = range.offset(0, -people);
// Adding Created and not created values inside L - O.
var H_K_CellVal = H_K_vals[(v - dataStart)][h];
// Logger.log(H_K_Cell.getA1Notation() + ': ' + H_K_CellVal); //DEBUG
// When a row is merged, the value is only inside the top row.
// Therefore, you need to check if the value is empty or not.
// If the value is empty. Place the top value of the merged cell inside the empty cell.
if (!H_K_vals[(v - dataStart)][h]) {
// Logger.log(H_K_Cell.getA1Notation() + ": is blank. We're below the top cell of the merged cell."); //DEBUG
// Set the value to the top cell of the merged cell with "mergedcellvalue".
range.setValue(mergedcellvalue);
} else {
// Logger.log(H_K_Cell.getA1Notation() + ": has a value. We're at the top of the merged cell."); //DEBUG
// Paste the H_K_CellVal into this range.
range.setValue(H_K_CellVal);
// Update the "mergedcellvalue" variable, so that it can be applied against lower cells of this merged cell.
mergedcellvalue = H_K_CellVal;
} // end of the if isblank.
} // End of the vertical row loop.
} // End of the horizontal row loop.
}

Google spreadsheet script for insert new row based on value of cell

Spreadsheet data looks like this
function myFunction()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('Active Listeners');
sh.insertRowBefore(15551)
}
As i have large range of rows that could be work on. If the value of the range matches with "Apr 9" then insert row before to that. Could anyone help me to get that.
A 'for loop' to cycle through your rows from the bottom would almost do the trick. The loop inserts a row after each row specified by i. Keep in mind you'll need a different solution if your Apr 9 column is formatted as a date. This works for plain text only. You can select the column and change to plain text with "Format > Number > Plain Text" on the menu.
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('Active Listeners');
//var shlen = sh.getDataRange().getLastRow(); //returns integer last row
var shlen = Browser.inputBox("Enter Last Row of Preferred Range", Browser.Buttons.OK_CANCEL);
var ecell = sh.getActiveCell().getA1Notation();
You may need a different dataRange (below), I've just grabbed the parameters of data in your whole sheet (above), then grabbed a range specified in A1 notation of "A1:B[number reference of bottom row]" The modification may be that you need "B1:C" + [shlen] or whichever other range.
if (shlen >= 1) {
var dataRange = sh.getRange("A1:A" + shlen).getValues();
for (var i = shlen; i > 0; i--) {
var row = dataRange[i-1];
if (row[0] == "Apr 9") {
sh.insertRowAfter(i-1)
}
}}
}
Someone more knowledgeable than me can pitch in if they have a better answer, but my only solution (which should be ok if it's a one-of) would be to just repeat the script a few times, starting at the row of your choice each time. Select cell A1 and then press "control (or command) + down arrow". It will take you to the first gap, which should be where the previous script ended. Remember the row number you're up to and plug that in the input box when you run the script again. Might take a few iterations but you'll get there.
If this process is going to be done repeatedly then best of luck in finding a solution :)

Increment a Google Sheet cell by 1 via App Scripts

I'm trying to increment a number in a Google sheet cell by one, when I custom menu item is clicked. But the result in the spreadsheet is either #NUM! or 1range. I've tried a number of different methods, as shown in the comments in my code.
Note that the number is stored as a custom number format so that it has 4 leading zeros, eg: 00001
function onOpen() {
var ui = SpreadsheetApp.getUi();
// Or DocumentApp or FormApp.
ui.createMenu('OrderNumber')
.addItem('Generate next order number', 'menuItem1')
.addToUi();
}
function menuItem1() {
SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var range = sheet.getRange("N11");
range = 1 + +range;
// For the previous line, I've also tried: range = 1 + parseInt(range)
// For the previous line, I've also tried: range = 1 + Number(range)
SpreadsheetApp.getActiveSheet().getRange('N11').setValue(range);
}
As a test if it just set it to a number it shows in the spreadsheet correctly, e.g:
range = 5
SpreadsheetApp.getActiveSheet().getRange('N11').setValue(range);
Any help on this would be greatly appreciated. Thanks.
A Range is an object containing a lot of information: values but also formatting, data validation, font size etc.
You need to call getValue() on range first to extract the number:
var range = sheet.getRange("N11").getValue();

How to add and remove a character from a cell to force a value change

I'm using a function to count the number of colored cells, but when they change color (not value), the spreadsheet doesn't recognize a value change and my formula doesn't update with the new number of colored cells.
To manually force an update, I have to add a character to that cell, then remove it, so that the sheet will recognize a value change and recount the number of colored cells. Is there a way to automate this and force the sheet to add and remove a character in order to force an update?
Hopefully, this is clear enough to be answerable.
You can simply use onEdit function if there is any data update on the cells. But for your case, I have tested below scripts works good, but I am still confused it is the best practice of coding, because I could not find any other options to solve your question.
//Below function will count the number of cells with the specified background color
function countWhereBackgroundColorIs(color, rangeSpecification) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var range = sheet.getRange(rangeSpecification);
var bgColors = range.getBackgrounds();
var k = 0;
for (var i in bgColors) {
for (var j in bgColors[i]) {
if(bgColors[i][j] == color)
k = k +1;
}
}
return k;
}
//Below function will append a space" " on the cell A1 data and then remove the appended char. This is for updating the range. Make sure that at least a cell mentioned in above function countWhereBackgroundColorIs() is getting updated. Here Used 'A1'
function updateSheet()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var cell = sheet.getRange("A1");
var d = cell.getValue();
cell.setValue(d+" ");
SpreadsheetApp.flush();
cell.setValue(d);
SpreadsheetApp.flush();
}
//creating a time based trigger to execute the updateSheet() function for every one min. Minimum time is 1min. This trigger creation is a one time activity and this can be done in GUI also Resources>All your triggers.
function createTimeDrivenTriggers() {
ScriptApp.newTrigger('updateSheet')
.timeBased()
.everyMinutes(1)
.create();
}

Resources