I'm using two scripts to achieve my goal. The first script inserts a timestamp in the adjacent column when an update is made to a field. This works great. The next script monitors the timestamp column and when the timestamp changes, copy entire row to a "recent updates" sheet. I'm then going to use the Awesome Table plugin to create a news feed for all the recent updates.
When the timestamp column is blank and an edit is made the timestamp is appropriately entered into the timestamp column. The second script picks it up and crops it into my "recent updates" sheet...
...but if a previous update was made and the timestamp field is already present the script runs without error, but does not copy the new row to "recent updates". How can I get the row to paste every time the timestamp field changes?
/**
* #file Copy row to new cell when date value changes
* {#link https://support.google.com/docs/thread/13191603}
*/
/**
* Runs the snippet.
* Please, register this function for EDIT event
* once from the owner of the Spreadsheet
*
* #param {GoogleAppsScript.Events.SheetsOnEdit} e
*/
function CopyUpdates(e) {
if (!e) return;
var currentSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var currentRange = currentSheet.getActiveRange();
var currentRow = currentRange.getRow();
if (
e.value &&
currentSheet.getName() == "Open Actions - Cutover Punchlist" , "Open Actions - FSA Interfaces" , "Open Actions - General" &&
currentRow > 2 &&
currentRange.getColumn() == 9
) {
var dataRange = currentSheet.getRange(currentRow + ':' + currentRow);
var destinationSheet = currentSheet.getParent().getSheetByName("RecentUpdates");
var destinationRow = destinationSheet.getLastRow() + 1;
dataRange.copyTo(destinationSheet.getRange(destinationRow, 1), {
contentsOnly: true
});
}
}
The script already is working properly upon testing. Maybe there is something that interferes with your trigger which can be caused by the first function. Thus you will need to merge them.
I renamed it to onEdit(e) instead. I merged them since they are actually a subset of onEdit(e), just having different conditions. It should be fine to merge them under the same function.
Code:
var currentSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
function onEdit(e) {
if (!e) return;
var currentRange = currentSheet.getActiveRange();
var currentRow = currentRange.getRow();
if ( e.value &&
currentSheet.getName() == "Open Actions - Cutover Punchlist" , "Open Actions - FSA Interfaces" , "Open Actions - General" &&
currentRow > 2 ) {
if (currentRange.getColumn() == 8) { // adjacent column (first function conversion, if H column is edited)
var adjacentCell = currentSheet.getRange('H' + currentRow);
var timestampCell = adjacentCell.offset(0, 1);
timestampCell.setValue(new Date());
// since H is edited, timestamp column is updated
// so we copy (regardless if the old value is blank or a timestamp)
copyUpdates(currentRow);
}
if (currentRange.getColumn() == 9) { // timestamp column (second function conversion, if I column is edited)
// edited timestamp manually, copy
copyUpdates(currentRow);
}
}
}
function copyUpdates(currentRow) {
var dataRange = currentSheet.getRange(currentRow + ':' + currentRow);
var destinationSheet = currentSheet.getParent().getSheetByName("RecentUpdates");
var destinationRow = destinationSheet.getLastRow() + 1;
dataRange.copyTo(destinationSheet.getRange(destinationRow, 1), {
contentsOnly: true
});
}
Sample Data:
Sample Testing:
1. Wrote "add timestamp" to "H3" (Should trigger your first function)
2. Wrote "add timestamp" to "H4" (Should trigger your first function)
3. Edited "H4" to "change timestamp" (Should trigger your first function)
4. Edited "I4" to "1/22/2021" (Should trigger your second function)
Sample Data outcome:
RecentUpdates outcome:
Related
I have used following OnEdit() trigger code to lock cell after entering data first time:
function LockCells(event){
var range = event.range;
var description = 'Protected'; // + stringDate;
var protection = range.protect().setDescription(description);
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
And when I enter a value in a cell as a user (not sheet admin), it instantaneously blocks the cell from re-entering value. Can we delay this process? I mean if we enter value now but the protection on that cell is applied after 10 minutes or one hour but not immediately?
I believe your goal is as follows.
Your function of LockCells is executed by the OnEdit installable trigger.
You want to run the script in the function LockCells after the OnEdit trigger is run.
In this case, how about the following modified script?
Modified script 1:
For example, when the OnEdit trigger is run, when you want to run the script in the function LockCells after about 6 minutes, the modified script can be a bit simple as follows.
function LockCells(event) {
Utilities.sleep(5 * 60 * 1000); // For example, after 5 minutes, the script is run.
var range = event.range;
var description = 'Protected'; // + stringDate;
var protection = range.protect().setDescription(description);
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
Modified script 2:
When you want to run the script in the function LockCells after more than 6 minutes, the modified script is as follows. Please copy and paste the following script to the script editor of Spreadsheet. And, please reinstall the OnEdit installable trigger to the function LockCells. By this, when you edit the cell, the edited cell is protected after 10 minutes in this sample script.
var time = 10 * 60 * 1000; // 10 minutes
function LockCells(event) {
var date = new Date().getTime();
var range = event.range;
var a1Notation = `'${range.getSheet().getSheetName()}'!${range.getA1Notation()}`;
var p = PropertiesService.getScriptProperties();
var ranges = p.getProperty("ranges");
ranges = ranges ? JSON.parse(ranges).concat({ date, a1Notation }) : [{ date, a1Notation }];
p.setProperty("ranges", JSON.stringify(ranges));
ScriptApp.newTrigger("lockCellsByTrigger").timeBased().after(time).create();
}
function lockCellsByTrigger(e) {
ScriptApp.getScriptTriggers().forEach(t => {
if (t.getUniqueId() == e.triggerUid) ScriptApp.deleteTrigger(t);
});
var limit = time;
var now = new Date().getTime();
var p = PropertiesService.getScriptProperties();
var ranges = p.getProperty("ranges");
if (!ranges) return;
ranges = JSON.parse(ranges);
var {rranges, r} = ranges.reduce((o, e) => {
o[e.date + limit < now ? "rranges" : "r"].push(e);
return o;
}, {rranges: [], r: []});
if (rranges.length == 0) return;
p.setProperty("ranges", JSON.stringify(r));
var description = 'Protected';
var me = Session.getEffectiveUser();
rranges.forEach(({a1Notation}) => {
var protection = SpreadsheetApp.getActiveSpreadsheet().getRange(a1Notation).protect().setDescription(description);
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
});
}
When you want to change the time, please modify time. In the current stage, after 10 minutes, the edited cell is protected.
The flow of this script is as follows.
When a cell is edited, LockCells is run by the installable OnEdit trigger.
Put the a1Notation of edited cell and the date to Properties Service, and install the time-driven trigger after 10 minutes.
When the time-driven trigger runs the function lockCellsByTrigger, the edited cells after 10 minutes are protected.
References:
Properties Service
newTrigger(functionName)
I am trying to write a script for google sheets which returns the date in the next cell when the user enters 'y' in the current cell. I have a script which does this already, but the problem with my script is that the columns which it is evaluating is based on the column index, which means if our data set ever grows then these columns always have to stay in the same index which is creating a lot of organizational issues.
My question is..
Is it possible to look for the column header title rather than the column index in my code, and if so, what changes would I need to make?
function onEdit(e) {
if ([19].indexOf(e.range.columnStart) == -1 || ['y', 'Y'].indexOf(e.value) == -1) return;
e.range.offset(0, 1)
.setValue(Utilities.formatDate(new Date(), "GMT-5", "MM-dd-yyyy"))
}
This code currently looks at column index 19 and when either 'y' or 'Y' is entered into a cell in column index 19 it then outputs the date in the next cell in column 20.
How can I change the code to look for where the column header = 'Replied?' rather than index?
Goal:
If the following criteria is met:
Value is written into column 19 (S).
Header of column 19 (S) is 'Replied?'.
Value written is either 'Y' or 'y'.
Then write a date into the adjacent cell.
Code:
function onEdit(e) {
var sh = e.source.getActiveSheet();
var row = e.range.getRow();
var col = e.range.getColumn();
var value = e.value.toUpperCase();
var header = sh.getRange(1, col).getValue();
if (col === 19 && value === 'Y' && header === 'Replied?') {
sh.getRange(row, 20).setValue(Utilities.formatDate(new Date(), "GMT-5", "MM-dd-yyyy"))
}
}
Explanation:
I've based everything on the event objects passed to your onEdit trigger. For var value I have used toUpperCase() so that we don't have to check for either 'Y' OR 'y', only 'Y' alone. Also, instead of using range.offset I have just specified column 20 specifically in the getRange().setValue().
References:
Event Objects
String.toUpperCase()
One possible way to do this is to name the column/ cell in google sheets. See this website on how to.
Basically:
Open a spreadsheet in Google Sheets.
Select the cells you want to name.
Click Data and then Named ranges. A menu will open on the right.
Type the range name you want.
To change the range, click Spreadsheet Grid.
Select a range in the spreadsheet or type the new range into the text box, then - click Ok.
Click Done.
You can then refer to that named cell in google scripts by creating a custom function
function myGetRangeByName(n) { // just a wrapper
return SpreadsheetApp.getActiveSpreadsheet().getRangeByName(n).getA1Notation();
}
Then, in a cell on the spreadsheet:
myGetRangeByName("Names")
I'd do this.
function onEdit(e) {
var editedColumn = e.range.columnStart;
var sh = SpreadsheetApp.getActiveSpreadsheet();
var ss = sh.getSheetByName("This");//you only want onedits to the specific page
var data = ss.getDataRange().getValues();
var header = data[0][editedColumn];
if (header != "Replied") return;
if(e.value.toLowerCase() == "y"){
e.range.offset(0, 1)
.setValue(Utilities.formatDate(new Date(), "GMT-5", "MM-dd-yyyy"));}
}
You could also consider using a checkbox, that might be faster for your users.
This is the script that I am using currently. I want it to apply to all sheets in my document automatically. Please help. The following is the code.
/**
* Creates a Date Stamp if a column is edited.
*/
//CORE VARIABLES
// The column you want to check if something is entered.
var COLUMNTOCHECK = 1;
// Where you want the date time stamp offset from the input location. [row, column]
var DATETIMELOCATION = [0,1];
// Sheet you are working on
var SHEETNAME = '46'
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
//checks that we're on the correct sheet.
if( sheet.getSheetName() == SHEETNAME ) {
var selectedCell = ss.getActiveCell();
//checks the column to ensure it is on the one we want to cause the date to appear.
if( selectedCell.getColumn() == COLUMNTOCHECK) {
var dateTimeCell = selectedCell.offset(DATETIMELOCATION[0],DATETIMELOCATION[1]);
dateTimeCell.setValue(new Date());
}
}
}
I want the script to execute on every sheet so that whenever I add a new sheet the script runs automatically and helps me add a time stamp to column two when I add a value to column 1.
Please help.
I am a complete noob here and as such some detailed explanation will help me immensely.
Basically, just remove the if statement and the code will apply to EVERY sheet.
In the answer given below, I've also used some of the objects returned by the onEdit trigger.
/**
* Creates a Date Stamp if a column is edited.
*/
//CORE VARIABLE
// The column you want to check if something is entered.
var COLUMNTOCHECK = 1;
function onEdit(e) {
var sh = e.range.getSheet();
if (e.range.columnStart == COLUMNTOCHECK){
// edited the right column do something
var targetcell = sh.getRange(e.range.rowStart,2);
targetcell.setValue(new Date());
}
else {
// do nothing
}
}
Goal: I'm trying to create a behavior tracker for four classes in Google Spreadsheets. The tracker has nine sheets: Class7A, Class7B, Class8A, Class8B, and Mon-Fri summary sheets. The goal was for each ClassXX sheet to have behavior tracking information for an entire week, but for the default view to show only the current day's information.
Attempts: During initial workup (with only the Class7A sheet created), I got this to work using a modification of the script found here (Thank you Jacob Jan Tuinstra!): Optimize Google Script for Hiding Columns
I modified it to check the value in the third row of each column (which held a 1 for Monday, 2 for Tuesday, etc), and if it did not match the numerical equivalent for the day of the week (var d = new Date(); var n = d.getDay();), then it would hide that column. This process was somewhat slow - I'm assuming because of the iterating through each column - but it worked.
Quite excited, I went ahead and added the rest of the sheets, and tried again - but the code as written, seems to affect only the current sheet. I tried modifying it by replacing var sheet = ss.getSheets()[0]; with for script that iterated through the columns, until i>4 (I've since lost that piece of code), with no luck.
Deciding to go back and try adapting the original version of the script to instead explicitly run multiple times for each named sheet, I found the that script no longer seems to work at all. I get various version of "cannot find XX function in sheet" or "cannot find XX function in Range."
Source: A shared version (with student info scrubbed) can be found here: https://docs.google.com/spreadsheets/d/1OMq4a4_Gh_xyNk_IRy-mwJn5Hq36RXmdAzTzx7dGii0/edit?usp=sharing (editing is on).
Stretch Goal: Ultimately, I need to get this to reliably show only the current day's columns (either through preset ranges (same for each sheet), or the 1-5 values), and I need it to do so for all four ClassXX sheets, but not the summary pages (and preferably more quickly than the iterations). If necessary, I can remove the summary pages and set them up externally, but that's not my first preference. I would deeply appreciate any help with this; so far my attempts have seemed to only take me backwards.
Thanks!
Current code:
function onOpen() {
// get active spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
// create menu
var menu = [
{name: "Show Today Only", functionName: "hideColumn"},
{name: "Show All Days", functionName: "showColumn"},
{name: "Clear Week - WARNING will delete all data", functionName: "clearWeek"}
];
// add to menu
ss.addMenu("Show Days", menu);
}
var d = new Date();
var n = d.getDay();
function hideColumn() {
// get active spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
// get first sheet
var sheet = ss.getSheets()[0];
// get data
var data = sheet.getDataRange();
// get number of columns
var lastCol = data.getLastColumn()+1;
Logger.log(lastCol);
// itterate through columns
for(var i=1; i<lastCol; i++) {
if(data.getCell(2, i).getValue() != n) {
sheet.hideColumns(i);
}
}
}
function showColumn() {
// get active spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
// get first sheet
var sheet = ss.getSheets()[0];
// get data
var data = sheet.getDataRange();
// get number of columns
var lastCol = data.getLastColumn();
// show all columns
sheet.showColumns(1, lastCol);
}
I cannot recreate the problem of the script not working at all, it's working fine for Class7A so that part is working fine.
So let's look at the two other problems:
Applying this to all Sheets
Speeding up the script
First let's create some globals we use in both functions
var d = new Date();
var n = d.getDay();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetNames = ss.getSheets().map(function(sheet) {return sheet.getName();});
var classSheets = sheetNames.filter(function(sheetName) {return sheetName.match("Class")});
Now we can iterate over classSheets and get the sheet by name and hide columns in each.
However hiding each individual column is very slow.
The sheet is built very structured, every week has 12 columns (except for friday which doesn't have the grey bar), so we can just calculate the ranges we want to hide.
function hideColumn() {
classSheets.map(function(sheetName){
var sheet = ss.getSheetByName(sheetName);
if (n == 1) {
// Hide everything after the first three columns + Monday
sheet.hideColumns(3 + 11, 12 * 4);
} else if (n == 5) {
// Hide everything to the left except the leftmost three columns
sheet.hideColumns(3, 4 * 12);
} else {
// Hide everything left of the current day
sheet.hideColumns(3, (n - 1) * 12);
// Hide everything after the current day
sheet.hideColumns(3 + n * 12, (5 - n) * 12 - 1);
}
});
}
Lastly we can shorten showColumn
function showColumn() {
classSheets.map(function(sheetName){
var sheet = ss.getSheetByName(sheetName);
var lastCol = sheet.getLastColumn();
sheet.showColumns(1, lastCol);
});
}
Currently i have the following google script:
function onEdit() {
var s = SpreadsheetApp.getActiveSheet();
var r = s.getActiveCell();
if(s.getName()=='Setup BRF'){
if( r.getColumn() != 2 ) { //checks the column
var row = r.getRow();
var time = new Date();
time = Utilities.formatDate(time, "GMT", "dd-MM-yy' , 'HH:mm:ss");
SpreadsheetApp.getActiveSheet().getRange('J1').setValue(time);
};
};
};
Everytime i edit something on the sheet called Setup BRF it updates the time and date in J1 to show when it was last updated/edited.
My question is if its possible to add a name to lets cell K1 which shows who last updated this sheet. My skill with javascript is nihil so any kind of help is appreciated.
You could use the event object passed to the function
So change the definition to
function onEdit(e) {
and then you can use e.user to get to the user
Something like this
if( r.getColumn() != 2 ) { //checks the column
var row = r.getRow(),
time = new Date(),
user = e.user;
time = Utilities.formatDate(time, "GMT", "dd-MM-yy' , 'HH:mm:ss");
SpreadsheetApp.getActiveSheet().getRange('J1').setValue(time);
SpreadsheetApp.getActiveSheet().getRange('K1').setValue(user);
};