Hello I am currently working on a time tracking system. With the following code I track the time how long a value was in a cell. This time is recorded in another worksheet and this is done continuously by appendRow ().
function onEdit(e) {
addTimestamp(e);
}
function addTimestamp(e) {
var ui = SpreadsheetApp.getUi();
var ws = "Tabellenblatt2";
var ss = e.source;
var targetSheet = ss.getSheetByName("Tabellenblatt1");
var range = targetSheet.getRange(3, 2, 1000, 1);
var currentDate = new Date();
if (e.source.getActiveSheet().getName() === ws && range != "") {
var val = e.range.getValue();
if (val != "") {
let rowToAdd = [val, "", currentDate, ""]
ss.getSheetByName("Tabellenblatt1").appendRow(rowToAdd);
ui.alert("Test2");
} else {
var sheet = ss.getSheetByName("Tabellenblatt1");
var dataFinder = sheet.createTextFinder(e.oldValue);
var nameRow = dataFinder.findAll()[0].getRow();
sheet.getRange(nameRow, 4).setValue(currentDate);
ui.alert("Test3");
}
}
}
Currently I cannot delete the content of the cell with the delete key, otherwise e.oldValue is undfined and I cannot get the date in the first worksheet. Now I always have to go to the formula area, mark the text in the cell and delete it. Is there a way to use the whole thing with the delete key anyway?
Unfortunately you cannot have the property oldValues if you use del or backspace button. According to Tanaike's comment here, there are certain scenarios where values and oldValues would appear in the event object.
In the case for editing a value to empty cell
e.value is included in e.
e.oldValue is NOT included in e.
In the case for overwriting a cell with a value by other value
Both e.value and e.oldValue are included in e.
In the case for deleting a value from a cell with a value
Both e.value and e.oldValue are NOT included in e.
Values of event object in multiple scenarios:
Adding value to an empty cell:
Overwriting a cell:
Deleting a value from a cell with a value:
A workaround would be creating a macro or button that will save and delete the value of the cell.
Example:
Code:
// this function will delete and display value of the active cell
function deleteCell(){
var sh = SpreadsheetApp.getActiveRange();
var oldValue = sh.getValue();
sh.clear();
Browser.msgBox(oldValue);
}
Button:
References:
Creating macros in Apps Script
Add A Google Sheets Button To Run Scripts
Related
I have a script that moves rows to a sheet of the same name based on a cell.
I am trying to get this to work onFormSubmit but am having no luck.
OnEdit the script works perfectly, but on form submit nothing happens after i submit a form.
function onFormSubmit(e) {
let range = e.range;
let col = range.getColumn();
let row = range.getRow();
let val = range.getValue();
let source = e.source.getActiveSheet();
if (col == 2 && val != '') {
let ss = SpreadsheetApp.getActiveSpreadsheet();
let sheet = ss.getSheetByName(source.getName());
let targetSheet = ss.getSheetByName(val);
let data = sheet.getRange(row, 1, 1, sheet.getLastColumn()).getValues();
targetSheet.appendRow(data[0]);
sheet.deleteRow(row);
}
}
first of all, the object "e" you get from an onEdit event is different than the one you get from an onFormSubmit event, their fields are different and the way you access their values as well.
The main difference that applies to your code is the way on how you access to the input values of the object e, for onEdit triggers you can access the input values with the e.value function (when editing 1 cell) and the e.values (when editing ranges of cell), whereas for onFormSubmit you can only access the input values using the e.values (array of values) .
You can see all differences here: App Script Documentation
Please try this code:
function onMyFormSubmit(e) {
const values = e.values;
if (values[1] != '') {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const targetSheet = ss.getSheetByName(values[1]);
targetSheet.getRange(targetSheet.getLastRow()+1 ,1,1,values.length).setValues([values]);
const sheet = e.range.getSheet();
sheet.deleteRow(e.range.rowStart);
}
}
PS. I tested my code setting trigger by clicking the "clock" icon on the left pane.
trigger setup
We have a spreadsheet that automatically attributes status for each row based on information enteredin cells located in column T. The stauts appear on column M showing open, pending and closed. I'm using the following code:
// Sheet the data is on.
var SHEET = "DLT";
// The value that will cause the row to hide.
var VALUE = "Closed"
// The column we will be using
var COLUMN_NUMBER = 14
function onEdit (e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var activeSheet = ss.getActiveSheet();
//Ensure on correct sheet.
if(SHEET == activeSheet.getName()){
var cell = ss.getActiveCell()
var cellValue = cell.getValue();
//Ensure we are looking at the correct column.
if(cell.getColumn() == COLUMN_NUMBER){
//If the cell matched the value we require,hide the row.
if(cellValue == VALUE){
activeSheet.hideRow(cell);
};
};
};
}
I understand that there is a mistake on the code, as the spreadsheet changes automatically the "stauts" value on column M as soon as information is entered on column T, and this code this won't work because technically the Active.cell bit is telling the script to look at the current cell being edited (which in this case will be the cell on column T as this is the last field to be filled before triggering the spreadsheet change status to closed.)
Doubt is, how to make the script still looks into the current row being edited but consider the value on column M and not the active cell.
function onEdit (e) {
var sh = e.range.getSheet();
//column 20 is T
if(sh.getName()== 'DLT' && e.range.columnStart == 20 ){
SpreadsheetApp.flush();//This will provide enough time to allow the spreadsheet to complete all of it's calculations.
//column 13 is M
if(e.range.offset(0,-7).getValue()=="Closed") {
sh.hideRows(e.range.rowStart);
}
};
}
I have a sheet where when I change a specific cell to "YES", I need a template sheet to be copied to a new version and named as per the value of a cell on the current row.
I'm having trouble working out how to get the value of the first cell in the row selected. This is what I have so far (I know this is wrong):
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var currentCell = sheet.getCurrentCell();
if (currentCell = "YES")
{
SpreadsheetApp.getActiveSpreadsheet().toast("New change control sheet added to workbook.","Change Control",15);
var sourceRow = ss.getActiveRange().getRowIndex();
var tabName = ss.getRange(cell,1).getValues();
ss.getSheetByName("CCTemplate").showSheet()
.activate();
ss.setActiveSheet(ss.getSheetByName('CCTemplate'), true);
ss.duplicateActiveSheet();
ss.setActiveSheet(ss.getSheetByName('CCTemplate'), true);
ss.getActiveSheet().hideSheet();
ss.setActiveSheet(ss.getSheetByName('Copy of CCTemplate'), true);
ss.getActiveSheet().setName("CC" & tabName);
}
}
Any ideas?
function onEdit(e) {
var sh=e.range.getSheet();
if(sh.getName()=='Your Sheet Name' && e.value=="YES") {
e.source.toast="New change control sheet added to workbook.","Change Control",15);
var tabName=sh.getRange(e.range.rowStart,1).getValue();
var tsh=e.source.getSheetByName('CCTemplate');
var csh=tsh.copyTo(e.source);
csh.setName('CC'+tabName);
}
}
You should avoid using activate in your scripts especially in simple triggers where you have to finish in 30 seconds. I think this code does the same thing that you intended for your code. One significant difference is that I use the information that comes in the event object that comes with the trigger. You should add the code Logger.log(JSON.stringify(e)) and then look at the logs you will see that there is a lot of information available to you which removes the need to run extra functions to get things like a spreadsheet.
Use event objects
onEdit offers among others the event objects range and value which are helpful to retrieve the range that has been edited and its value.
Also
When you want to a cell and compare it against a value, like in if (currentCell = "YES") - you need to retrive its value (either currentCell.getValue() or just event.value) and you need to use == instead of = for comparison.
Be careful with getValues() vs getValue(). The former gives you a 2D array and is not necessary if you want to retrieve the value of a single cell.
There is no need to set your sheet to active in order to change its name.
You can rewrite your code as following:
function onEdit(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var currentCell = event.range;
var value = event.value;
if (value == "YES")
{
...
var sourceRow = range.getRowIndex();
var tabName = ss.getRange(sourceRow, 1).getValue();
...
ss.getSheetByName('Copy of CCTemplate').setName("CC" + tabName);
}
}
I have the following code to try and set a lookup formula when a user deletes a cell by mistake:
function onEdit(e)
{
var ssA = SpreadsheetApp.getActive();
var ss = ssA.getSheetByName("Completed Work Requests")
var lastRow = ss.getLastRow();
var range = ss.getRange(5,6,lastRow,1);
var data = range.getValues();
for(var i=0;i<data.length;i++)
{
if(data[i][0] == "")//If true then it's blank
{
data[i][0]="=IFERROR(INDEX('Client lookup sheet'!C[-1]:C[-1],MATCH(R[0]C[-4],'Client lookup sheet'!C[-5]:C[-5],false)),\"\")"
range.setValue(data[i][0]);
}
}}
The problem I am having is the range.setValue(data[i][0]); part where I can see that it is setting the entire range to the value of data[i][0] in the previous line. The issue is that even if a user inputs a value manually, the onEdit function simply resets the value to data[i][0] = .... while I want them to be able to set the manual value without the function overwriting the value.
So my question boils down to what do I need to use instead of range.setValue(data[i][0]) to ensure only the cell evaluated by data[i][0] == "" is set to become a formula rather than the entire range?
Thanks!
I have fixed my issues by editing my code to:
edit: The previous issue was that I couldn't figure out a way to set value when an instance of data[i][0] == "" was found. The original code in the question above had the setValue applied to the entire range so when a user manually inputted a value, the setValue would simply reset the value to the formula. I have found that I can find the range of data[i][0] using getRange(i+5,6) as the row starts at 5 and column number = 6. This new range can then be used to setValue at the appropriate cell rather than the whole range
function onEdit(e)
{
var ssA = SpreadsheetApp.getActive();//changed from openById() for my convenience
var ss = ssA.getSheetByName("Completed Work Requests")
var lastRow = ss.getLastRow();
var range = ss.getRange(5,6,lastRow,1);
var data = range.getValues();
for(var i=0;i<data.length;i++)
{
if(data[i][0] == "")//If true then it's blank
{
data[i][0]="=IFERROR(INDEX('Client lookup sheet'!C[-1]:C[-1],MATCH(R[0]C[-4],'Client lookup sheet'!C[-5]:C[-5],false)),\"\")"
var rangedata = ss.getRange(i+5,6)
rangedata.setValue(data[i][0]);
}
}}
Lots of people have asked how to get e.oldValue and e.value. However, how do I get the old value of a cell if the contents were overwritten via a "paste" of contents from another cell on the sheet? Here is what I know:
Let's take cell A1 with contents of "foo" and cell A2 with contents of "bar".
If A1 is overwritten by someone entering "bar" into it (including pasting from an external source): e.value = "bar" and e.oldValue = "foo".
If A1 is cleared, e.value = {"oldValue":"foo"}, e.oldValue = "foo".
If A1 is overwritten by someone copying A2 and pasting it into A1: e.value = {} and e.oldValue = {}. So we have to use e.range.getValue() to get our "new value" of "bar". However, how do I get the "oldValue"??
Yes, I know I can get it from cell A2, but how do I know what was overwritten in cell A1? That's what I want to know.
Right now my code for getting the two values I want to know is:
var newValue = (typeof e.value == "object" ? e.range.getValue() : e.value);
var oldVlaue = e.oldValue;
Please think of this as one of several workarounds. I thought that it might become a workaround by saving the data before editing. This sample script retrieves oldValue by using spreadsheet saved the data before editing. In order to use this sample script, please carry out as follows.
Please modify "backupfile" of var backupfilename = "backupfile"; to an unique name.
Run a function of init(). By this, the backup file is created and the sheet that you want to retrieve oldValue is copied to it.
Install onEdit(e) as an installable trigger. The install method is below.
On script editor
Edit -> Current project's triggers
Click here to add one now.
Set "Run" to onEdit
Set "Events" to From spreadsheet, On edit
After these, when you edit the spreadsheet, onEdit(e) carries out retrieving data from both current spreadsheet and backup spreadsheet and then, the data of current spreadsheet is copied to the backup spreadsheet. So you can retrieve oldValue and newValue for the edited range. Also users that the spreadsheet is shared can retrieve oldValue and newValue through this sample script.
Sample script :
var backupfilename = "backupfile"; // In this sample, this is a global variable.
function copyToo(srcrange, dstrange) {
var dstSS = dstrange.getSheet().getParent();
var copiedsheet = srcrange.getSheet().copyTo(dstSS);
copiedsheet.getRange(srcrange.getA1Notation()).copyTo(dstrange);
dstSS.deleteSheet(copiedsheet);
}
// At first, please run this function.
function init() {
// Source
var srcss = SpreadsheetApp.getActiveSheet();
var range = srcss.getDataRange().getA1Notation();
var srcrange = srcss.getRange(range);
// Destination
var backupfile = DriveApp.getFilesByName(backupfilename);
var dstid = backupfile.hasNext()
? backupfile.next().getId()
: SpreadsheetApp.create(backupfilename).getId();
var dstrange = SpreadsheetApp.openById(dstid).getSheets()[0].getRange(range);
copyToo(srcrange, dstrange);
PropertiesService.getScriptProperties().setProperty('backupfileid', dstid);
}
function onEdit(e) {
var newValue = e.range.getValues();
var dstid = PropertiesService.getScriptProperties().getProperty('backupfileid');
var oldValue = SpreadsheetApp
.openById(dstid)
.getSheets()[0]
.getRange(e.range.getA1Notation())
.getValues();
Logger.log("newValue %s", newValue)
Logger.log("oldValue %s", oldValue)
// Update backup file
var range = e.source.getDataRange().getA1Notation();
var srcrange = e.source.getRange(range);
var dstrange = SpreadsheetApp.openById(dstid).getSheets()[0].getRange(range);
copyToo(srcrange, dstrange);
}
If I misunderstand your question, I'm sorry.