I am looking for help with a script that posts that a specific sheet has been edited rather than when any sheet in the document is modified.
Here is what I currently have:
function onEdit() {
var d = new Date();
var h = d.getHours();
var min = d.getMinutes();
var sec = d.getSeconds();
var h_str = h;
var min_str = min;
var sec_str = sec;
// format time nicely
if (h < 10)
h_str = '0' + h;
if (min < 10)
min_str = '0' + min;
if (sec < 10)
sec_str = '0' + sec;
// create the formatted time string
var time_str = h_str + ':' + min_str + ':' + sec_str;
// create the message
var s = 'Roster last modified on: ' + d.toDateString() + ' # ' + time_str;
// change the range
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Infernal Dawn').getRange("A1").setValue(s);}
This works perfectly fine to only update the sheet "Infernal Dawn" with the modification date, yet it also updates when ANY sheet is modified.
I found that there were 2 different ways of doing this (one using the gid). The only problem is that my skills in coding are extremely limited and I couldn't figure out how to integrate it into the above code. Any help would be appreciated!
The function onEdit has an optional argument that remembers which cell\range\sheet was edited. The structure of this argument is described here.
function onEdit(e)
{ var d=new Date();
var s=d.toLocaleString();
if(e.source.getActiveSheet().getName()=="TheSheetThatWeWishToTrace")
{ SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Infernal Dawn').getRange("A1").setValue(s);}
}
Related
I am trying to get data from a particular google sheets to create events in google calendar. See the spreadsheet.
https://docs.google.com/spreadsheets/d/1eBEStiTKXI0YPXfQBYzqXjwwv4kZ21033TdtysxdhHI/edit?usp=sharing
Basically, when someone ticks the boxes, it creates an event for the person (row 2) at date (Col B) at 10:00:00 AEST. So for example, when some ticks the box in cell P14, it creates an event with:
name - Lily Ahadi - PC
Date of event - 16-Mar-2020
Time of event: 10:00:00
Here is the code I partially worked on and then got someone else to help and we both didnt get anywhere. I am about to give up but thought I will give a last try with stackflow experts. The code is availabe in the script editor of the sheet.
function onEdit(e) {
try {
var range = e.range;
Browser.msgBox(range);
var nameSheet = e.source.getSheetName();
var rowID = range.rowStart;
var colID = range.columnStart;
var res = e;
var oldValue = res.oldValue;
var newValue = res.value;
var data = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(nameSheet);
if (oldValue == "FALSE" && newValue == "TRUE" && nameSheet == "Master Client List"){
var category = data.getRange(3, colID).getValue();
var date = data.getRange(rowID, 2).getValue();
date = getYesterdaysDate(date)
var timeDiff = 60;
var startTime = "10:00:00";
if (category == "PC"){
var name = data.getRange(2, colID - 1).getValue();
Browser.msgBox(name+'-'+category + date);
}else{
var name = data.getRange(2, colID - 2).getValue();
}
// var startDateTime = date+' '+startTime+':00';
var startDateTime = testMoment1(date, startTime);
var endDateTime = addMins(startDateTime, timeDiff);
var event = CalendarApp.getDefaultCalendar().createEvent(name+'-'+category,
new Date(startDateTime),
new Date(endDateTime),
{description: ''});
Logger.log('Event ID: ' + event.getId());
}
}
catch(err) {
SpreadsheetApp.getUi().alert(err);
}
}
function testMoment(date, time) {
eval(UrlFetchApp.fetch('https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js').getContentText());
var date = moment(date).format('YYYY/MM/DD');
// var time = time;
// Logger.log(moment(date).format('MM/DD/YYYY'));
// tell moment how to parse the input string
var momentObj = moment(date + time, 'YYYY-MM-DDLT');
// conversion
var dateTime = momentObj.format('YYYY-MM-DDTHH:mm:ss');
return dateTime;
}
function testMoment1(date, time) {
eval(UrlFetchApp.fetch('https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js').getContentText());
// var date = moment(date).format('YYYY/MM/DD');
// var time = time;
// Logger.log(moment(date).format('MM/DD/YYYY'));
var c = new Date();
var n = c.getFullYear();
// tell moment how to parse the input string
var momentObj = moment(date + time, 'YYYY-MM-DDLT').set('year', n).add(0, 'days');
// conversion
var dateTime = momentObj.format('YYYY-MM-DDTHH:mm:ss');
// dateTime = moment(dateTime, "YYYY-MM-DDTHH:mm:ss");
return dateTime;
}
function addMins(dateTime, durationInMinutes){
eval(UrlFetchApp.fetch('https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js').getContentText());
var startTime = dateTime;
// var durationInMinutes = '120';
var endTime = moment(startTime, 'YYYY-MM-DDTHH:mm:ss').add(durationInMinutes, 'minutes').format('YYYY-MM-DDTHH:mm:ss');
return endTime;
}
function getYesterdaysDate(date1) {
var date = new Date(date1);
date.setDate(date.getDate());
var day = date.getDate();
var month = (date.getMonth()+1);
var year = date.getFullYear();
month = month < 10 ? '0'+month : month;
day = day < 10 ? '0'+day : day;
// Logger.log(date.getFullYear() + '-' + (date.getMonth()+1) + '-' + date.getDate());
return year + '/' + month + '/' + day;
}
function myFunction() {
var data=SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Master Client List');
// var s=ss.getActiveSheet();
var c=data.getLastColumn();
for (var i = 3; i <= c; i++) {
if (i%3 === 0){
var name = data.getRange(2, i).getValue();
var dob = data.getRange(1, i).getValue();
var timeDiff = 60;
var startTime = "10:00:00";
Logger.log(name);
Logger.log(name);
Logger.log(startTime);
//birthdayevent(name, dob, startTime, timeDiff)
Logger.log(name);
// break;
}
}
}
Your code has two main issues:
1. You are querying for if (oldValue == "FALSE")
If you implement into your code the line Logger.log(oldValue); you will realize that an empty checkbox will return you the value "false" and not "FALSE". You need to modfy your if condition accordingly.
2. You are trying to use UrlFetchApp on simple onEdit trigger
As specified under restrictions for simple triggers:
They cannot access services that require authorization.
This problem can be easily solved by transforming your trigger into an installable one.
For this:
Rename your function onEdit() to something different
Bind to the funciton an installable onEdit trigger as described here
After you implement those two modifications your code will run and create an event when you check a checkbox.
Now, I am not familiar enough to judge either the event parameters (data, event title) are retrieved as you desire. For troubleshooting I recommend you to log all variables - this will help you to spot any error quickly.
I have the similar issue as described at How to make sure a daily time trigger runs?.
I have a specific script in one of the Google sheets with a daily trigger (time-driven, should trigger every morning, set up through interface, not programmatically). But the script doesn't execute every day. I can see this in the execution report, where there're just successful executions and no failed ones. I can also see if the script executed by checking a cell in the sheet which gets updated with the execution timestamp when the script runs. And I've set up an immediate notification for the failed executions in the trigger settings.
In my specific case, the script should ran every day from Nov 9 - Nov 13, but it ran just on Nov 9, Nov 10, Nov 12. And I didn't get any notification about the failed execution.
The script itself doesn't use any API, it's pretty basic: reading data in one sheet, doing some calculation and writing to another sheet (talking about the sheets in single Google Sheet file).
If I run the main function manually, it always works.
I'd be very glad to get some ideas what could be wrong. Thanks.
EDIT: Code sample (main function and prototype for Array.includes)
function main(){
var date = new Date();
//var date = new Date(2019, 9, 1); // year, month (zero-indexed!!!), day
//var date = new Date(date.getYear(), date.getMonth()-3); // testing
var currentDay = Utilities.formatDate(date, "CET", "d");
Logger.log('currentDate: ' + Utilities.formatDate(date, "CET", "YYYY-MM-dd HH:mm:ss.S") + ' | currentDay: ' + currentDay);
if (currentDay == 1) {
Logger.log('currentDay is 1st of the month');
date = new Date(date.getYear(), date.getMonth() - 1);
var newCurrentDay = Utilities.formatDate(date, "CET", "d");
}
var monthToCheck = Utilities.formatDate(date, "CET", "MMMM").toUpperCase();
var yearToCheck = Utilities.formatDate(date, "CET", "YYYY");
Logger.log('dateToCheck: ' + Utilities.formatDate(date, "CET", "YYYY-MM-dd HH:mm:ss.S") + ' | monthToCheck: ' + monthToCheck + ' | yearToCheck: ' + yearToCheck);
var firstProjectRow = 7; // first row with the project data
var firstProjectCol = 1; // first column with project data - should contain Tool IDs
var numOfProjectRows = 999; // num of project rows to check (counted from and including var firstProjectRow)
var numOfProjectCols = 21; // num of project columns to check (counted from and including var firstProjectCol the last one contains number of hours for the last service)
var firstProjectHoursCol = 7; // first column with data about project hours (usually PM hours)
// ************* DO NOT EDIT BELOW THIS LINE ************* //
//return;
var indexedFirstProjectHoursCol = firstProjectHoursCol - 1;
var ss = SpreadsheetApp.getActiveSpreadsheet();
//var sheet = ss.getSheets()[3];
var sheetName = monthToCheck + ' ' + yearToCheck;
var sheet = ss.getSheetByName(sheetName);
Logger.log('sheet: ' + sheetName);
var range = sheet.getRange(firstProjectRow, firstProjectCol, numOfProjectRows, numOfProjectCols); // getRange(row, column, numRows, numColumns)
var rangeValues = range.getValues();
//Logger.log('rangeValues: "' + rangeValues);
var toolData = new Array();
var toolIds = new Array();
var toolHours = new Array();
//return;
for (var row in rangeValues) {
Logger.log('row: "' + row);
var clientId = rangeValues[row][0];
var projectId = rangeValues[row][1];
var hoursSum = 0;
// we have Tool ID so it's OK to proceed
if (clientId != "" && projectId != "") {
var clientProjectId = clientId + "-" + projectId;
for (var col in rangeValues[row]) {
var cellValue = rangeValues[row][col];
//Logger.log('col: ' + col + ' value: ' + value);
// get hours sum
if (col >= indexedFirstProjectHoursCol)
hoursSum += typeof cellValue == 'number' ? cellValue : 0;
}
//Logger.log('hoursSum: [' + hoursSum + ']');
var record = {id: clientProjectId, hours: hoursSum};
Logger.log("Data: " + record.id + " : " + record.hours);
// don't yet have a record of clientId-projectId
if (!toolIds.includes(clientProjectId)) {
toolData.push(record);
}
else {
recordIdx = toolIds.indexOf(clientProjectId);
toolData[recordIdx].hours += hoursSum;
}
toolIds = [];
toolHours = [];
toolData.forEach(function(item) {
toolIds.push(item.id);
toolHours.push(item.hours);
});
}
//Logger.log(toolData);
//Logger.log('ROW DONE!');
}
Logger.log('ROWS DONE!');
Logger.log('toolData.length: ' + toolData.length);
toolData.forEach(function(item) {
Logger.log('toolData: ' + item.id + " : " + item.hours);
});
Logger.log('DONE!!!');
// fill the table in the sheet with assigned number of hours
fillTheSheet(sheetName, toolData);
}
Apps Script triggers have always been a bit finicky. But of late they have been far more unreliable than usual (there have been several reports of spurious triggers and other maladies).
In this case, you can avoid using them altogether by leveraging an external service such as cron-jobs.org.
You'll have to refactor your app script project and deploy it as a public Web App with a doPost(e) function. You'd then pass the Web App's url to the external service as a web-hook endpoint that is invoked daily.
This question already has answers here:
Periodically refresh IMPORTXML() spreadsheet function
(4 answers)
Closed last month.
I'm trying to get a Google sheets apps script to work for an IMPORTXML I'm using.
A1
=importxml("http://www.nfl.com/liveupdate/scorestrip/ss.xml","//#q")
A2
=importxml("http://www.nfl.com/liveupdate/scorestrip/ss.xml","//#h")
The data fills from A1:B16
According to a script I found on web to have it auto refresh:
function getData() {
var queryString = Math.random();
var cellFunction1 = '=IMPORTXML("' + SpreadsheetApp.getActiveSheet().getRange('A1').getValue() + '?' + queryString + '","'+ SpreadsheetApp.getActiveSheet().getRange('A2').getValue() + '")';
SpreadsheetApp.getActiveSheet().getRange('C1').setValue(cellFunction1);
var cellFunction2 = '=IMPORTXML("' + SpreadsheetApp.getActiveSheet().getRange('A4').getValue() + '?' + queryString + '","'+ SpreadsheetApp.getActiveSheet().getRange('A5').getValue() + '")';
SpreadsheetApp.getActiveSheet().getRange('C2').setValue(cellFunction2);
}
I don't know what I'm supposed to be putting/replacing in that code with mine. If someone could help me to explain what I'm supposed to be changing to get it to work in my sheet/provide some examples of how one might look that would be a huge help.
I appreciate
You can update the function by using getFormula() then setFormula() in a time-driven trigger function. Here is a code snippet from a related SO post:
/**
* Go through all sheets in a spreadsheet, identify and remove all spreadsheet
* import functions, then replace them a while later. This causes a "refresh"
* of the "import" functions. For periodic refresh of these formulas, set this
* function up as a time-based trigger.
*
* Caution: Formula changes made to the spreadsheet by other scripts or users
* during the refresh period COULD BE OVERWRITTEN.
*
* From: https://stackoverflow.com/a/33875957/1677912
*/
function RefreshImports() {
var lock = LockService.getScriptLock();
if (!lock.tryLock(5000)) return; // Wait up to 5s for previous refresh to end.
// At this point, we are holding the lock.
var id = "YOUR-SHEET-ID";
var ss = SpreadsheetApp.openById(id);
var sheets = ss.getSheets();
for (var sheetNum=0; sheetNum<sheets.length; sheetNum++) {
var sheet = sheets[sheetNum];
var dataRange = sheet.getDataRange();
var formulas = dataRange.getFormulas();
var tempFormulas = [];
for (var row=0; row<formulas.length; row++) {
for (col=0; col<formulas[0].length; col++) {
// Blank all formulas containing any "import" function
// See https://regex101.com/r/bE7fJ6/2
var re = /.*[^a-z0-9]import(?:xml|data|feed|html|range)\(.*/gi;
if (formulas[row][col].search(re) !== -1 ) {
tempFormulas.push({row:row+1,
col:col+1,
formula:formulas[row][col]});
sheet.getRange(row+1, col+1).setFormula("");
}
}
}
// After a pause, replace the import functions
Utilities.sleep(5000);
for (var i=0; i<tempFormulas.length; i++) {
var cell = tempFormulas[i];
sheet.getRange( cell.row, cell.col ).setFormula(cell.formula)
}
// Done refresh; release the lock.
lock.releaseLock();
}
}
Hope this helps.
I have been working on something related and finally solved it by using code from this StackOverFlow Post, but I needed to add some extra bits.
It wasn't working well for me, so I made some changes and added extra logging to make it understandable for me. Here is goes:
function RefreshImports() {
var lock = LockService.getScriptLock();
if (!lock.tryLock(5000)) return; // Wait up to 5s for previous refresh to end.
var now = new Date();
// Show start time on log
Logger.log("Starting Running at " + now.toLocaleTimeString());
var url = "URL OF YOUR SHEET";
var sheetName = "NAME OF YOUR SHEET";
var ss = SpreadsheetApp.openByUrl(url);
var sheet = ss.getSheetByName(sheetName);
var dataRange = sheet.getDataRange();
var formulas = dataRange.getFormulas();
var tempFormulas = [];
for (var row=0; row<formulas.length; row++) {
for (var col=0; col<formulas[0].length; col++) {
// Blank all formulas containing any "import" function
// See https://regex101.com/r/bE7fJ6/2
var re = /.*[^a-z0-9]import(?:xml|data|feed|html|range)\(.*/gi;
if (formulas[row][col].search(re) !== -1 ) {
tempFormulas.push({row:row+1,
col:col+1,
formula:formulas[row][col]});
sheet.getRange(row+1, col+1).setFormula(""); //cleans up the formula
}
}
}
// After a pause, replace the import functions
Utilities.sleep(500);
for (var i=0; i<tempFormulas.length; i++) {
var cell = tempFormulas[i];
sheet.getRange( cell.row, cell.col ).setFormula(cell.formula);
var nowLogger = new Date();
Logger.log("Update import from row " + cell.row + " col " + cell.col + " done at " + nowLogger.toLocaleTimeString());
Utilities.sleep(1000); //adding to try to control the amount of parallel connections from the Sheet
}
// Show Finished time on log
var now = new Date();
Logger.log("Sources from URLs were last updated at " + now.toLocaleTimeString());
// Done refresh; release the lock.
lock.releaseLock();
}
Then I added the Time-Based trigger from my project in https://script.google.com/home/triggers, and that one handles the automatic update execution.
I hope this helps!
It is actually not working. I mean it really does not refresh the cell whereas using a random number it works:
function getData() {
var queryString = Math.random();
var Xpath_1 = "/html/body/text()";
var importXpath_1 = '=IMPORTXML("' + 'http://www.pde-racing.com/trams/tram.php?id=999&valor=1&manega=Entrenament+1&dbTemps=Event999Ex.scdb&dbInscrits=Event999.scdb&name=&_=1616318271525' + '?' + queryString + '";"'+ Xpath_1 + '")';
SpreadsheetApp.getActiveSheet().getRange('B39').setValue(importXpath_1);
}
I have a bounded script to a spreadsheet 'A' that generates a new spreadsheet 'N' for each name I have in a list in spreadsheet 'A'. I would like to add a bounded script to each spreadhsheet 'N' such as:
function onEdit() {
var sh = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var row = sh.getActiveCell().getRowIndex();
var col = sh.getActiveCell().getColumn();
Logger.log(row + " " + col);
if (row == 3 && col == 1)
{
var value = sh.getActiveCell().getValue();
if (value == "I")
{
var today = formatDate(new Date());
var newdate = new Date(today);
newdate.setDate(newdate.getDate() + 15);
var dd = newdate.getDate();
var mm = newdate.getMonth() + 1;
var y = newdate.getFullYear();
var someFormattedDate = mm + '/' + dd + '/' + y;
Logger.log(someFormattedDate);
sh.getRange(row, 3).setValue(someFormattedDate);
}
}
}
function formatDate(date)
{
return (date.getMonth() + 1) + '/' + date.getDate() + '/' + date.getFullYear();
}
I have tried to do that by inserting formulas but the TODAY() function always updates the cell.
Any ideas? Is it possible to add bounded snippet of code to a google spreadsheet created programmatically with google apps scripts?
Thank you for your help.
I found what I was looking for with SriptApp Class with the SpreadsheetTriggerBuilder
How can I change this simple cell formula to a script that will be apply to the sheet :
=if(D3="Devis";B3+8;(if(D3<>"Devis";"")))
Where :
C column contains the formula
"Devis" is a name presents in the D column
B column contains Dates
Thanks a lot
First you need something that will trigger the script. So you'll need something like a simple onEdit() function, or a change trigger.
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('Sheet1');
var valueOfCellEdited = e.value;
Logger.log('valueOfCellEdited: ' + valueOfCellEdited);
var columnOfCellEdited = e.range.getColumn();
Logger.log('columnOfCellEdited: ' + columnOfCellEdited);
var rowOfCellEdited = e.range.getRow();
Logger.log('rowOfCellEdited: ' + rowOfCellEdited);
if (columnOfCellEdited === 3) {
var valueToGet = sh.getRange(rowOfCellEdited, 4);
var dateToGet = "";
if (valueOfCellEdited === "Devis") { //Date to write is already set to an empty string, so no "Else" needed
var dateInColB = sh.getRange(rowOfCellEdited, 2).getValue();
Logger.log('dateInColB: ' + dateInColB);
Logger.log('typeof dateInColB: ' + typeof dateInColB);
dateToGet = new Date(dateInColB);
var dateInMilliseconds = dateToGet.valueOf();
Logger.log('dateInMilliseconds: ' + dateInMilliseconds);
//60 seconds in a minute = 60 * 1000 = 60000 milliseconds in a minute.
//60 minutes in a hour = 60,000 * 60 = 3,600,000
//24 hours in day 3,600,000 * 24 = 86,400,000
var datePlusEight = dateInMilliseconds + (8 * 86400000);
Logger.log('datePlusEight: ' + datePlusEight);
var newDateIs = new Date(datePlusEight);
Logger.log('newDateIs: ' + newDateIs);
Logger.log('newDateIs: ' + newDateIs.toString());
sh.getRange(rowOfCellEdited, 4).setValue(newDateIs);
};
};
};
Comment out the Logger.log() statements for use. To test, edit a cell in column C, with a date right next to it in the same row, but in column B.
In the spreadsheet, under the Tools menu, choose Script Editor, copy the code and paste it into the script editor.