Popup box if a negative number based from formula - google-apps-script

I am trying to figure out a script that will popup a box if a number in column F goes negative. I think I may be running into an issue due to the negative number generating from a formula as opposed to someone manually typing it in. Here is the script I have been working with:
function onEdit(event){
var sheet = event.source.getActiveSheet().getName()
var editedCell = event.range.getSheet().getActiveCell();
if(sheet=="Numbers"){
if(editedCell.getColumn() == 6 && event.value==-0.1){
Browser.msgBox('It looks like there is a negative number in Column F.');
}}}
I think it may have something to do with the 'editedcell' function but I am not 100%. Does anyone know of a better script that will popup a box even if a formula is generating a negative number (the value in column F will be a formula that may generate a negative)?
At this point, nothing happens with this code unless I manually type "-1" or some other negative in Column F.

Answer:
The event object returns the range of the cells you edited, not the ones edited by a script or formulae. As well as this, there are a few things you can do to fix or optimise your code.
Code Fixes:
Firstly, you can change your conditional to optimise the script and only make calls when you need them - this I have done with a slightly different conditional order.
As well as this, rather than event.range.getSheet().getActiveCell() you need to specify the values of column F explicitly, and use the getDisplayValues() function as this will also pick up the values if the cell contains a formula:
function onEdit(event) {
var sheet = event.source.getActiveSheet();
if (sheet.getName() != "Numbers") {
return;
}
var values = sheet.getRange('F:F').getDisplayValues()
for (var i = 0; i < values.length; i++) {
if (values[i] < 0) {
Browser.msgBox('It looks like there is a negative number in Column F.');
return;
}
}
}
References:
Google Apps Script - Event Objects
Google Apps Script - Best Practices

Related

Google sheets script writing, how do I prevent an auto update or recacluation of my timestamps in googel sheets?

I attached a copy of the spreadsheet I run in google. The concept is when someone begins picking an order, they will for instance type their name in I3 and their time will start. once complete they will type their name in J3 , each causing a time stamp below leading to a total duration time. later it will factor percentages.
The problem is the time stamps seem to randomly update without prompting to do so. it seems to be when it is printed or reopened. This will cause inaccuracies in the times and percentages. Any assistance would be greatly appreciated
It would appear that I should write a script to accommodate this need, but I haven't the slightest on how to do this. I was directed to this forum from a reply in google docs help forum
enter link description here
I don't think you can choose on which cell you update or recalculate (Tell me if I'm wrong).
A non-optimized workaround (Maybe some expert have got better solution):
function onEdit(e){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var i = 3;
while(i <= sheet.getLastRow())
{
var data = sheet.getRange(i, 9, 3, 2).getValues();
if(data[1][0].length == 0)
{
if(data[0][0] != "")
{
var d = new Date();
sheet.getRange(i+1, 9).setValue(d);
}
}
if(data[1][1].length == 0)
{
if(data[0][1] != "")
{
var d = new Date()
sheet.getRange(i+1, 10).setValue(d);
}
}
i=i+3;
}
}
The onEdit(e) function is a simple trigger implement in Google Spreadsheet. It will trigger each time a cell is edit.
On this code above, we iterate to check on each element if a name was added or not. If so, the code will set the date and time below the name just added. Since you set the value on one single moment, no auto update will modify those time.
for this workaround, you need to remove the formula you put on the cell where you want your timestamp (i.e I4 and J4). You can keep the cells which calculate the difference between time, they should work with no problem.
Paste below script in the script editor and save it.
function onEdit(e) {
if (e.range.rowStart % 3 != 0 || [9, 10].indexOf(e.range.columnStart) == -1) return;
var o = e.range.offset(1, 0);
if (!o.getValue()) o.setValue(new Date())
}
Don't run the script by clicking the play button in the script editor. Instead to back to any of the tabs, clear all cells where you now have the =now() formula and enter a name. See if the timestamp appears.

I have a pseudocode, but can't code yet

thank you and sorry for my incredibly unexperienced question in advance. So, I want to make a code and I know what I want it to do, I just don't know how to program. What I need is:
function GenPre()
1.- delete range Presupuesto!A12:C42
2.- copy range Imp!A2:Imp!C33 VALUES in Presupuesto!A12:Presupuesto!C42 (Imp cells are formulas, and I want to copy just the values)
3.- show only used rows in column A in Presupuesto!A12:A42 (consider some rows will be already hidden, so unhiding them first would be an idea)
4.- go to sheet Presupuesto (once I do this function, I want to end up on the sheet Presupuesto
end Generar
This function will be runned by a button in another sheet in the same spreadsheet.
and so far, I have this:
function GenPre() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetbyname(Presupuesto);
//next step is to select and delete the content of the range on the sheet
}
I know I'm asking for much, I just can't find much about selecting defined cells... and I really don't know how to program yet.
Thanks a bunch!!
Edit
So, I started tweaking with what k4k4sh1 answered and got this (AND reading other posts on hiding rows containing "x" on a given cell):
function GenPre() {
var sheetp = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Presupuesto') //name a variable to the sheet where we're pasting information
var sheetc = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Imp') //name a variable to the sheet frome where we're copying information
sheetp.getRange('a12:c41').clearContent() //delete all values in the range where we're copying
sheetc.getRange('A2:C31').copyValuesToRange(sheetp,1,3,12,41); //copy from source range to destination range
sheetp.showRows(12,41); //make sure all rows in the destination range are shown
for( i=12 ; i<=41 ; i++) {
if (sheetp.getRange('A'+i).getValue() == '') { // status == ''
sheetp.hideRows(i);
}
}
}
Te script is running how it should, but now, I want it to run faster (takes 12 seconds to run, when it doesn't really look that heavy), and is there a function to switch my view to sheetp? thank you all!
You're asking us to do all the work :)
Let's start from your piece of code:
the method .getSheetByName(shName) accepts a string as argument, so you should change it to
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Presupuesto');.
Mind that JavaScript is case-sensitive, so .getSheetbyname is not the same as .getSheetByName().
According to Sheet Class Reference use sheet.getRange() to get your Range Object. Take a look to Range Class Reference: to clear the range content including formats use .clear(), to clear just the content leaving the formatting intact use .clearContent().
To hide unused rows try:
function hideRows(sheetName, column) {
var s = SpreadsheetApp.getActive().getSheetByName(sheetName);
s.showRows(1, s.getMaxRows());
s.getRange(column)
.getValues()
.forEach(function (r, i) {
if (r[0] == '') {s.hideRows(i + 1);}
});
}
// hideRows('Presupuesto', 'A12:A42');

Change cell background color onEdit based on value

How can I change the cell background color based on the cell's content in an onEdit() function?
I've had many versions of code that I tested for this - some working almost right, some not working at all.
But I have yet to get this to work the way I need it to.
Please forgive the lack of elegance in the way that this is written, but I actually need to keep the code as straightforward as possible since there will be many cell changes, many conditionals, and many differing numbers of cells that will be changed depending on what gets changed on the worksheet.
Ok, so here goes...
function onEdit(event)
{
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet2");
var changedCell= event.source.getActiveRange().getA1Notation();
if (changedCell == 'B3') {
var c = ss.getRange("B3").getValue();
if (c < 2); {
ss.getRange("B3").setBackgroundColor('#ff0000');
ss.getRange("B12").setBackgroundColor('#ff0000');
}
if (c > 1); {
ss.getRange("B3").setBackgroundColor('#000000');
ss.getRange("B12").setBackgroundColor('#000000');
}
}
}
A few things to note
1.The name of the method is setBackground and not setBackgroundColor
2.How is the cell B3 formatted. The comparison works only if it is formatted as an integer. In most cases, Google Spreadsheet automatically formats the cells based on the data type, but if I'm writing code, I'd double check. So use something like
var c = parseInt(ss.getRange("B3").getValue()) ;
3.The semicolon is not needed after the if condition. That will terminate the if condition immediately. So use
if (c < 2) {
and
if (c > 1) {
4.Finally, I don't know how data comes into B3, but if you have 1.5 in B3, then both the if conditions become true and your background color is overwritten. So, I suggest that you use a if..elseif
For better readability, I'd use setBackground('red') and setBackground('white') for the common colours.
In addition to the advice from Srik, you should consider the efficiency of your onEdit() function. The general idea is to determine whether you should bail out as soon and as cheaply as possible, and then optimize the rest of the code according to the Best Practices (minimize Service calls, mainly).
It looks like you want the onEdit() to perform only on "Sheet2", but the way you have written it, it will trigger for a change on any sheet, but mess around with colors on "Sheet2". (Not a visible problem, since the conditions on "Sheet2" are the ones being used to decide on coloring, but you'll be burning up script execution time needlessly.) By using the event info to figure out what sheet has been updated, and exiting if it's not "Sheet2", the cost of the onEdit() drops.
I agree with Srik about the if statements... I just can't tell what you wanted. I don't think the suggestion of else if alone will solve it, though, because it would treat c > 1 as c > 2... if you wanted that, why not write it? Maybe you wanted no color for 1 < c < 2, with red for c < 1 and black for c > 2? You should sort out the logic in that part.
Here's how an optimized onEdit() would look, making maximum use of the event info:
function onEdit(event)
{
var ss = event.range.getSheet();
if (ss.getName() !== "Sheet2") return; // Get out quickly
var changedCell = event.source.getActiveRange();
var changedCellA1 = changedCell.getA1Notation();
if (changedCellA1 !== 'B3') return;
var c = event.value; // We know we edited cell B3, just get the value
var background = 'white'; // Assume 1 <= c <= 2
if (c > 2) {
background = 'red';
}
else if (c < 1) {
background = 'black';
}
changedCell.setBackground(background);
ss.getRange("B12").setBackground(background);
}

Google Drive - How to Autopopulate Google Spreadsheet with Concatenate functions when users submit data via webform?

I have a Google Drive Spreadsheet in which I'm trying to have a formula autopopulate all the way down the rows, as users submit data via a webform attached to the spreadsheet. The tricky part is that I use a CONCATENATE function already, to perform a concatenation of the data submitted on several columns into one single cell.
However, for the CONCATENATE function to work, I have to "apply" it to newly submitted rows.
Is there a way to automate the filling of this formula down the rows in the spreadsheet?
I've tried to place an ArrayFormula function to it, even setting the range (A1:A), but I couldn't find the proper syntax for it work, if it may like this.
The function goes:
=CONCATENATE(CHAR(10)&X14&V14&Y14&J14&" "&K14&" "&L14&M14&N14&O14&" "&P14&" "&Q14&R14&S14&CHAR(10)&T14&"."&CHAR(10)&U14&"."&CHAR(10)&W14&CHAR(10)&CHAR(10)&CHAR(10)&I14&CHAR(10)&Z14&" "&AA14&CHAR(10)&AB14&AC14&AD14&AE14&AF14&AG14&AH14&"."&CHAR(10)&AI14&AJ14)
Any suggestion will be greatly appreciated.
(Answered by the OP in a question edit. Transformed into a community wiki answer. See Question with no answers, but issue solved in the comments (or extended in chat) )
The OP wrote:
I've found a little script from the Script Gallery of Google Spreadsheet (AutoFormulas, by tuxincarnate[#]gmail[dot]com), which solves the trick! Just tested it with a dozen of submissions from the webform and it does what it promises, autopopulate with formulas down the columns where applied.
// Updates all rows except the last to have the same formulas as row 3 (allowing for a header and different row 2 formula)
// To activate this functionality, the last row in the column should have the value '(auto)'
function UpdateFormulas() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetCount = ss.getNumSheets();
for (var sheetIndex = 0; sheetIndex < sheetCount; ++sheetIndex) {
var sheet = ss.getSheets()[sheetIndex];
var rowCount = sheet.getMaxRows();
var columnCount = sheet.getMaxColumns();
if (rowCount < 5) continue;
for (var columnIndex = 1; columnIndex <= columnCount; ++columnIndex) {
if (sheet.getRange(rowCount, columnIndex).getValue() == '(auto)') {
var row3Range = sheet.getRange(3, columnIndex);
for (var rowIndex = 4; rowIndex < rowCount; ++rowIndex) {
if (sheet.getRange(rowIndex, columnIndex).isBlank()) {
row3Range.copyTo(sheet.getRange(rowIndex, columnIndex));
}
}
}
}
}
}
#David Tew wrote:
Whilst you have a solution already, you might like to consider for the future the following formula, which is what you were originally looking for (You don't need to use CONCATENATE since you are choosing the columns to join together with & symbol)
=arrayformula(CHAR(10)&X2:X&V2:V&Y2:Y&J2:J&" "&K2:K&" "&L2:L&M2:M&N2:N&O2:O&" "&P2:P&" "&Q2:Q&R2:R&S2:S&CHAR(10)&T2:T&"."&CHAR(10)&U2:U&"."&CHAR(10)&W2:W&CHAR(10)&CHAR‌​(10)&CHAR(10)&I2:I&CHAR(10)&Z2:Z&" "&AA2:AA&CHAR(10)&AB2:AB&AC2:AC&AD2:AD&AE2:AE&AF2:AF&AG2:AG&AH2:AH&"."&CHAR(10)&‌​AI2:AI&AJ2:AJ)
this should go in the second row
The OP wrote:
#DavidTew showed me a clear and easy way to have it solved. The ArrayFormula should go alone, telling it to put together the ranges for every column into every single row. Works like charm. Proper syntax is: =arrayformula(A2:A&B2:B&C2:C), in order to have contents from cells A2, B2 and C2 "concatenated" into the cell where the ArrayFormula is applied. The most important issue is that by using this function, it autpopulates all the way down the rows as users submit data via a webform attached to the spreadsheet.

Google Apps Script - Spreadsheet run script onEdit?

I have a script that I use in my spreadsheet. It is at cell B2 and it takes an argument like so =myfunction(A2:A12). Internally the function gets info from a large range of cells.
Nothing seems to work. I tried adding it to
Scripts > Resources > Current Project Triggers > On edit
Scripts > Resources > Current Project Triggers > On open
How can I have this function update the result with every document edit?
When you are making calls to Google Apps services inside your custom function (like getRange and getValues etc), unfortunately there is no way of updating such custom functions with each edit, other than passing all of the cells that you are "watching" for editing.
And, perhaps even more frustratingly, the workaround of passing say a single cell that references all of your "watched" cells with a formula doesn't trigger an update - it seems that one needs to reference the "watched" cells directly.
You could pass GoogleClock() as an argument which will at least update the function output every minute.
But the advice from many members on this forum (who have much more knowledge about this stuff than me) would simply be: "don't use custom functions".
I am not sure if this exact code will work but you can try something like this...
function onEdit(event){
var activeSheet = event.source.getActiveSheet();
if(activeSheet.getName() == "ActiveSheetName") {
var targetSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("TargetSheetName");
targetSheet.getRange(2,2).setValue("=myfunction(A2:A12)");
}
}
Assuming that B2 cell in on the sheet "TargetSheetName" and assuming that the edited cell is on the sheet "ActiveSheetName", the function onEdit will trigger when you edited any cell in any sheet. Since there is an if statement to check if that edited cell is on the sheet "ActiveSheetName" it will trigger only if the edited cell is on that sheet and it will set the B" cell to the value =myfunction(A2:A12), forcing it to update (i guess).
hope that i am correct and that i was helpful
I had a similar issue, for me I wanted to "watch" one particular cell to trigger my function.
I did the following (pretending A1 is the cell i am watching)
IF(LEN(A1) < 1, " ", customFunction() )
This successfully triggered if I ever edited that cell. However:
"Custom functions return values, but they cannot set values outside
the cells they are in. In most circumstances, a custom function in
cell A1 cannot modify cell A5. However, if a custom function returns a
double array, the results overflow the cell containing the function
and fill the cells below and to the right of the cell containing the
custom function. You can test this with a custom function containing
return [[1,2],[3,4]];."
from: https://developers.google.com/apps-script/execution_custom_functions
which makes it almost useless, but it might work for your case?
If the custom function is assigned to a project trigger it has more power so personally I ended adding it to "Scripts > Resources > Current Project Triggers > On edit"
and basically "watched a column" so it only did things if the current cell was within the "edit range". This is a bit of a bodge and requires some hidden cells, but it works for me at the moment
var rowIndex = thisspreadsheet.getActiveCell().getRowIndex();
var colIndex = thisspreadsheet.getActiveCell().getColumn();
//clamp to custom range
if(rowIndex < 3 && colIndex != 5)
{
return 0;
}
//check against copy
var name = SpreadsheetApp.getActiveSheet().getRange(rowIndex, 5).getValue();
var copy = SpreadsheetApp.getActiveSheet().getRange(rowIndex, 6).getValue();
if(name != copy )
{
///value has been changed, do stuff
SpreadsheetApp.getActiveSheet().getRange(rowIndex, 6).setValue(name);
}