Add up previous and current cell values when changed - google-apps-script

I am trying to update 1 value in a Google sheet. When I select a cell which already contains a number (ex: 500), and I write a number (ex: 100), it should add them up without pressing a button (500+100).
Right now, when I edit the cell, the onEdit(e) will get triggered. I can then grab the cell's value by doing:
SpreadsheetApp.getActiveSpreadsheet().getActiveRange().getValue()
However, I cannot get the previous (before the change/edit) value, so I will always end up adding the two same values together. Here's some example code:
//Obviously only example code
var sheet = SpreadsheetApp.getActiveSpreadsheet();
function onEdit(e) {
if(activeRange.getColumn() == 3) {
var currentValue = sheet.getActiveRange().getValue();
var newValue = currentValue + sheet.getActiveRange().getValue();
sheet.getActiveRange().activeRange.setValue(oldValue);
}
}
Obviously I want to replace sheet.getActiveRange().getValue() in the newValue to the PREVIOUS value, otherwise I add both values up, which are the same. I hope you get what I mean. The previous value of the cell is never being sent/saved/logged anywhere.
In something like Javascript/jQuery, I would do this: When cell has been selected, save the current value in a variable. Then, when the cell has changed value, add the old value to the new value, and change the cell's value to be the newest added up value. Farely easy, but I cannot find anything about how to do this kind of trigger in Google Apps Script.
How can I possibly do this? Thanks!

"it should add them up without pressing a button" no that won't happen, but if you have 500 in Sheet1 cell C2 you need to duplicate it else where like i first commented - maybe C2 on another tab ( Sheet2 ). Then when you type 100 (and hit enter) you'll need to go and grab the old duplicated value(500), add it to the current edited value (100) then set the new calculated one(600):
function onEdit(e) {
if(activeRange.getColumn() == 3) {
var ss = e.source;
var sheet1 = ss.getActiveSheet();
var sheet2 = ss.getSheetByName("Sheet2");
var old = sheet2.getRange('C2').getValue(); // duplicated value
var current = sheet.getActiveRange().getValue(); // value after hitting enter to edit
var new = current + old;
sheet1.getActiveRange().setValue(new);
sheet2.getRange('C2').setValue(new); // update duplicated value
}
}
You'll need to handle matching up what rows to look at for duplicate values but for this example I just used row 2.

Related

Using cell input into function and ovewriting result into result in same cell in Google Sheet

In Google Sheet, I would like to take the input of a cell, make a calculation, and display the result in the same cell in a different format. This would likely always be a percentage value that I would use conditional formatting to color the cell to provide a 'dashboard' view of statistics.
Example would be usage statistics for a month.
Assets
Records
limit
50
1000
November
29
295
Assets
Records
limit
50
1000
November
58%
30%
I found a Quora post detailing how to create your own scripts, so I believe I have the baseline of taking and modifying content of a cell:
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var cell = ss.getActiveSelection();
var cell_input = cell.getValue();
var cell_address = cell.getA1Notation()
var cell2 = ss.getRange(cell_address);
cell2.setFormula('=2*'+cell_input)
}
In this example, I'm unsure how to reference the cell it should be dividing against.
Here's a general example that should give you an idea of how to manipulate the cells, formats and values in Sheets:
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var cell = ss.getActiveSelection();
var cell_input = cell.getValue();
var divisor = cell.offset(-1, 0)
if (e.range.getA1Notation()=="B3" || "C3"){
cell.setNumberFormat("#").setValue(Math.ceil((cell_input / divisor.getValue()) * 100) + "%")
}
}
If you create a sheet that looks like this you will get the behavior you were looking for in B3 and C3:
How it works:
Add the e parameter to the onEdit(e) trigger so you can use e.range to read the range that called the function.
To get the cell you're dividing against you need to either know its exact range or its relative position to the cell that called it. Since in your sample the percentage cell is below the divisor, you can just offset() it by -1, which returns the cell above it. If the relative position changes you'll need to take this into account to modify the offset but it should work in general if your table has a consistent structure.
You'll need to use an if to fire the trigger only in a specific range, otherwise the entire sheet will be affected. You do this by comparing the caller e.range to your desired range. In this example for the sake of simplicity I just had two cells so I just compared the individual B3 and C3 cells to e.range.getA1Notation(), but if you want a bigger range you'll probably want to use a more advanced technique like the ones in this question
To get around the format problem described in TheWizEd's comment I'm forcing the cell to a text value using setNumberFormat("#"). This way if you enter a value a second time it will read it as a number rather than a percent. I tested using the value elsewhere in the sheet and it still works as a percentage when needed, but watch out for unintended behavior.
Sources:
Simple triggers
Range object documentation
When working with triggers, take advantage of the event object.
Replace
function onEdit(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var cell = ss.getActiveSelection();
var cell_input = cell.getValue();
by
function onEdit(e){
var cell_input = e.value ?? 0;
Notes:
SpreasheetApp.Spreadsheet.getActiveSeletion() returns the first range that belongs to the active selection, this might cause problems because the active seleccion could include multiple cells, and the current cell could be any cell in the selection even one cell that is not part of the returned range.
SpreadsheetApp.Range.getValue() returns the value of the top left cell in the range. A user could select multiple cells and use the tab key to change the current cell, so under certain circunstances the value obtained this way might not be the value of the edited cell.
e.value ?? 0 is used to get 0 as the default value, i.e., when a cell is cleared.
As onEdit is triggered when any cell is edited your script should include a condition to only change the corresponding cells.
function onEdit(e){
const cell_input = e.value ?? 0;
if(e.range.rowStart === 3 && [2,3].includes(e.range.columnStart)){
const newValue = (100 * cell_input / e.range.offset(-1,0).getValue()).toFixed() + '%';
e.range.setValue(newValue);
}
}
The above uses
SpreadsheetApp.Range.offset to get the cell above of the edited cell.
Writes a the percentage as string, taking advange of the Google Sheets automatic data type assignation.
References
https://developers.google.com/apps-script/guides/triggers
https://developers.google.com/apps-script/guides/triggers/events

Automatically populate cell in Google Sheets when another cell in same row is manually filled but error

In Google Sheets, I want to create a macro that will automatically populate a column in each row when another column in that row is manually filled. The autofilled cell will use a formula that import from other googlesheet file and using query to import the data. I currently using script but i cant put a apostrof to complete my formula
`
function onEdit(e) { //Runs every time the sheet is edited
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('DB Mitra'); //Change this to whatever your sheet is named
var inputCol = sheet.getRange('B2'); //Change this to whatever column the date is going to be entered
//This is the range that will be checked. Slightly redundant, but makes it easier to reuse this script without needing to retype every variable
var myRange = inputCol;
//Get the row & column indexes of the active cell
var row = e.range.getRow();
var col = e.range.getColumn();
//Check that your edited cell is within the correct column
if (col == myRange.getColumn()) { //This runs only if a value is entered into the column defined in 'inputCol'
if(sheet.getRange(e.range.getA1Notation()).getValue() == '') {return}; //If the edited cell is empty (ie the date is deleted, nothing happens)
if(row == 1) {return} //If the header is changed, nothing happens
let codeCell = sheet.getRange('D'+row); //Change to the column that will store the generated code value
codeCell.setValue('=QUERY({IMPORTRANGE("1-K_ZltvOev2t9iqKIOHR8B-PY6ODBKYAwTjaDzLbHJE";"2022!B2:T494")};"SELECT Col4 WHERE Col1 = '"&B'+row+'&"'")');
//let hardValue = codeCell.getValue(); //Gets the value from the formula you just entered
//codeCell.setValue(hardValue); //Replaces the formula with just the resulting value
};
}
`
the formula should be like this
enter image description here
but it always error if i put it like that
enter image description here
Thank you for anyone willing to help me. credit to Automatically populate cell in Google Sheets when another cell in same row is manually filled for the inspiration code
In your script, how about modifying using the template literals as follows?
From:
codeCell.setValue('=QUERY({IMPORTRANGE("1-K_ZltvOev2t9iqKIOHR8B-PY6ODBKYAwTjaDzLbHJE";"2022!B2:T494")};"SELECT Col4 WHERE Col1 = '"&B'+row+'&"'")');
To:
codeCell.setValue(`=QUERY({IMPORTRANGE("1-K_ZltvOev2t9iqKIOHR8B-PY6ODBKYAwTjaDzLbHJE";"2022!B2:T494")};"SELECT Col4 WHERE Col1 = '"&B${row}&"'")`);
or
codeCell.setFormula(`=QUERY({IMPORTRANGE("1-K_ZltvOev2t9iqKIOHR8B-PY6ODBKYAwTjaDzLbHJE";"2022!B2:T494")};"SELECT Col4 WHERE Col1 = '"&B${row}&"'")`);
Reference:
Template literals

getlastrow, then last column, and then populate based on that information

Recently, there was a program by the name of Eddienuput that allows you to properly train against basically anything in training mode. One issue is that you have to make a text file with all the button presses, as well as the timing for each. Once this text file is made, the inputs are added into a virtual controller.
The working sheet can be seen here.
The idea here is that you put the data of the character on the second sheet, then on the generator sheet you select the buttons you want, then press the [>] to put it to the right of the current buttons stored, and you use the [v] button.
When I hit the down arrow (I've assigned the script to the buttons), the three entries needed for the program to register the input are inserted one row below the active row.
The problem is the Right arrow. I sort of have it right (it puts the three inputs needed to the right.. but it isn't finding the bottom-right entry and putting it to the right of it, but instead it looks for the furthest right column and puts it to the right of everything instead of next to the last cell on the last row.
here is the code as I have it now:
function addbuttontoString()
{
//DEFINE ALL ACTIVE SHEETS
var ss = SpreadsheetApp.getActiveSpreadsheet();
var eddGenSheet = ss.getSheetByName("Eddie Input Generator");
var lrwithcontentofEddGenSheet = eddGenSheet.getLastRow()+1;
var lcwithcontentofEddGenSheet = eddGenSheet.getLastColumn();
var CharacterFrameDataSheet = ss.getSheetByName("CharacterFrameData");
//Current string Variables
var totalcurrentstringRows = lrwithcontentofEddGenSheet-4;
var fullcurrentString = eddGenSheet.getRange(5,1,totalcurrentstringRows, lcwithcontentofEddGenSheet);
var cslastRow = fullcurrentString.getLastRow();
var bottomrightcolumnNumber = eddGenSheet.getRange(lrwithcontentofEddGenSheet,1,1,lcwithcontentofEddGenSheet).getLastColumn();
Logger.log(bottomrightcolumnNumber)
//GET LAST ROW #OF ITEM SHEET
var lastrowItem = CharacterFrameDataSheet.getLastRow();
// Get the perfect Eddie value from Character Frame Data you are grabbing data for
var button = eddGenSheet.getRange('B4').getValue();
// Set up the Perfect Frame Variable
for(var i = 2; i <= lastrowItem; i++)
{
if(button == CharacterFrameDataSheet.getRange(i, 1).getValue())
{
var Perfectframe = CharacterFrameDataSheet.getRange(i, 4).getValue();
}
}
Logger.log(fullcurrentString.isBlank());
if(fullcurrentString.isBlank()){
// POPULATE eddGen SHEET Same Line
eddGenSheet.getRange("A5").setValue(button);
eddGenSheet.getRange("B5").setValue("F");
eddGenSheet.getRange("C5" ).setValue(Perfectframe);
} else {
eddGenSheet.getRange(lrwithcontentofEddGenSheet-1, bottomrightcolumnNumber +1).setValue(button);
eddGenSheet.getRange(lrwithcontentofEddGenSheet-1, bottomrightcolumnNumber +2).setValue("W");
eddGenSheet.getRange(lrwithcontentofEddGenSheet-1, bottomrightcolumnNumber +3).setValue(Perfectframe);
}
}
I believe your goal as follows.
When you click the right arrow button, you want to add the values from the 1st empty column of the row.
In your script, in order to retrieve the coordinate of the column for putting values, getLastColumn() is used as follows. In this case, the 1st empty column of the row is not retrieved. The last column of the sheet is retrieved. So, how about the following modification?
From:
var bottomrightcolumnNumber = eddGenSheet.getRange(lrwithcontentofEddGenSheet,1,1,lcwithcontentofEddGenSheet).getLastColumn();
To:
var bottomrightcolumnNumber = eddGenSheet.getRange(lrwithcontentofEddGenSheet - 1, 1).getNextDataCell(SpreadsheetApp.Direction.NEXT).getColumn();
In this modification, the 1st empty column is retrieved using getNextDataCell().
Note:
I'm not sure about your actual situation. So when above modification was not useful for your situation, can you provide the detail information about your actual situation? By this, I would like to modify it.
References:
getLastColumn()
getNextDataCell(direction)

Google Sheets Script - Move Rows from One Tab to Another

I'm trying to modify the code from the below forum post to help fit my needs:
https://productforums.google.com/forum/#!topic/docs/ehoCZjFPBao/discussion
function onEdit() {
// moves a row from a sheet to another when a magic value is entered in a column
// adjust the following variables to fit your needs
// see https://productforums.google.com/d/topic/docs/ehoCZjFPBao/discussion
var sheetNameToWatch = "Transfers";
var columnNumberToWatch = 15; // column A = 1, B = 2, etc.
var valueToWatch = "yes";
var sheetNameToMoveTheRowTo = "Archive";
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveCell();
if (sheet.getName() == sheetNameToWatch && range.getColumn() == columnNumberToWatch && range.getValue() == valueToWatch) {
var targetSheet = ss.getSheetByName(sheetNameToMoveTheRowTo);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sheet.getRange(range.getRow(), 1, 1, sheet.getLastColumn()).moveTo(targetRange);
sheet.deleteRow(range.getRow());
}
}
What this code does is moves a row from one tab to another when a specific value is entered (in the code above, it moves a row from "Transfers" to "Archive" when the word "yes" is entered in column 15 (Column O).
My needs are a little bit different. I cannot have a user moving one row at a time because there may be multiple rows involved and combined together, they will need to meet certain criteria (e.g. do the amounts in all rows balance to 0, is the same account used, etc.). This I believe is a rudimentary data validation.
Therefore, I tried to make a formula so that if I entered the word yes in cell F8, it populates the word "Send" (used to be "yes", changed for clarity) to column O. If the word yes is entered in cell F8, then every row with the word "Send" should be moved to the Archive Column. The problem is, the above code relied on a function called OnEdit, and populating the rows with the word Send via formula does not trigger the script to run. The above code needs the user to manually enter the correct keyword in order to move the rows over.
Can someone help to modify or rewrite the code so that it looks for a user to manually type in the keyword "yes" in cell F8, and then have it move any row that had the word "Send" populated by a formula in Column O?
The below Google Sheet is a simplified example of what I'm trying to do. Additional comments can be found on the "MASTER - DO NOT EDIT" tab.
https://docs.google.com/spreadsheets/d/1iajS90qvwEOGVnl2lpDbVtcI532OO8n4NLZEBDUpVzA/edit#gid=398066315
Thanks for looking. If anyone needs additional info, please let me know.
~~~~~~~~~~~~~~~~~~~~~~~~~~~
EDIT: I tried out Cooper's code below, and it seems to work. I'm a novice at this, but I've managed to add a few things to make it run onEdit, and to delete the initial trigger word.
An example of this slightly tweaked code running can be found below:
function onEdit() {
archiveRows();
clearCells();
}
function archiveRows()
{
/*installable trigger rows 10-13 https://developers.google.com/apps-script/guides/triggers/installable
if(!projectTriggerExists()
{
ScriptApp.newTrigger('myFunction')
.forSpreadsheet(ss)
.onOpen()
.create();
}//by wrapping the trigger creation like this you don't have to worry about creating unwanted triggers.
*/
var ss=SpreadsheetApp.getActive();
var sh0=ss.getSheetByName('Transfers'); //sh0 = Transfers tab
var rg0=sh0.getDataRange(); //rg0 = Range of sh0, This is functionally equivalent to creating a Range bounded by A1 and (Range.getLastColumn(), Range.getLastRow()). https://developers.google.com/apps-script/reference/spreadsheet/sheet#getdatarange
var sh1=ss.getSheetByName('Archive'); //sh1 = Archive tab
var vals=rg0.getValues(); //Returns the rectangular grid of values for this range. Returns a two-dimensional array of values, indexed by row, then by column. The values may be of type Number, Boolean, Date, or String, depending on the value of the cell. Empty cells will be represented by an empty string in the array. Remember that while a range index starts at 1, 1, the JavaScript array will be indexed from [0][0]. https://developers.google.com/apps-script/reference/spreadsheet/range#getvalues
for(var i=vals.length-1;i>11;i--) //When deleting rows it is better to start from the bottom otherwise deleted rows will mess up your loop indexing
{
if(vals[i][14]=='SEND') //If column 14 in the range has the word 'SEND' then run the next lines of code. 14 is column O. Column 0 is 15, but see note above regarding JavaScript array being indexed from [0][0]
{
sh1.appendRow(vals[i]);
sh0.deleteRow(i+1);//indexes start at zero but rows start at one
}
}
}
//This could be accomplished with SpreadsheetApp.getActive().getActiveSheet().clear();
function clearCells()
{
//https://stackoverflow.com/questions/9268570/i-need-a-button-to-clear-cells-in-a-google-spreadsheet
var ss=SpreadsheetApp.getActive();
ss.getRange('A1:A1').clearContent()
}
function projectTriggerExists(functionName)
{
if(functionName)
{
var allTriggers=ScriptApp.getProjectTriggers();
var funcExists=false;
for(var i=0;i<allTriggers.length;i++)
{
var trigger=allTriggers[i];
if(allTriggers[i].getHandlerFunction()==functionName)
{
funcExists=true;
break;
}
}
}
return funcExists;
}
The nature of the spreadsheet is that I will need to duplicate the "Transfers" tab in the original post a dozen times so that it can be used by multiple people at once (data being entered in by the user essentially over 12 "forms"). All the data put into these dozen "Transfers" tabs should rout to the same "Archive" tab. However, I'd also like to build in a rudimentary routing system, so that instead of having everything rout to one tab, different code words send the lines to different "Archive" tabs. For instance, "OK+Send+Staff1" sends the line to a tab called Staff1Archive, "OK+Send+Staff2" sends to a tab called Staff2Archive, "OK+Send+Staff3" sends to a tab called Staff3Archive, etc.
Can anyone help with this code as well?
I think this will do it for you.
function archiveRows()
{
var ss=SpreadsheetApp.getActive();
var sh0=ss.getSheetByName('Transfers');
var rg0=sh0.getDataRange();
var sh1=ss.getSheetByName('Archive');
var vals=rg0.getValues();
for(var i=vals.length-1;i>11;i--)
{
if(vals[i][14]=='SEND')
{
sh1.appendRow(vals[i]);
sh0.deleteRow(i+1)
}
}
}

Google SS: how to make a self-altering cell value for memoizing dates?

Crossposted # Google SS forums
I want do do this:
function memoizeDate(condition) {
if (condition) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var cell = sheet.getActiveCell();
cell.setValue(Date.now());
}
}
So that in (say) cell C2, if you set its value to =memoizeDate(eq(b2,"foo")), then as soon as b2 gets changed, C2 changes itself to the memoized value.
However, I get a "You do not have permission to call setValue" error, and I'm also not sure if the "active" cell (from the script's POV) is the one from which the script was called (i.e. C2) rather than who knows what else (B2? whatever random thing might be selected by the user?).
Have you tried like this (suggestion) :
function memoizeDate(condition) {
if (condition) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var cell = sheet.getActiveCell();
return new Date();
}else{
return 'nop or whatever'
}
}
EDIT following your comment : I keep reading your question and comments but I'm not sure what you really want to do.
On one hand you say "overriding the function itself" which means in fact returning a value (and that's what my code does) and on the other hand you try to get the active cell which is the one the user is editing (B2 in your example) and in that case you simply can't do that , please read the documentation about custom function :
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]];.
All in all I think this question is rather unclear, (no personal offense) you should try to reformulate it at the least.