I'm struggling a bit with Google App Script. I'm looking to write a simple script that checks when a user changes a colour, and, if it's wrong, corrects it.
Here's my code so far:
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var r = sheet.getActiveRange();
if (r.getBackground() != '#D9EAD3') {
var ui = SpreadsheetApp.getUi();
ui.alert('Wrong colour!');
r.setBackground('#D9EAD3');
}
}
It almost works...However.
it only works when text is changed, not colour (there doesn't appear to be an onFormat option)
it appears to change cell a1 every time.
Can anyone help? Thanks.
EDIT
That's a great help - thanks, Sandy. My code now looks like this:
function formatWasChanged(e) {
var r = e.range;
if (r.getBackground() != '#D9EAD3' && r.getBackground() != '#FFFFFF') {
var ui = SpreadsheetApp.getUi();
ui.alert('Wrong colour!');
r.setBackground('#D9EAD3');
}
}
I've also defined this as a project trigger, called on change. Unfortunately, it's still not running when I test it on my spreadsheet - var r = e.range; appears to fail when I run it in the console.
You can also use an installable "Change" trigger. That can check for:
Quote from Google Documentation:
e.changeType - The type of change (EDIT, INSERT_ROW, INSERT_COLUMN, REMOVE_ROW, REMOVE_COLUMN, INSERT_GRID, REMOVE_GRID, FORMAT, or OTHER)
Note the FORMAT type of change.
Google Documentation - Change Trigger
You must click the 'Resources' menu, and choose 'Current Project Triggers' in the code editor.
function formatWasChanged(e) {
Logger.log('e.changeType: ' + e.changeType);
if (e.changeType === 'FORMAT') {
Logger.log('Its a FORMAT changeType: ');
};
};
If you do stay with the onEdit() trigger,
Don't use:
var r = sheet.getActiveRange();
To get the range that was edited, use:
var r = e.range;
Google documentation:
e.range - A Range object, representing the cell or range of cells that were edited
Related
I am a newbie and have been using a simple App Script to send out emails with triggers onEdit and onChange. However, my Worksheet has over ten sheets and any edits/changes (done by me or by computations) in any of the sheets sends out an email, causing unintended spam! To avoid this, if I could use some code that sends the email based only on ANY CHANGE to a specific cell's value, in a specific sheet, my problem would be solved. My outgoing email message is short and the whole message is in just ONE cell (C2). If I can add a line of code which monitors for ANY change in that cell C2, and sends out an email if there is a change, that's it! I'd be done. My Script is as follows:
function sendEmail(){
var ss = SpreadsheetApp.getActiveSpreadsheet()
var sheet1=ss.getSheetByName('Email');
var emailAddress = sheet1.getRange(2,1).getValue();
var subject = sheet1.getRange(2,2).getValue();
var message = sheet1.getRange(2,3).getValue();
MailApp.sendEmail(emailAddress, subject, message);
}
Answer:
You can do this with an onEdit() and a conditional.
Code Example:
function onEdit(e) {
const specificSheet = "Email" // for example
const specificCell = "C2" // for example
let sheetCheck = (e.range.getSheet().getName() == specificSheet)
let cellCheck = (e.range.getA1Notation() == specificCell)
if (!(sheetCheck && cellCheck)) {
return
}
else {
sendEmail()
}
}
Rundown of this function:
Defines the sheet and A1 notation of the specific cell to check
Gets the Sheet and the A1 notation of the cell which was just edited
Returns if either the Sheet or the Cell are not the defined specific cell (using De Morgan's law)
Runs sendEmail() if the cell and Sheet are correct
References:
Event Objects | Apps Script | Google Developers
Simple Triggers | Apps Script | Google Developers
De Morgan's laws - Wikipedia
Working from the answer, this is how it ended up for me since I had several ranges to check:
function onEdit(e){
if(wasEdited(e, range1)){ // e.g. range1 = "Sheet1!A5"
// handle range1 change
}
if(wasEdited(e, range2)){ // e.g. range2 = "Sheet1!A7"
// handle range2 change
}
}
function wasEdited(e, range){
let tab = getTabFromA1Range(range)
let cell = getRangeFromA1Range(range)
return e.range.getSheet().getName() == tab && e.range.getA1Notation() == cell
}
function getTabFromA1Range(a1Range){
return a1Range.substring(0, a1Range.indexOf("!"))
}
function getRangeFromA1Range(a1Range){
return a1Range.substring(a1Range.indexOf("!")+1)
}
On further research, the following solution seems to work the best:
function sendEmail(){
Utilities.sleep(30000);
var ss=SpreadsheetApp.getActiveSpreadsheet();
var data=ss.getActiveSheet().getActiveCell().getA1Notation();
var sheetname = ss.getActiveSheet().getName();
var sheet1=ss.getSheetByName('Email');
var emailAddress = sheet1.getRange(2,1).getValue();
var subject = sheet1.getRange(2,2).getValue();
var message = sheet1.getRange(2,3).getValue();
if(data.indexOf('A:C')!=-1.23456789) {
MailApp.sendEmail(emailAddress, subject, message);
}
};
The key seems to be the "if statement" on line 10. Please note the time delay of half a minute I added to the script. This is because without it, on the trigger activating, the previous email was going out instead of the current one. Obviously my app has a slight delay in syncing and the trigger fired before all the current data got populated in the relevant cell!
I have a sheet where when I change a specific cell to "YES", I need a template sheet to be copied to a new version and named as per the value of a cell on the current row.
I'm having trouble working out how to get the value of the first cell in the row selected. This is what I have so far (I know this is wrong):
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var currentCell = sheet.getCurrentCell();
if (currentCell = "YES")
{
SpreadsheetApp.getActiveSpreadsheet().toast("New change control sheet added to workbook.","Change Control",15);
var sourceRow = ss.getActiveRange().getRowIndex();
var tabName = ss.getRange(cell,1).getValues();
ss.getSheetByName("CCTemplate").showSheet()
.activate();
ss.setActiveSheet(ss.getSheetByName('CCTemplate'), true);
ss.duplicateActiveSheet();
ss.setActiveSheet(ss.getSheetByName('CCTemplate'), true);
ss.getActiveSheet().hideSheet();
ss.setActiveSheet(ss.getSheetByName('Copy of CCTemplate'), true);
ss.getActiveSheet().setName("CC" & tabName);
}
}
Any ideas?
function onEdit(e) {
var sh=e.range.getSheet();
if(sh.getName()=='Your Sheet Name' && e.value=="YES") {
e.source.toast="New change control sheet added to workbook.","Change Control",15);
var tabName=sh.getRange(e.range.rowStart,1).getValue();
var tsh=e.source.getSheetByName('CCTemplate');
var csh=tsh.copyTo(e.source);
csh.setName('CC'+tabName);
}
}
You should avoid using activate in your scripts especially in simple triggers where you have to finish in 30 seconds. I think this code does the same thing that you intended for your code. One significant difference is that I use the information that comes in the event object that comes with the trigger. You should add the code Logger.log(JSON.stringify(e)) and then look at the logs you will see that there is a lot of information available to you which removes the need to run extra functions to get things like a spreadsheet.
Use event objects
onEdit offers among others the event objects range and value which are helpful to retrieve the range that has been edited and its value.
Also
When you want to a cell and compare it against a value, like in if (currentCell = "YES") - you need to retrive its value (either currentCell.getValue() or just event.value) and you need to use == instead of = for comparison.
Be careful with getValues() vs getValue(). The former gives you a 2D array and is not necessary if you want to retrieve the value of a single cell.
There is no need to set your sheet to active in order to change its name.
You can rewrite your code as following:
function onEdit(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var currentCell = event.range;
var value = event.value;
if (value == "YES")
{
...
var sourceRow = range.getRowIndex();
var tabName = ss.getRange(sourceRow, 1).getValue();
...
ss.getSheetByName('Copy of CCTemplate').setName("CC" + tabName);
}
}
Having issues getting the following to work.
The intent was on edit to push information from the specific cell or cells of active sheet to specific cells on a separate worksheet.
Note: I am new to google sheets
function onEdit(e) {
var source = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Working");
var cell = source .getActiveCell();
if (cell.getRow() == 9 && cell.getColumn() == 2) {
var target = DriveApp.getFileById("1biaIVlafaNQTHjtR8ctASCpDmC2O1wwfJfAUCmzIztI")
.getSheetByName("Master_Sheet");
target.getRange("A1").setValue(cell.getValue());
}
}
The reason it does not work is because you are using onEdit(). This is a simple trigger that will fire off whenever you edit the sheet. Since simple triggers cannot perform operations that require authorization you are limited to working only in the Spreadsheet and cannot access any other files.
Read up on restrictions here
I am now able to push information to the target sheet using the following code.
function myFunction() {
var sourceSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Source");
var sourceData = sourceSheet.getRange("A3:C3").getValues();
sourceData.splice(0,1); // Remove header
var targetSS = SpreadsheetApp.openById("1pyJzZ86WDh2FNXFufUAt2SkAUod32i7AzvG0EKmnvEU").getSheetByName("Destination");
var targetRangeTop = targetSS.getLastRow(); // Get # rows currently in target
targetSS.getRange(targetRangeTop+1,1,sourceData.length,sourceData[0].length).setValues(sourceData);
};
I have a spreadsheet-bound script that is invoked by clicking an image in the spreadsheet. I've found that the script can be blocked if a cell that it needs to modify is active or being edited by the user.
The code could be anything, even as something as simple as this:
function newf() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var range = sheet.getRange("R4");
range.clear();
}
When the cell R4 is open, the function will ignore the cell.
How do I deselect an open cell when clicking on an image to run a script?
The easy (manual) solution would be to click off the open cell prior to clicking on the image, but I am trying to make the script fool-proof and this can be a costly concern. Is there any way that the function can forcefully deselect an active cell?
Edit: I do not mean active cell. Definitely "open cell." Here are pictures of the error.
Open cell with data being entered
Clicked on Picture, program ran
You can use Sheet.setActive() to move the attached user's cursor to a different location in the spreadsheet. You need to remember that there is a relationship between the User Interface and scripts invoked from it, which is what allows the script to determine and change the currently active cell. This does not extend to the multiple user case, though - if User1 is editing the guarded cell when User2 clicks to run the script, the script will have no idea about it.
function newf() {
var safePlace = "A1"; // Some safe cell
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var activeCell = ss.getActiveCell();
if (activeCell.getSheet() == sheet && activeCell.getA1Notation() == "R4")
sheet.setActiveSelection(safePlace);
var range = sheet.getRange("R4");
var activeCell = ss.getActiveCell();
if (activeCell.getA1Notation() == range.getA1Notation() && activeCell.getSheet().getName() == range.getSheet().getName()) {
// Move user from target cell
sheet.setActiveSelection(safePlace);
range.clear();
}
I had the same problems, both the initial one and with the solution proposed by Mogsdad.
I resolved it the following way:
Before running any of the script, the first three lines get a different sheet opened and the spreadsheet gets flushed:
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('DIFFERENT SHEET NAME'), true);
SpreadsheetApp.flush();
The rest of the script follows.
Then at the end of the script, I reopen the initial sheet with a simple:
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('ORIGINAL SHEET NAME'), true);
Another "solution" to this is not to use a button, but use a check mark as if it we a button, and use the onEdit function.
function onEdit(e)
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
checkbox1 = ss.getRange("saveCheckbox_001");
if (checkbox1.isChecked())
{
submit1(); //do all the fun stuff associated with this checkbox
checkbox1.uncheck();
}
//more checkbox handling and calling other functions can go here if you need more than one "button"
}
I just wondered why clearContent() function does not work very well in new google spreadsheet!
function onEdit(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var range = sheet.getRange("A:L");
var cell = ss.getActiveSelection();
var row = cell.getRow();
var column = cell.getColumn();
var range2 = sheet.getRange(row,column);
var response = Browser.msgBox('Did you confirm it?', Browser.Buttons.YES_NO);
if(response == 'yes'){Browser.msgBox('Thank you!', Browser.Buttons.OK)}
else{range2.clearContent()}
}
When I used old google spreadsheet, it worked.
But I'm using new google spreadsheet. It doesn't work.
getRange('A1:A2').clearContent(); is works.
But getRange(1,2).clearContent(); not work, even though it did work in old google spreadsheet!
Please do not tell me use old google spreadsheet.
Tell me how!!
I don't even see a .getActiveSelection() method in the ss class.
Don't see what you're doing with var range either.
We can just use the passed ss object e to get the range, then clear it when needed.
function onEdit(e) {
var range = e.range;
var response = Browser.msgBox('Did you confirm it?', Browser.Buttons.YES_NO);
if ( response == 'yes' ) {
Browser.msgBox('Thank you!', Browser.Buttons.OK);
}
else {
range.clearContent();
}
}
Thank you for your kind answer.
But I found what the problem is.
In the new google spreadsheet function clearContent() doesn't wotk because of 'data validation'!
So I set remove data validation function and clearcontent, finally, set same data validation in active cell.
It works!
Remove data validation.
Clearcontent.
Set data validation.
If anyone has same problem, try it.