Event from one sheet dumping timestamp on another sheet - javascript

I need to create an operation on another sheet of where the event came from.
For example, when any cell in the "data" sheet is edited/changed, dump a timestamp in the "other" sheet (within the same spreadsheet). If it helps to make the code faster, the "data" sheet will at least always have cell 1,1 edited even if it's the same value because something else automatically dumps data and re-writes the entire "data" sheet.
The main problem is that I don't know what value to compare the conditional statement with since I don't know what values will change in the see. See below.
// Dumps the timestamp of the last time the "Data" sheet was edited
function onEdit(e){
var dataSheetLastColumn = thisSpreadsheet.getSheetByName("Data").getLastColumn();
var dataSheetLastRow = thisSpreadsheet.getSheetByName("Data").getLastRow();
var dataSheetEvent = e.source.getSheetByName("Data").getRange(1, 1, dataSheetLastRow, dataSheetLastColumn);
// puts a time-stamp on another sheet
var theOtherSheet = thisSpreadsheet.getSheetByName("Other");
theOtherSheet.getRange(2, 1).setValue(new Date());
}

try this:
function onEdit(e) {
if(e.range.getSheet().getName()=='Other'){return;}
e.source.getSheetByName('Other').getRange(2,1).setValue(Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "MM/dd/yyyy HH:mm:ss"));
}
Note: this event only occurs on user edits.
Simple Trigger Restrictions
function onEdit(e) {
if(e.range.getSheet().getName()!='Data' ) {return;}
e.source.getSheetByName('Other').getRange(2,1).setValue(Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "MM/dd/yyyy HH:mm:ss"));
}

Related

Format plain text to date

Goal
Write a script that does the following:
get data from sheet
modify column to flip names from [Last, First] to [First Last]
modify 2 columns to abbreviate company names & statuses
write resulting data to another spreadsheet without changing the format
add timestamp for when data copied
Problem
I can get the data BUT it writes everything back as plain text. Thus instead of dates writing out as "yyyy-MM-dd", they write out as something like this: Mon Oct 19 2020 01:00:00 GMT-0400 (Eastern Daylight Time)
Expectation:
screenshot of dates as "yyyy-MM-dd"
Result:
screenshot of dates as whatever this garble is
I have googled extensively and can't seem to find a solution. I believe my problem is with using toString() in the Array.map. I'm not sure how to restrict the map method to only the columns that need modifying. Right now it affects the whole array.
(I used the code from Google Apps Script for Multiple Find and Replace in Google Sheets to write this part)
//-----FIND AND REPLACE FOR COMPANY & STATUS ABBREVIATIONS
function replaceInSheet(initArray, to_replace, replace_with){
//Loop over rows in array
for(var row in initArray ){
//Use Array.map to execute a replace call on each of the cells in the row.
var replaced_values = initArray[row].map(function(originalValue){
return originalValue.toString().replace(to_replace,replace_with);
});
//Replace the original row values with the replaced values
initArray[row] = replaced_values;
}
}
Question--> How do I get the output of my script to format dates in two of my columns, correctly?
Attempted Solutions that didn't work
I tried swapping the name flip code to happen after the abbreviation code so I could add a setNumberFormat('yyyy-MM-dd') within the name flip for loop. I couldn't figure out how to apply this to columns within my array. Something like initarray[x][5].setNumberFormat("yyyy-MM-dd") gave me an error saying "TypeError: initArrayx.setNumberFormat is not a function"
I tried adding code before (and then even after) .setValues() at the end to change the format. Some resources I referenced:
setNumberFormat('yyyy-MM-dd') from stackoverflow: Set cell format with google apps script
Utilities.formatDate(new Date(), "CST", "yyyy-MM-dd") from stackoverflow: Get today date in google appScript
website post by BLACKCJ: "Cell Number Formatting with Google Apps Script"
google's developer documentation
and all sorts of other articles, blogs, forums, etc.
I tried writing a completely separate function to change the format after my script runs. Nope. I can't get those 2 columns to format as dates
Code & Sample Spreadsheet
Here's the whole code I'm using, modified to work with a sample google sheet I created just for the purposes of this question.
Sample Google Sheet:
https://docs.google.com/spreadsheets/d/1Ys77hQHHajIo-Xaxyom0SVnyVMZ6bKOT8Smpadd2jv4/edit?usp=sharing
Script:
// ==================================================
// FUNCTION TO RUN
// ==================================================
function syncData(){
//Ger Source Data
var ss = SpreadsheetApp.getActiveSpreadsheet();
var thisSheet = ss.getSheetByName("source");
var thisData = thisSheet.getRange("A4:M11");
var initArray = thisData.getValues();
//Get Target Location
var toSheet = ss.getSheetByName("target");
var toRange = toSheet.getRange("A4:M11"); //Range starts at A4
//CHANGE [LAST, FIRST] TO [FIRST LAST]
for (var x = 0; x < initArray.length; x++){
var indexOfFirstComma = initArray[x][0].indexOf(", ");
if(indexOfFirstComma >= 0){
//If comma found, split and update values in the values array
var lastAndFirst = initArray[x][0];
//Update name value in array
initArray[x][0] = lastAndFirst.slice(indexOfFirstComma + 2).trim() + " " + lastAndFirst.slice(0, indexOfFirstComma).trim();
}
}
//ABBREVIATE COMPANY
replaceInSheet(initArray, 'Bluffington School','BLF HS');
replaceInSheet(initArray, 'Honker Burger','HBGR');
replaceInSheet(initArray, 'Funky Town','FT');
//ABBRIVIATE STATUS
replaceInSheet(initArray, 'Regular','Staff');
replaceInSheet(initArray, 'Contractual','Part');
replaceInSheet(initArray, 'Temporary','Temp');
//Clear Target Location
var toClear = toSheet.getRange("A4:M11")
toClear.clearContent();
//Write updated array to target location
toRange.setValues(initArray);
//Write timestamp of when code was last run
setTimeStamp(toSheet);
}
//-----FIND AND REPLACE FOR COMPANY & STATUS ABBREVIATIONS
function replaceInSheet(initArray, to_replace, replace_with){
//Loop over rows in array
for(var row in initArray ){
//Use Array.map to execute a replace call on each of the cells in the row.
var replaced_values = initArray[row].map(function(originalValue){
return originalValue.toString().replace(to_replace,replace_with);
});
//Replace the original row values with the replaced values
initArray[row] = replaced_values;
}
}
//-----ADD TIMESTAMP FOR WHEN THE SCRIPT LAST RAN
function setTimeStamp(toSheet) {
var timestamp = Utilities.formatDate(new Date(), "CST", "yyyy-MM-dd # h:mm a");
toSheet.getRange('F1').setValue(timestamp);
}
setNumberFormat('yyyy-MM-dd') is a good solution but it's a method of a Range of the sheet. Not an array.
To apply the format you need to get a range first. Something like this:
toSheet.getRange('G4:G').setNumberFormat('yyyy-MM-dd');
And there is one more thing ) Try to change this line:
var initArray = thisData.getValues();
to:
var initArray = thisData.getDisplayValues();

Automatically protect a cell / range after inputting data in Google Sheets

I have a public spreadsheet, where people can enter their names under the column "Name". Everything in this sheet is protected, except for the cells in the column "Name". Since the spreadsheet is public, I want to avoid a situation where someone can troll and delete all the names that have been inputted. Hence, I'm trying to set up a script using the on edit triggers to protect the cell in this range after anyone has entered their name in a cell. So far I've been manually protecting the cells after a name has been entered.
I've found out that the best way to do this would be to use the on edit trigger. I have used javascript before but as I'm new to google spreadsheet scrips, I can't get my script to run like it's supposed to. The current script is supposed to automatically protect the range on edit, and add a description of when the protection was done.
Sample spreadsheet with the script in it here: https://docs.google.com/spreadsheets/d/18NlVKcaeyOkgqIa6WAuDsu5TSYK37m_vXLmp7p7Q8kc/edit?usp=sharing
function protectOnEdit(event) {
var ss = SpreadsheetApp.getActive();
var range = ss.getRange('Sheet1!A2:A1000');
var timeZone = Session.getScriptTimeZone();
var stringDate = Utilities.formatDate(new Date(), timeZone, 'dd/MM/yy HH:mm');
var description = 'Protected on ' + stringDate;
var protection = range.protect().setDescription(description);
// below code taken directly from Google's documentation
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
Reference: https://developers.google.com/apps-script/reference/spreadsheet/range#protect()
The data range in question is A2:A1000 and currently it -seems- to partially work, however, it protects the WHOLE range after editing a single cell, instead of just protecting the edited cell like it's supposed to.
Are there any steps I'm missing in order for the script to lock the cells individually, instead of the whole range? Any insights are very appreciated!
I have made some corrections:
function protectOnEdit(e) {
var range = e.range;
// Be sure to have edited the sheet "Sheet1"
if (range.getSheet().getName() != "Sheet1") return;
// Be sure to have a single cell as edited range
if (range.getWidth() != 1 || range.getHeight() != 1) return;
// Be sure to have edited cell inside A2:A1000
if (range.getColumn() != 1 || range.getRow() < 2) return;
// Be sure to have non-blank new value
if (!e.value) return;
// Protect this range
var timeZone = Session.getScriptTimeZone();
var stringDate = Utilities.formatDate(new Date(), timeZone, 'dd/MM/yy HH:mm');
var description = 'Protected on ' + stringDate;
var protection = range.protect().setDescription(description);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) protection.setDomainEdit(false);
}
At first the script checks several conditions for desired protection. They seem to me to be important for the task. If all of them are TRUE, then protects a single cell. The spreadsheet owner has no restrictions to edit protected cells but other editors have.
Of course, the owner should install a trigger for this function properly to automate the process.

Javascript in Google Sheets script: help using setNumberFormat

Hoping this is a simple problem for you lot. I have no coding knowledge at all. But been using the below script in a Google Sheet to grab changing data from another sheet and log it daily, appending as it goes. Can't remember where I found the script - if I did I'd go back and ask its creator. It's been working fine; only thing is I have to manually copy paste my preferred date format every day. So I'd like the script to print the date in "dd/MM/yyyy" format (while retaining hours and minutes data inside the cell). I've been reading and searching online, and experimenting for ages but can't figure it out. This is the base code:
function recordHistory() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("History");
var source = sheet.getRange("A2:C2");
var values = source.getValues();
values[0][0] = new Date();
sheet.appendRow(values[0]);
};
I've tried placing setnumberformat in various places and nearly always get an error. Perhaps my best attempt, inspired by other examples I've seen, was to add these new lines:
function recordHistory() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("History");
var source = sheet.getRange("A2:C2");
var values = source.getValues();
values[0][0] = new Date();
sheet.appendRow(values[0]);
var cell = SpreadsheetApp.getActiveSheet().getRange(2, 1, 979);
cell.setNumberFormat("dd/MM/yyyy");
};
I hoped this would format the entire date row (Row A) after appending the new data. Probably a clunky solution even if it worked, but it didn't. It's my only attempt so far that doesn't return an error! So, yay? But it doesn't change the date format. So I give up. Any ideas?
To specify the format for a specific cell
var cell = sheet.getRange("A2");
cell.setNumberFormat("dd/MM/yyyy");
To specify the format for a range of cells
var cells = sheet.getRange("A2:C2");
cells.setNumberFormat("dd/MM/yyyy");
Please see the documentation for Range and formats.
Just in case you need the time as well, follow this code.
var cell = sheet.getRange("A2");
cell.setNumberFormat("dd/MM/yyyy h:mm:ss AM/PM");

Google Script to copy and archive Form Responses in New Sheet on Weekly Basis

So I'll start out by saying I just started learning Javascript 4 days ago. Now that that's out of the way, my intention with this script.
I'd like to automate the process of moving Google Form Responses, which are collected in a spreadsheet, to a new sheet within the same workbook as an archive.
I'd like this to happen on a weekly basis, and for each archive sheet that is created to have only 1 weeks responses. This should be between 12:01AM-1:00AM on Sundays, it really doesn't matter during that hour when it happens.
I would also like to then delete all of those responses from the primary collection sheet(Current_Responses), but if I have to manually delete these later it's fine (and probably good, because then I can review that the script worked properly).
I feel like I have a pretty solid start on doing this, but since I am new to all this, I would really appreciate it if a more experienced scripter could look over my code and tell me if this will work how I intend it to, and if not, where the mistakes are and how to correct them. I'm happy to make mistakes, and then learn from them so any advice will be deeply honored.
I researched several topics and scripts across three websites to help put this together. Thanks in advance for any help and advice!
// function to copy from Current_Responses to new sheet 'Archived_Responses
//(UTC Date)' placed after Current_Responses
function CreateCopySheetWeekly() {
//source info
var ss = SpreadsheetApp.getActiveSpreadsheet();
var templateSheet = ss.getSheetByName('Current_Responses');
var range = ss.getRange ('A:I'); //replace column length as needed
var data = range.getValues ();
//creates target sheet to copy responses to
var ts = 'Archived_Responses '+formatDate();
ss.insertSheet(sheetName, ss.getSheets().length, {template: templateSheet});
ts.getRange(ts.getLastRow()+1, 1, data.length, data[0].length).setValues(data);
}
//end of primary function
//function to determine and format UTC Date for CreateCopySheetWeekly function
function formatDate() {
var month, day, d = new Date();
month = ('0'+(d.getUTCMonth()+1)).slice(-2);
day = ('0'+(d.getUTCDate()).slice(-2);
return d.getUTCFullYear()+'-'+month+'-'+day;
}
//end of date function
//check every hour to determine when to perform newSheetLast function. Intended for Sunday
//between 0001-0100
window.setInterval (onSunday(){
var today = new Date();
if (today.getDay() == 0 && today.getHours() === 12) {
CreateCopySheetWeekly();
}, 600000);
Go easy on me since I am new at this, but constructive criticism never hurt anyone.
If you run this once a week we can make the simplifying assumption that you want all responses backed up once a week and then clear the main response sheet.
Let's go through the functions step by step and optimize it.
First of all you are already getting all rows, if you want all data you can just getDataRange() and not have to worry about extending.
Also we won't need the actual data.
function CreateCopySheetWeekly() {
//source info
var ss = SpreadsheetApp.getActiveSpreadsheet();
var templateSheet = ss.getSheetByName('Current_Responses'); // Current Responses
var range = ss.getDataRange();
formatDate() is just creating a ISO8601 representation of the current date if I understand correctly so you can remove the function and instead use
var ts = 'Archived_Responses '+ new Date().toISOString().slice(0,10);
Insert sheet with a template already copies all the data so you only need
ss.insertSheet(ts, ss.getSheets().length, {template: templateSheet});
Then we want to clean up our main response sheet.
If we just clear the range the form will keep appending after what would be the last row if we had never cleared because it remembers how many responses it got so we need to delete all rows but the headers.
templateSheet.deleteRows(2, range.getNumRows() - 1);
Making the final script
function CreateCopySheetWeekly() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var templateSheet = ss.getSheetByName('Current_Responses');
var range = ss.getDataRange();
var ts = 'Archived_Responses '+ new Date().toISOString().slice(0,10);
ss.insertSheet(ts, ss.getSheets().length, {template: templateSheet});
templateSheet.deleteRows(2, range.getNumRows() - 1);
}
Lastly you can schedule it by going to Resources > Current Project's Triggers and set up a time based trigger.

Clear cell value of adjacent cell if value is a date

I've been working on this script in Google Apps Script for a little while now and think I've got it almost figured out. I could really use another pair of eyes though, as it hasn't yet worked to completion.
What I'm trying to do:
I'm working on a sheet dealing with tentative and actual dates. These two dates are in adjacent columns. Once the "actual" date gets filled into its cell, I would like the "tentative" date to be deleted.
Here is my script thus far:
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var range = sheet.getRange('F4:F');
var range2 = sheet.getRange('E4:E');
var rule = range.getDataValidation();
if (rule != null) {
var criteria = rule.getCriteriaType();
var args = rule.getCriteriaValues();
var clear = range2.clear({validationsOnly: false});
}
}
What do I need to do to get this script running?
It appears you want to delete the value in column E if column F was filled. If so, I don't see the need for dealing with data validation rules. The following script checks that the edited cell is in column F, row >= 4, and that the new value in the cell is a date. If these conditions hold, it clears the cell to the left (using offset).
function onEdit(e) {
if (e.range.getColumn() == 6 && e.range.getRow() >= 4) {
if (e.range.getValue() instanceof Date) {
e.range.offset(0, -1).clear();
}
}
}
Note: this script uses the event object passed to onEdit function. Interestingly, its "value" property turned out to be not useful here, because it is not a date object even when a user enters a date. This is what I use e.range.getValue() instead.

Categories