Get details of cells changed from a Google Spreadsheet change notification in a machine readable format - email

If I have a Google Spreadsheet e.g.
https://docs.google.com/spreadsheet/ccc?key=0AjAdgux-AqYvdE01Ni1pSTJuZm5YVkJIbl9hZ21PN2c&usp=sharing
And I have set up notifications on it to email me immediately whenever a cell changes.
And I make a change to that spreadsheet via the spreadsheet API - i.e. not by hand.
Then I get an email like this:
Subject: "Notification Test" was edited recently
See the changes in your Google Document "Notification Test": Click
here
other person made changes from 10/01/2014 12:23 to 12:23 (Greenwich
Mean Time)
Values changed
If I open the 'Click here' link then I get this URL which shows me the cell that has changed in the spreadsheet:
https://docs.google.com/a/DOMAINGOESHERE/spreadsheet/ver?key=tn9EJJrk6KnJrAEFaHI8E3w&t=1389356641198000&pt=1389356621198000&diffWidget=true&s=AJVazbUOm5tHikrxX-bQ0oK_XEapjEUb-g
My question is:
Is there a way to get the information about which cell has changed in a format that I can work with programmatically- e.g. JSON?
I have looked through the Google Spreadsheet API:
https://developers.google.com/google-apps/spreadsheets/
and at the Drive API Revisions:
https://developers.google.com/drive/manage-revisions
I have also tried setting up an onEdit() event using Google Apps Script: https://developers.google.com/apps-script/understanding_triggers
I thought this last approach would be the answer.
The problem with this approach is that whilst onEdit can be used to email details of changes, it appears to only be fired if the spreadsheet is edited by hand whereas mine is being updated programmatically via the spreadsheet API.
Any ideas?

You could build a function that checks for changes. One way to do this is by comparing multiple instances of the same spreadsheet. If there are differences, you could email yourself. Using the time driven trigger, you can check every minute, hour, day, or week (depending on your needs).
var sheet = **whatever**;//The spreadsheet where you will be making changes
var range = **whatever**;//The range that you will be checking for changes
var compSheet = **whatever**;//The sheet that you will compare with for changes
function checkMatch(){
var myCurrent = sheet.getRange(range).getValues();
var myComparison = compSheet.getRange(range).getvalues();
if(myCurrent == myComparison){//Checks to see if there are any differences
for(i=0;i<compSheet.length;++i){ //Since getValues returns a 'multi-dimensional' array, 2 for loops are used to compare each element
for(j=0;j<compSheet[i].length;++i){
if(myCurrent[i][j] != myComparison[i][j]){//Determines if there is a difference;
//***Whatever you want to do with the differences, put them here***
}
}
myEmailer(sheet.getUrl());//Passes the url of sheet to youur emailer function
compSheet.getRange(range).setValues(myCurrent);//Updates compSheet so that next time is can check for the next series of changes
}
}
Then from Resources>Current project's triggers you can set checkMatch to run every minute.
Also check out https://developers.google.com/gdata/samples/spreadsheet_sample for pulling data as json

Related

I got 4 running onedit functions and created a 5th one to trigger an email based on a checkbox, but it does not work

I created a google spreadsheet to track samples from vendors. I added 4 OnEdit functions to send email to two separate email addresses once the sample feedback is provided (approval cell is filled out with "Y" for approved or "N" for not approved. one function for each email address for a total of 4). However, I also tried to create a 5th OnEdit function to trigger an email (recipient based on the row) once the tracking checkbox is clicked. Unfortunately, that 5th OnEdit function is not working properly. Can you please advise how to solve this? Wondering if I should put all of these together in one function (and how, because it's my first time playing around with Apps Script), or if there is something wrong with the coding that is causing this not work properly.
Currently, the email goes out when ANY cells of that specific row are changed, and not just when the checkbox is clicked. Not only that, but the function has a huge error rate and is basically not working at all.
FUNCTION:
function onEdit(e) {
var range = e.range;
var col = range.getColumn();
var row = range.getRow();
var edited_value = e.value;
var ss = e.range.getSheet();
if(col == 5 && edited_value == "TRUE" && ss.getRange("R"+row).getValue() == "")
{
Browser.msgBox("Check If You Have an Email Address in column R ")
}else
{
var to = ss.getRange("R"+row).getValue();
var subject = ss.getRange("G"+row).getValue();
var Email_body = ss.getRange ("D"+row).getValue();
MailApp.sendEmail(to,subject,Email_body)
}
}
The onEdit(e) function is a simple trigger. Simple triggers run in a restricted context where classes such as MailApp and GMailApp are not available, because they require user authorization.
To make it work, you will have to use an installable trigger.
Note that when you have multiple onEdit(e) functions in the same script project,
only one of them will run. Also see onEdit(e) best practices.

Google Apps Script - Email when row in Google sheet is updated

I am a teacher and new to programing script. I have a sheet named 'Scores' in a Google spreadsheet that has a list of emails in column A and an array of data in the following columns. When any data in B:R is changed I would like to automatically send an email to the address listed in column A of the row that changed in this sheet that includes the data in that row and associated column headers.
Example.
Send Email to address in 'A4'
Subject line: Undated Scores
A string of text as a greeting.
Create a table with 'column headers' and 'Row Data'
B1 - B4
C1 - C4
D1 - D4
...to last column
Thanks
You will have to compose the subject and the message with the information found in data. The index for data is one less than the column number. If you wish to learn more about the onedit event object try adding console.log(JSON.stringify(e)) to the second line and it will print in the execution log. I like to use Utilties.formatString() when composing text mixed in with merged data.
//function will only run with you in the correct sheet and you edit any cell from b to r or 2 to 18
function sendEmailWhenBRChanges(e) {
const sh=e.range.getSheet();
const startRow=2;//wherever your data starts
if(sh.getName()=='Your Sheet Name' && e.range.columnStart>1 && e.range.columnStart<19 e.range.rowStart>=startRow) {
let data=sh.getRange(e.range.rowStart,1,1,18).getValues()[0];//data is now in a flattened array
//compose subject and message here if you want html then use the options object
GmailApp.sendEmail(data[0],Subject,Message);
}
}
on edit event object
Note you will have to create an installable trigger because sending email requires permission. You can create the trigger programmatically using ScriptApp.newTrigger() or go to the triggers section of the new editor or the edit menu in the legacy editor and don't forget to put the e in the parameters section of the function declaration.
Also please note that you cannot run this function directly from the script editor because it requires the event object that it gets from the trigger.
I know this is what you asked for but your not going to like it because it will trigger the email to be send whenever to edit any of the columns. You will probably prefer changing it later to accommodate putting a column of checkboxes which can be used as buttons for sending the emails.

Google Sheet notification when a cell in a particular column is edited

I have google sheet where i would like to have an email notification been sent to a specified user whenever a cell in particular column has been edited.
The sheets contains 15 columns and one of the column is for comments and another for email address.
My requirement is whenever any cells in the comment column is edited, I would like to have an email sent to email address mentioned in a different column of the same sheet.
I did went over few of thread and found this thread had similar problem and has been successfully answered with a code. However, when i used this script it gives a a "Cannot read property 'range' of undefined (line 2, file "Code")" message.
I'm a noob to coding and not sure what does that means.
I also tried Magic Cell Notification addon but to no avail.
Any help will be greatly appreciated.
Send Email
You need to create an installable trigger for onMyEdit.
You need to provide the sheet name, the emailColumn, the commentColumn, the startingRow for data, the and the subject.
And please realize that you can't call this function from the script editor as it requires the event block from the onedit trigger.
`
function onMyEdit(e) {
var sh=e.range.getSheet();
if(sh.getName()!='Your Sheet Name')return;
var emailColumn=1;//you have to tell me what column the email is on
var commentColumn=2;//you have to tell me what column the comment is on
var startingRow=2;//you have to tell me what row the data starts on
var subject='You tell me what the subject is';
if(e.range.columnStart==emailColumn && e.range.rowStart>startingRow && e.value) {
GmailApp.sendEmail(sh.getRange(e.range.rowStart,emailColumn).getValue(), subject, sh.getRange(e.range.rowStart,commentColumn));
}
}

updating existing data on google spreadsheet using a form?

I want to build kind of an automatic system to update some race results for a championship. I have an automated spreadsheet were all the results are shown but it takes me a lot to update all of them so I was wondering if it would be possible to make a form in order to update them more easily.
In the form I will enter the driver name and the number o points he won on a race. The championship has 4 races each month so yea, my question is if you guys know a way to update an existing data (stored in a spreadsheet) using a form. Lets say that in the first race, the driver 'X' won 10 points. I will insert this data in a form and then call it from the spreadsheet to show it up, that's right. The problem comes when I want to update the second race results and so on. If the driver 'X' gets on the second race 12 points, is there a way to update the previous 10 points of that driver and put 22 points instead? Or can I add the second race result to the first one automatically? I mean, if I insert on the form the second race results can it look for the driver 'X' entry and add this points to the ones that it previously had. Dunno if it's possible or not.
Maybe I can do it in another way. Any help will be much appreciated!
Thanks.
Maybe I missed something in your question but I don't really understand Harold's answer...
Here is a code that does strictly what you asked for, it counts the total cumulative value of 4 numbers entered in a form and shows it on a Spreadsheet.
I called the 4 questions "race number 1", "race number 2" ... and the result comes on row 2 so you can setup headers.
I striped out any non numeric character so you can type responses more freely, only numbers will be retained.
form here and SS here (raw results in sheet1 and count in Sheet2)
script goes in spreadsheet and is triggered by an onFormSubmit trigger.
function onFormSubmit(e) {
var sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet2');
var responses = []
responses[0] = Number(e.namedValues['race number 1'].toString().replace(/\D/g,''));
responses[1] = Number(e.namedValues['race number 2'].toString().replace(/\D/g,''));
responses[2] = Number(e.namedValues['race number 3'].toString().replace(/\D/g,''));
responses[3] = Number(e.namedValues['race number 4'].toString().replace(/\D/g,''));
var totals = sh.getRange(2,1,1,responses.length).getValues();
for(var n in responses){
totals[0][n]+=responses[n];
}
sh.getRange(2,1,1,responses.length).setValues(totals);
}
edit : I changed the code to allow you to change easily the number of responses... range will update automatically.
EDIT 2 : a version that accepts empty responses using an "if" condition on result:
function onFormSubmit(e) {
var sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet2');
var responses = []
responses[0] = Number((e.namedValues['race number 1']==null ? 0 :e.namedValues['race number 1']).toString().replace(/\D/g,''));
responses[1] = Number((e.namedValues['race number 2']==null ? 0 :e.namedValues['race number 2']).toString().replace(/\D/g,''));
responses[2] = Number((e.namedValues['race number 3']==null ? 0 :e.namedValues['race number 3']).toString().replace(/\D/g,''));
responses[3] = Number((e.namedValues['race number 4']==null ? 0 :e.namedValues['race number 4']).toString().replace(/\D/g,''));
var totals = sh.getRange(2,1,1,responses.length).getValues();
for(var n in responses){
totals[0][n]+=responses[n];
}
sh.getRange(2,1,1,responses.length).setValues(totals);
}
I believe you can found everything you want here.
It's a form url, when you answer this form you'll have the url of the spreadsheet where the data are stored. One of the information stored is the url to modify your response, if you follow the link it will open the form again and update the spreadsheet in consequence. the code to do this trick is in the second sheet of the spreadsheet.
It's a google apps script code that need to be associated within the form and triggered with an onFormSubmit trigger.
It may be too late now. I believe we need a few things (I have not tried it)
A unique key to map each submitted response, such as User's ID or email.
Two Google Forms:
a. To request the unique key
b. To retrieve relevant data with that unique key
Create a pre-filled URL (See http://www.cagrimmett.com/til/2016/07/07/autofill-google-forms.html)
Open the URL from your form (See Google Apps Script to open a URL)

Is there a way to get the original value of a cell in onEdit()?

I'd like to know if there's a way to get the original value of the cell from within the onEdit() event.
For example:
Cell A1's current value is "hello"
I edit A1 it by changing it to "world"
Right now, when I try to get the value from the active range, I'd get "world"
But I would also like to have the possibility to get the original value, i.e. "hello". Is that currently possible?
You can use onEdit(e) and e.oldValue if you're dealing with a single cell. The example below adds the original value to A1 when any cell is edited. 'undefined' if the cell was blank.
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var cell = sheet.getActiveCell();
var origValue = e.oldValue
sheet.getRange('A1').setValue(origValue);
}
The only code that would come close to this is to use an onOpen exit to save
the initial value. The initial value could be saved in several ways. There
are "user properties". I don't know how they work, yet. I am new to this.
You could also use a "hidden" sheet to save the value, in the "onOpen" exit.
There may be other ways, that I don't know about.
The problem becomes more difficult with an increasing number of cells for which
you want to save the original value.
By-the-way, you should understand that the "onOpen" routine fires at the time
the spreadsheet is opened. It so happens, that the end-user also has access and
can change cell values before the onOpen handler finishes its execution. You may
not capture all of your initial values.
One final thing you should know. The onEdit trigger is NOT fired when an UNDO or REDO
event occurs. The cell's contents could change and you will not know it.
I don't know how a validation routine works. If the routine rejects a value, will
the spreadsheet restore the original value? If it does, then this might get around
the onOpen problem. If it only tells the user the value is invalid, it will not be
of much help.
A really round about way that may work, but is very complicated is to save the image
before the spreadsheet closes. You post all the "after" images to a second spreadsheet.
Then in your onEdit handler you look at the corresponding cell in the other spreadsheet.
You then decide to restore the previous image or allow the new image to proceed.
Lastly a wild idea of using a data table in place of the second spreadsheet.
I am just learning about all of these concepts, so don't ask me how to implement them.
I just understand that they MIGHT be possible. For coding and support purposes they
may not be the best options. But since the current script service does not provide
before image access, it is about the best I could do. You have to understand that this
google interface is a client-server application. Your scripts run on the server. The data changes occur in the "clients" (end-users) browser.
One final note: the onEdit trigger does not fire for an UNDO or REDO change to a cell.
So the cell could change and your script is not aware of it.
I don't think that's possible.
I imagine you could get that functionality by having a exact copy of your sheet on a second sheet that updates automatically when your 'onEdit' functions ends.
Until that update, data on the second sheet will have the former value.
A bit tricky but why not ?-)
EDIT : seems to be the 'question of the day', see this post and Henrique Abreu's pertinent comment.
When you change the value of a cell diagrammatically, you can use the setComment method to store the original value as a comment in that cell.
What you basically need to do is to create a shadow sheet (which you can protect and hide or even have it in a totally separate spreadsheet) and use the IMPORTRANGE function to get the values of the original sheet into the shadow sheet (This gives enough delay time to get the old value that was in the cell before it got edited).
=IMPORTRANGE("enter your original sheet's ID","enter the range you wish to get from the sheet")
Please note that using this code, when editing multiple cells at the same time, the function will only work on the first cell.
function onEdit(){
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var originalSheet = sheet.getSheetByName('Original');
var shadowSheet = sheet.getSheetByName('Shadow');
var editedCell = originalSheet.getActiveCell();
var cellColumn = editedCell.getColumn();
var cellRow = editedCell.getRow();
var oldValue = shadowSheet.getRange(cellRow, cellColumn).getValue();
var cellValue = editedCell.getValue();
var editorMail = Session.getActiveUser().getEmail(); \\getting the editor email
if ("The condition you want to meet for the onEdit function to activate"){
editedCell.setNote(editorMail + "\n" + new Date + "\n" + oldValue + " -> " + cellValue);
}
}