I have data in 6 columns and then a column "Last updated" where I manually write the date of the last change in the row. I am sure there is a way to automatize this, but cannot make it work.
note - I do not now how to code, just tried to modify multiple codes I found on the web and nothing worked for me, I am getting desperate.
It is basically a database of chemical substances, where each column is a property, and I need to stamp the last time any of the properties were changed. Can anyone please help me with this?
You will have to enter some code in order to make this work. This link gives a good example on how to create a script for your sheet.
The most important part of your script is that you want it to run EVERY time you make a change to the sheet in ANY cell. Then when it runs, you can check which cell was changed and them determine if that cell is in the area you're interested in checking. Consider a sheet set up like this one:
When you have your sheet set up, then go to the Tools menu and select Script Editor. From there you'll enter the code below. What I'm giving you is an example that you must change to meet your needs. The first part to understand is the function onEdit. This is a specially named function that will run EVERY time a change is made to the sheet. So when you have
function onEdit(eventObj) {
}
Then anything you put inside the braces will execute each time a change is made (after the user types Enter). So your onEdit function looks like this:
function onEdit(eventObj) {
//--- you could set up a dynamic named range for this area to make it easier
var thisSheet = SpreadsheetApp.getActiveSheet();
var checkRange = thisSheet.getRange("B2:D5");
if (isInRange(checkRange, eventObj.range)) {
Logger.log('cell is in range');
var propertyCell = eventObj.range;
var timestampCell = thisSheet.getRange(propertyCell.getRow(), 5);
timestampCell.setValue(Utilities.formatDate(new Date(), "UTC+8", "MM-dd-yyyy hh:mm:ss"));
} else {
Logger.log('must be outside the range');
}
The eventObj is the cell that the user edited and changed. So we are using that cell to compare with our area to check. If we determine that the cell is within that area, then we go ahead and define the cell to hold the timestamp and then assign a value to it. The value is the formatted date and time.
The important piece here is the checkRange variable that is defined as the area of all your properties. So when the onEdit function runs, it defines the area you're checking, then calls isInRange to see if the cell that was edited is within that area. Here is the function:
function isInRange(checkRange, targetCell) {
Logger.log('checking isInRange');
//--- check the target cell's row and column against the given
// checkrange area and return True if the target cell is
// inside that range
var targetRow = targetCell.getRow();
if (targetRow < checkRange.getRow() || targetRow > checkRange.getLastRow()) return false;
Logger.log('not outside the rows');
var targetColumn = targetCell.getColumn();
if (targetColumn < checkRange.getColumn() || targetColumn > checkRange.getLastColumn()) return false;
Logger.log('not outside the columns');
//--- the target cell is in the range!
return true;
}
Your result will look like this:
So now, all together, just copy and paste this code into your script editor:
function isInRange(checkRange, targetCell) {
Logger.log('checking isInRange');
//--- check the target cell's row and column against the given
// checkrange area and return True if the target cell is
// inside that range
var targetRow = targetCell.getRow();
if (targetRow < checkRange.getRow() || targetRow > checkRange.getLastRow()) return false;
Logger.log('not outside the rows');
var targetColumn = targetCell.getColumn();
if (targetColumn < checkRange.getColumn() || targetColumn > checkRange.getLastColumn()) return false;
Logger.log('not outside the columns');
//--- the target cell is in the range!
return true;
}
function onEdit(eventObj) {
//--- you could set up a dynamic named range for this area to make it easier
var thisSheet = SpreadsheetApp.getActiveSheet();
var checkRange = thisSheet.getRange("B2:D5");
if (isInRange(checkRange, eventObj.range)) {
Logger.log('cell is in range');
var propertyCell = eventObj.range;
var timestampCell = thisSheet.getRange(propertyCell.getRow(), 5);
timestampCell.setValue(Utilities.formatDate(new Date(), "UTC+8", "MM-dd-yyyy hh:mm:ss"));
} else {
Logger.log('must be outside the range');
}
}
It's not necessary to use scripts. It may be easier to use the formula:
=(your_datetime - date(1970;1;1) ) * 86400000
timestamp-to-date-and-back-screenshot
Check the example here: https://docs.google.com/spreadsheets/d/1JHr4z3-32fWYUAjm3bG_Wyr-ZXn7FUvrECa3B8TtJtU/edit?usp=sharing
Related
I have a spreadsheet I designed in Google Sheets to input data at work and then a formula that determines if the part needs to be replaced and provides the part number required. I need either a macro or appScript that will start with a certain cell on the same sheet, highlight it, allow me to type a value in it, then either by pressing the ENTER or TAB key to move to the next cell on the page (Not necessarily the next door cell, but a cell in another column and/or row), -AND- based on a data validation check box determine which cells are selected. How do I write either a macro or appScript to do what I need? Which would be easier?
Reference
sheet.setActiveSelection()
Script:
Try
function onEdit(event){
var sh = event.source.getActiveSheet();
var rng = event.source.getActiveRange();
if (sh.getName() == 'mySheetName'){ // adapt
var addresses = ["E7","H7","E10","H10","E13","H13","E16"]; // adapt
var values = addresses.join().split(",");
var item = values.indexOf(rng.getA1Notation());
if (item < addresses.length - 1){
sh.setActiveSelection(addresses[item + 1]);
}
}
}
Note:
This way you can determine the order in which the cells are selected.
If you have a script that copies cells into a master data sheet, you can take advantage of the range list. (by the way, you can find here how to transfer the data).
In case of protected sheet:
If your sheet is protected, except for the cells that need to be filled in, you can use a script that will search for them and reorganize them.
function onEdit(event) {
var sh = event.source.getActiveSheet();
var rng = event.source.getActiveRange();
if (sh.getName() == 'mySheetName') { // adapt
var addresses = listOfUnprotectedRanges()
var values = addresses.join().split(",");
var item = values.indexOf(rng.getA1Notation());
if (item < addresses.length - 1) {
sh.setActiveSelection(addresses[item + 1]);
}
}
}
function listOfUnprotectedRanges() {
var p = SpreadsheetApp.getActiveSheet().getProtections(SpreadsheetApp.ProtectionType.SHEET)[0];
var ranges = p.getUnprotectedRanges().map(r => [r.getA1Notation(), r.getRow(), r.getColumn()])
ranges = ranges.sort(function (a, b) { return a[2] - b[2]; }); // sort by columns
ranges = ranges.sort(function (a, b) { return a[1] - b[1]; }); // sort by ranges first
return ranges.map(r => r[0])
}
I have created the following simple function:
function test(r,c) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sheet.getRange(r,c).setBackground("red");
return 1;
}
In the spreadsheet, I write "=test(row(),column())
This results in ERROR with the following message:
Error: You do not have permission to call setBackground (line 3).
It is NO problem if I create another function call in the script as follows:
function test_the_test(){
test(5,4);
}
Why can't I call the test function from the spreadsheet cell?
Thank you in advance
As it is clearly explained in the documentation, 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. That is of course also true for other methods such as setBackground etc.
It's not possible to call anything which sets content from cell, but it is possible to call it from buttons.
Actually is quite easy. Definitely it's not true that you can't change others cell content.
The trick is to not call the function from cell but mount it into drawing/image.
Click insert -> drawing
Draw a image and save it (you should see
your image in spreadsheet)
Click it by right mouse button - in top right corner, there is a little
triangle opening options
Click Assign script and type name of your
script without parenthesis (like "test" not "test()" ) and confirm
Click the button. A pop-up window asking for privileges to access spreadsheet appears.
Confirm it, if problem with refresh occurs just refresh it manually (F5)
Now you can click the button and you can edit any cell you like
This code work fine when mounted to button.
function test() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sheet.getRange(3,5).setBackground("red");
}
The current version of Google Sheets (Jan 2022) uses the Apps Script editor and allows you to put scripts in a container file that automatically attaches to your Google Sheet.
You can open the Apps Script editor from the Google Sheets menu under Extensions -> App Script
In the default Code.gs editor file you can simply extend a default event hook such as onEdit() and put in logic to filter your actions to a specific set of circumstances. Here is a simple example of using this hook:
function onEdit(e) {
if( ! e ){
return;
}
var currentSheet = e.source.getActiveSheet();
var currentRange = e.range;
// only want action to occur when a single cell changes
if( currentRange.getNumRows() === 1 && currentRange.getNumColumns() === 1 ){
var currentColumn = currentRange.getLastColumn();
var currentRow = currentRange.getLastRow();
// only want action to occur for a column on a certain sheet
var myTargetSheet = "Sheet 1";
var myTargetColumn = 1;
if( currentSheet.getName() == myTargetSheet && currentColumn == myTargetColumn ){
// set background color for the selected row based on a lookup
var cellValue = currentRange.getCell(1,1).getValue();
var assignedColor = myCustomSearch( cellValue );
currentSheet.getRange( "A" + currentRow + ":E" + currentRow ).setBackgroundColor( assignedColor );
}
}
}
function myCustomSearch( searchTerm ){
var assignedColor = "#ffffff";
var lookupSheet = SpreadsheetApp.getActive().getSheetByName("Sheet 2");
var lookupRange = lookupSheet.getRange("H1:H20"); // where the search terms live
var numColumns = lookupRange.getNumColumns();
var numRows = lookupRange.getNumRows();
var lookupList = lookupRange.getValues();
for( var myColumn=0; myColumn < numColumns; myColumn++ ){
for( var myRow=0; myRow < numRows; myRow++ ){
if( lookupList[myRow][myColumn] == searchTerm ){
assignedColor = lookupSheet.getRange("H"+(myRow+1)).getBackgroundColor();
break;
}
}
}
return assignedColor;
}
I'm pretty new to scripting in Sheets, and I can't get this to work. I have two columns. In column A I want the date to appear and "freeze" in the cell when I fill out the cell next to it in column B, and only then.
I have tried several things, and got inspiration from here: Freeze a date, once entered?, but I can't get it to work. I basically want:
In cell in A1: =IF(ISBLANK(B1), "", TODAY())
But today is not supposed to be updated unless I change B1.
I've tried:
function FreezeDate(String) {
if (String == "") {
return ""
} else {
return new Date()
}
}
I've also tried using OnEdit(String) with the same body as FreezeDate but this also changes the date when I open the sheet.
Is this what your looking for?
The date is only added to column one when column two of the same line is edited and column one is blank. So any further edits of column two do not change the date entry in that row.
function insertDateCol1WhenCol2IsEdited(e){//Use with an installable onEdit trigger
var sheetname='Your Sheet Name'
var rg=e.range;
var sh=rg.getSheet();
if(sh.getName()!=sheetname){return;}
if(sh.getName()==sheetname && rg.columnStart==2 && sh.getRange(rg.rowStart,1).isBlank()){
sh.getRange(rg.rowStart,1).setValue(new Date());
}
}
Thank you for response. I thought onEdit(e) had to be given an argument, and that it fired when this was changed. I know see that this is not the case. I think I've figured it out:
function onEdit(e) {
var ss = SpreadsheetApp.getActive();
var activeRow = ss.getActiveCell().getRow();
var activeCol = ss.getActiveCell().getColumn();
var activeCellValue = ss.getActiveCell().getValue();
// target cell is in same row as active cell and in column 4 (D)
var targetCell = SpreadsheetApp.getActiveSheet().getRange(activeRow, 4)
// only trigger when change is in column A, when a cell is '-' should not
trigger
if (activeCol == 1 && activeCellValue != '-') {
targetCell.setValue(new Date())
} else {
targetCell.setValue("")
}
}
Working with a Google Sheets where every line includes an ID and a picklist value. Example: Google Sheet.
What I am trying to do is run a custom function when someone edits the picklist cell. The function takes two arguments, the value from the ID and the picklist cell of the same line, then performs a HTTP POST request to update the record in our CRM.
//When STAGE cell on Google Sheet is updated, run this function:
function updateProjectStage(status, id) {
var baseURL = 'https://crm.zoho.com/crm/private/json/Potentials/updateRecords?authtoken=xxx&scope=crmapi&id=', // see docs https://www.zoho.com/crm/help/api/updaterecords.html
recordID = id, // building id from A column
stage = '<Potentials><row no="1"><FL val="Stage">' + status + '</FL></row></Potentials>'; // status from B column
var postURL = baseURL + recordID + '&xmlData=' + stage;
Logger.log(postURL);
var response = UrlFetchApp.fetch(postURL); // update record in crm
var sanitizedResponse = JSON.parse(response.getContentText()); // get confirmation/failure
Logger.log(sanitizedResponse);
}
I don't know how to run the function for this picklist type of cell - I cannot just input =updateProjectStage(status, id) into the cell like I am used to doing because it errors out.
Example: Error Message.
Is this even possible?
Your answer lies in capturing the edit event when the user modifies any cell on the sheet. The user can modify any cell, of course. Your job is to determine if that cell is in the range you care about. The onEdit event can be captured using this function:
function onEdit(eventObj) {
//--- check if the edited cell is in range, then call your function
// with the appropriate parameters
}
The object passed into the event describes the cell that was edited. So we set up a "check range" and then make a comparison of that range to whichever cell was edited. Here's the function:
function isInRange(checkRange, targetCell) {
//--- check the target cell's row and column against the given
// checkrange area and return True if the target cell is
// inside that range
var targetRow = targetCell.getRow();
if (targetRow < checkRange.getRow() || targetRow > checkRange.getLastRow()) return false;
var targetColumn = targetCell.getColumn();
if (targetColumn < checkRange.getColumn() || targetColumn > checkRange.getLastColumn()) return false;
//--- the target cell is in the range!
return true;
}
The full event function for the edit event would be
function onEdit(eventObj) {
//--- you could set up a dynamic named range for this area to make it easier
var checkRange = SpreadsheetApp.getActiveSheet().getRange("B2:B10");
if (isInRange(checkRange, eventObj.range)) {
//--- the ID cell is on the same row, one cell to the left
var idCell = eventObj.range.offset(0,-1);
//--- the status cell is the one that was edited
var statusCell = eventObj.range;
updateProjectStage(statusCell, idCell);
}
}
Here's the whole thing all together:
function isInRange(checkRange, targetCell) {
Logger.log('checking isInRange');
//--- check the target cell's row and column against the given
// checkrange area and return True if the target cell is
// inside that range
var targetRow = targetCell.getRow();
if (targetRow < checkRange.getRow() || targetRow > checkRange.getLastRow()) return false;
Logger.log('not outside the rows');
var targetColumn = targetCell.getColumn();
if (targetColumn < checkRange.getColumn() || targetColumn > checkRange.getLastColumn()) return false;
Logger.log('not outside the columns');
//--- the target cell is in the range!
return true;
}
function onEdit(eventObj) {
//--- you could set up a dynamic named range for this area to make it easier
var checkRange = SpreadsheetApp.getActiveSheet().getRange("B2:B10");
if (isInRange(checkRange, eventObj.range)) {
Logger.log('cell is in range');
//--- the ID cell is on the same row, one cell to the left
var idCell = eventObj.range.offset(0,-1);
//--- the status cell is the one that was edited
var statusCell = eventObj.range;
updateProjectStage(statusCell, idCell);
} else {
Logger.log('must be outside the range');
}
}
function updateProjectStage(status, id) {
Logger.log('we are updating');
}
I have created the following simple function:
function test(r,c) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sheet.getRange(r,c).setBackground("red");
return 1;
}
In the spreadsheet, I write "=test(row(),column())
This results in ERROR with the following message:
Error: You do not have permission to call setBackground (line 3).
It is NO problem if I create another function call in the script as follows:
function test_the_test(){
test(5,4);
}
Why can't I call the test function from the spreadsheet cell?
Thank you in advance
As it is clearly explained in the documentation, 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. That is of course also true for other methods such as setBackground etc.
It's not possible to call anything which sets content from cell, but it is possible to call it from buttons.
Actually is quite easy. Definitely it's not true that you can't change others cell content.
The trick is to not call the function from cell but mount it into drawing/image.
Click insert -> drawing
Draw a image and save it (you should see
your image in spreadsheet)
Click it by right mouse button - in top right corner, there is a little
triangle opening options
Click Assign script and type name of your
script without parenthesis (like "test" not "test()" ) and confirm
Click the button. A pop-up window asking for privileges to access spreadsheet appears.
Confirm it, if problem with refresh occurs just refresh it manually (F5)
Now you can click the button and you can edit any cell you like
This code work fine when mounted to button.
function test() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sheet.getRange(3,5).setBackground("red");
}
The current version of Google Sheets (Jan 2022) uses the Apps Script editor and allows you to put scripts in a container file that automatically attaches to your Google Sheet.
You can open the Apps Script editor from the Google Sheets menu under Extensions -> App Script
In the default Code.gs editor file you can simply extend a default event hook such as onEdit() and put in logic to filter your actions to a specific set of circumstances. Here is a simple example of using this hook:
function onEdit(e) {
if( ! e ){
return;
}
var currentSheet = e.source.getActiveSheet();
var currentRange = e.range;
// only want action to occur when a single cell changes
if( currentRange.getNumRows() === 1 && currentRange.getNumColumns() === 1 ){
var currentColumn = currentRange.getLastColumn();
var currentRow = currentRange.getLastRow();
// only want action to occur for a column on a certain sheet
var myTargetSheet = "Sheet 1";
var myTargetColumn = 1;
if( currentSheet.getName() == myTargetSheet && currentColumn == myTargetColumn ){
// set background color for the selected row based on a lookup
var cellValue = currentRange.getCell(1,1).getValue();
var assignedColor = myCustomSearch( cellValue );
currentSheet.getRange( "A" + currentRow + ":E" + currentRow ).setBackgroundColor( assignedColor );
}
}
}
function myCustomSearch( searchTerm ){
var assignedColor = "#ffffff";
var lookupSheet = SpreadsheetApp.getActive().getSheetByName("Sheet 2");
var lookupRange = lookupSheet.getRange("H1:H20"); // where the search terms live
var numColumns = lookupRange.getNumColumns();
var numRows = lookupRange.getNumRows();
var lookupList = lookupRange.getValues();
for( var myColumn=0; myColumn < numColumns; myColumn++ ){
for( var myRow=0; myRow < numRows; myRow++ ){
if( lookupList[myRow][myColumn] == searchTerm ){
assignedColor = lookupSheet.getRange("H"+(myRow+1)).getBackgroundColor();
break;
}
}
}
return assignedColor;
}