Work on single cells of a given range Google App Scripts - google-apps-script

So, I've been trying to figure out a way to work on an individual cell of a given range, but I can't see how I can do this.
For instance, if I have set a variable to the range "A5:H25" and I want to change the values of one (or more) cells withing this range, how can one do this, keeping the other cell's values?
I know we can use an array of arrays to change multiple values, but what about the other values I want to keep untouched?
In my specific example, I have a range set this way:
var date = Utilities.formatDate(new Date(), "GMT+1", "dd/MM/yyyy");
var sprdsht = SpreadsheetApp.getActiveSpreadsheet();
var shtSessions = sprdsht.getSheets()[3];
var shtSessionslastRow = shtSessions.getLastRow();
var shtSessionslastCol = shtSessions.getLastColumn();
//Get table Ranges from the 4 sheets
var tblPushRange = shtPush.getRange(1, 1, shtPushlastRow, shtPushlastCol);
var tblSessionsRange = tblPushRange;
if (tblSessionsRange(1,1).getValue() == ""){ //<--- Check if 1st cell of the given range is empty
tblSessionsRange.setValues([[date]]) //<-- Set that 1st cell value to date
}else{
tblSessionsRange.offset(tblPushRange.getLastRow() + 1,1).setValue(date);
Logger.log(tblPushRange.getLastRow() + 1)
}
So, how can I change values in individual cells and keep all the other values untouched, within this range I set?
Note: I think the code in the else block isn't correct either, but I'll address it later!
Thanks
Psy

Solution:
You can use getCell(row,column) on a range to get a specific single cell. Note that the first cell in the range is designated as (1,1).
Sample:
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("A5:H25");
// set value of cell B7 to "A"
range.getCell(3,2).setValue("A");
Reference:
Class Range | getCell()

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

How can I conditionally copy between Google sheets?

I would like to conditional copy a value from one sheet to another.
About the below script:
It checks if in SOURCE tab the value in column H is equal to 45 and if Column A is empty. If so, it should copy the value in Column G into the last row of column A in TARGET tab. It also ads data to some of the TARGET tab columns
If I change this: sourceSheet.getRange(rangeToCopy).copyTo(destination.getRange(destination.getLastRow()+1, 1)),{contentsOnly:true};
to this: sourceSheet.getRange(rangeToCopy).copyTo(destination.getRange(destination.getLastRow()-1, 1)),{contentsOnly:true}; the cell is pasted on Column A on the penultimate row, which makes me think that the present code tries to copy to a row after the last visible one instead of pasting into the next blank cell after a non blank one in Column A, hence returning the error explained on the next bullet
As is, it returns the error: The coordinates of the range are outside the dimensions of the sheet. (I understood that the problem is because of the use of getlasrow and being used as is, it's trying to retrieve a row that does not exist)
This is the code so far:
function CopyTo(){
var sheet = SpreadsheetApp.getActive();
var sourceSheet = sheet.getSheetByName("SOURCE");
var destination = sheet.getSheetByName("TARGET");
var lastRow = sourceSheet.getDataRange().getLastRow();
for(var row=3; row<=lastRow; row++){
if(sourceSheet.getRange(row,1).getValue() == "" & sourceSheet.getRange(row,8).getValue() >= "45"){
Logger.log("CELL H"+row+" has \">=45\""+"\n=================\n"+"CELL A"+row+" is \"empty\""+"\n=================\n"+"RANGE TO COPY:\n"+ "\"G"+row+":G"+row+"\"");
var rangeToCopy = "G"+row+":G"+row;
sourceSheet.getRange(rangeToCopy).copyTo(destination.getRange(destination.getLastRow()+1, 1)),{contentsOnly:true};
destination.getRange(destination.getLastRow(), 9).setValue("Added Text Column 9");
destination.getRange(destination.getLastRow(), 13).setValue("Added Text Column 13");
destination.getRange(destination.getLastRow(), 14).setValue(new Date());
}
}
}
Also, there are other columns in Target sheet that fetch values so maybe that's the reason why the script isn't pasting on the right place but I can't get it to work.
Could you be so kind to help out?
Thank you in advance.
This is a test sheet which returns the previously mentioned error when running the script.
UPDATE
I've added to the script:
var column = destination.getRange('A' + destination.getMaxRows())
var lastFilledRow = column.getNextDataCell(SpreadsheetApp.Direction.UP).getA1Notation().slice(1)
Logger.log(lastFilledRow);
so I would get the last filled row number in Column A. It retrieves it but I'm having trouble in using it as range and I get the error: Exception: Cannot convert 'Range' to int.
sourceSheet.getRange(rangeToCopy).copyTo(destination.getRange(destination.getRange(lastFilledRow,1), 1)),{contentsOnly:true};
The problem lies with your TARGET sheet having a set maximum row of 84, thus, destination.getLastRow()+1 will return 85 which is outside the said maximum row.
To solve this, create a new TARGET sheet or find a way to remove the limit in the current TARGET sheet. Your current code should be fine.
EDIT:
I've edited your v2 code to make it work on the current test sheet provided. Please see code below.
function CopyTo_V2(){
var sheet = SpreadsheetApp.getActive();
var sourceSheet = sheet.getSheetByName("SOURCE");
var destination = sheet.getSheetByName("TARGET");
var lastRow = sourceSheet.getDataRange().getLastRow();
var column = destination.getRange('A' + destination.getMaxRows())
for(var row=3; row<=lastRow; row++){
if(sourceSheet.getRange(row,1).getValue() == "" & sourceSheet.getRange(row,8).getValue() >= "45"){
Logger.log("CELL H"+row+" has \">=45\""+"\n=================\n"+"CELL A"+row+" is \"empty\""+"\n=================\n"+"RANGE TO COPY:\n"+ "\"G"+row+":G"+row+"\"");
var rangeToCopy = "G"+row+":G"+row;
var lastFilledRow = parseInt(column.getNextDataCell(SpreadsheetApp.Direction.UP).getA1Notation().slice(1))
Logger.log(lastFilledRow);
sourceSheet.getRange(rangeToCopy).copyTo(destination.getRange(lastFilledRow+1,1)),{contentsOnly:true};
destination.getRange(lastFilledRow+1, 9).setValue("Added Text Column 9");
destination.getRange(lastFilledRow+1, 13).setValue("Added Text Column 13");
destination.getRange(lastFilledRow+1, 14).setValue(new Date());
}
}
}

Google Sheets - Create Data Validation

I need to create several hundred data validation dropdowns that will refer to different ranges.
Dropdowns needs to be created in each cell of sheet1, in column 'O'.
However each dropdown has to refer to a different row in another sheet ('sheet2').
For example:
data validation dropdown in 'sheet1', cell 'O2' will refer to an entire row 1 in 'sheet2'
dropdown in 'sheet1', cell 'O3' will refer to a row 2 in 'sheet2', and so on.
Data validation setup:
List from range, show dropdown list in a cell, reject input.
I'd appreciate if you guys could help out with a script that will create predefined number of such dropdowns in a column.
regards,
You can use this sample code:
function createDataValidation() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s1 = ss.getSheetByName("Sheet1");
var s2 = ss.getSheetByName("Sheet2");
var s2_lastRow = s2.getLastRow();
//create data validation per row
for (var row = 1; row <= s2_lastRow; row++){
//create an a1Notation to select a complete row sample: "A1:1", "A2:2", and so on.
var a1Notation = "A"+row+":"+row;
//create data validation rule
var listRange = s2.getRange(a1Notation);
var rule = SpreadsheetApp.newDataValidation()
.requireValueInRange(listRange, true)
.setAllowInvalid(false)
.build();
//assign rule to the current cell in sheet1 column "O"
//add 1 row offset since data validation will start at cell "O2"
a1Notation = "O"+(row+1);
Logger.log(a1Notation);
var cell = s1.getRange(a1Notation);
cell.setDataValidation(rule);
}
}
What it does?
Get individual sheets using Spreadsheet.getSheetByName(name)
Get Sheet2's last row number using Sheet.getLastRow()
Loop each individual row in Sheet2
Create an a1Notation string for the current row using this format (row1: "A1:1")
Select a range to be used for the data validation using Sheet.getRange(a1Notation)
Create a new data validation rule using SpreadsheetApp.newDataValidation(). This will return a DataValidationBuilder where you can specify what type of data validation to use. For Values in Range, use DataValidationBuilder.requireValueInRange(range, showDropdown)
Regarding rejecting inputs, by default new data validation rules set it to true. See DataValidationBuilder.setAllowInvalid(allowInvalidData)
Set newly created data validation rule for a specific cell using Range.setDataValidation(rule)
OUTPUT:
Sample Sheet2:
Sample Output Sheet1:

Google Sheets setting value of multiple cells in a column

I am trying to change the values of a range of code in my spreadsheet, to reset a template back to default values when done. I found this code as an example on another thread, but can't ask there because you can't ask questions or something.
function storeValue() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
// ss is now the spreadsheet the script is associated with
var sheet = ss.getSheets()[0];
var values = [
["2.000", "1,000,000", "$2.99"]
];
var range = sheet.getRange("B2:D2");
range.setValues(values);
}
This works great, changes those three cells to the value needed. This isn't what I ultimately need, but it works. I want to change the values in A8-A10 though, and when I change that cell range(And nothing else),I get the following error:
Incorrect range height, was 1 but should be 3
What am I doing wrong here? I'm sure it's simple.
The values array you have will write to columns in a row. To write to row in a column change to:
var values = [["2.000"], ["1,000,000"], ["$2.99"]];

Add up previous and current cell values when changed

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.