I have script for Google Sheets that I collected on interwebs and got some help here. No I have 2 onEdit in conflict. I overcome that by creating script Trigger for onEdit2. It works but I don't think it is the best solution. Could you help get those two separated onEdit with if functions into one, please?
//Dependent Dropdown list
function onEdit(e){ // Function that runs when we edit a value in the table.
masterSelector(master1,master2,master3,master4);
var activeCell = e.range; // It returns the coordinate of the cell that we just edited.
var val = activeCell.getValue(); // Returns the value entered in the column we just edited.
var r = activeCell.getRow(); // returns the row number of the cell we edit.
var c = activeCell.getColumn(); // returns the column number of the cell we edit.
var wsName = activeCell.getSheet().getName();
if (wsName === masterWsName && c === firstLevelColumn && r > masterNumberOfHeaderRows) { // the if delimits the section sensitive to modification and action of the onEdit.
applyFirstLevelValidation(val,r);
} else if (wsName === masterWsName && c === secondLevelColumn && r > masterNumberOfHeaderRows){
applySecondLevelValidation(val,r);
}
} // end of onEdit
// addRow by checkboxes
function onEdit2(e) {
masterSelector(master1,master2,master3,master4);
//IF the cell that was edited was in column 4 = D and therefore a checkbox AND if the cell edited was checked (not unchecked):
if (e.range.columnStart === 4 && e.range.getValue() === true) {
var sheet = SpreadsheetApp.getActiveSheet(),
row = sheet.getActiveCell()
.getRow(),
//(active row, from column, numRows, numColumns)
rangeToCopy = sheet.getRange(row, 1, 1, 30);
sheet.insertRowAfter(row);
rangeToCopy.copyTo(sheet.getRange(row + 1, 1));
//Reset checked boxes in column 4
sheet.getRange(row,4,2,1).setValue(false);
}
}
Whole script is here, if needed.
A script cannot contain two functions with the same name. Rename your first onEdit function to onEdit1 (actually it will be better to assign a descriptive name) and the second function as onEdit2, then put them both in one function named onEdit and pass the parameter e to both of them:
function onEdit(e){
onEdit1(e);
onEdit2(e);
}
Related:
Two OnEdit functions not working together
Best Practices for Multiple OnEdit Functions
How to run multiple onEdit functions in the same google script (google sheets)?
Bracketing multiple onEdit functions
Related
I have a google sheet for my business that creates a prep list for events.
After tackling an automatic clearing issue for over 25 hours(serious);I got some help from your fantastic community, modified the code to work, see below: (If Fx is empty, clear the cell to the left):
function cgar() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('auto');
var values = sheet.getRange("F11:F20").getValues();
var ranges = values.reduce((ar, [f], i) => {
if (f == "") ar.push("E" + (i + 11));
return ar;
}, []);
sheet.getRangeList(ranges).clearContent();
}
Is there a way for me to skip a specific cell, either based on location or value = "Garnish"?
If I change the code to take the whole range of "F5:F24" instead of the individual groups "F11:F20" it clears the header rows of the merged cell E10:F10 & E21:F21.
2.
var syr1 = ("D6")
var syr2 = ("D7")
var syr1isblank = SpreadsheetApp.getActiveSheet().getRange(syr1).isBlank()
var syr2isblank = SpreadsheetApp.getActiveSheet().getRange(syr2).isBlank()
if (syr1isblank == true) {
SpreadsheetApp.getActiveSheet().getRange("C16:D23").moveTo(sheet.getRange(syr1))
}
else if (syr2isblank == true && syr1isblank == false ) {
SpreadsheetApp.getActiveSheet().getRange("C16:D23").moveTo(sheet.getRange(syr2))
}
I have been using if and else if to move the group ranges that are below the cleared ones to the newly created space above, but it's clunky:
Is there a way that I can also modify this to scan for empty range, I think the process would be like this:
first empty cell spotted
Mark the cell to the left to a variable?
Scan the range f.x. E11:F20 for lastRow after it has been cleared by
the function above
Move the range to the variable marked cell above.
Image of the sheet in question: https://i.stack.imgur.com/ND2DC.png
I would greatly appreciate any assistance, thank you.
Background
I'm trying to write a script that will check two different cells to see if the checkbox has been checked. So, it would first check one cell (AL4) to see if the value is true, and then a different cell (Q4) to see if the value is true. If the value is true, then it will run a different function.
Both of the cells I am checking are in the same sheet, "Dashboard", so it doesn't have to find different sheet names. I've been different ways to get it to check the different cells by using .getcolumn and .getrow commands but I've had some trouble with that too. I've also run into some problems where it will ignore the if statement and run the function on every edit and not just when the two cells are checked.
How can I get an if and else if statement to work in the same onEdit function?
Current Script
function onEdit(e) {
const sheetName = "Dashboard"
var range = e.range;
if (range.getSheet().getSheetName() != sheetName || range.getA1Notation() != "AL4" || !range.isChecked()) {
return;
RefreshCheckmark2();
} else if (range.getSheet().getSheetName() != sheetName || range.getA1Notation() != "Q4" || !range.isChecked()) {
return;
InverseYieldRefresh();
}
}
Also, the above code is completely functional if you remove the else if statement and the brackets after the first If statement.
Ideally I would like to be able to add more else if statements so that it can check more cells because I like to have checkboxes run commands and if my understanding is correct, you can't have more than one onEdit function in a single spreadsheet.
Thank you for any insight!
I think what you are trying to do right now is to check if it is not AL4 or Q4 rather than checking if it is AL4 or Q4. Also, in your current code, you use return; before running any function so it would really skip running the function even if it goes in your if-statement. You can try this code below:
function onEdit(e) {
const sheetName = "Dashboard"
var range = e.range;
if (range.getSheet().getSheetName() == sheetName && range.getA1Notation() == "AL4" && range.isChecked()) {
RefreshCheckmark2();
}
else if (range.getSheet().getSheetName() == sheetName && range.getA1Notation() == "Q4" && range.isChecked()) {
InverseYieldRefresh();
}
}
I only changed the conditions to make it check if it is AL4 or Q4 then removed the return; to make the functions inside the if-statements run once conditions are satisfied.
Let me know if this solves your problem.
I have this code that works for restricting data in all cells to 5 characters. I like that it shortens the data back to 5 characters if you put more that 5 in.
function onEdit(e) {
var limit = 5;
if(e.value.length > limit) {
e.range.setValue(e.value.substring(0, limit));
}
}
If I click the Run button it does show an error.
TypeError: Cannot read property "value" from undefined. (line 3, file "Code")
But it seems to work fine.
I would like to have the script work on a certain range of cells only. Say C3:C100. So I had to reference the sheet and cells, so changed the script to this.
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("C3:C100");
var limit = 5;
if(e.value.length > limit) {
e.range.setValue(e.value.substring(0, limit));
}
}
This script does not work on any of the cells, let alone the selected ones.
Can anyone help me please? What have I missed?
A onEdit script is triggered by any edit in a spreadsheet. It 'captures' the edit in what is called an event object. The event object can be passed as an argument to the function. In that case you'll see onEdit(e) or onEdit(event) as the function name. That object has some parameters attached to it: e.range (the edited range), e.value (the value of the edit), e.source (the spreadsheet where the edit is done), etc..
Now when you run the function from the script editor (by clicking the play button) no actual edit is taking place in the spreadsheet. Therefore no event object will be created and all parameters of that object will be undefined.
If you want to 'limit' the scope of the script to the range C3:C100, you can change the script as follows:
function onEdit(e) {
var limit = 5;
if (e.value.length > limit && e.range.rowStart > 2 && e.range.rowStart < 101 && e.range.columnStart == 3) {
e.range.setValue(e.value.substring(0, limit));
}
}
Note that currently the script will work on every tab/sheet inside your spreadsheet/workbook. If you want to further limit the script to work on only one sheet (e.g. 'Sheet1'), you can try something like
function onEdit(e) {
var limit = 5;
if (e.source.getActiveSheet().getName() == 'Sheet1' && e.value.length > limit && e.range.rowStart > 2 && e.range.rowStart < 101 && e.range.columnStart == 3) {
e.range.setValue(e.value.substring(0, limit));
}
}
I have a Google Sheet I'm working on.
I have a script that fills in the column B with a timestamp when I update column A on the first tab. However I need it to do the same on the second tab, but I can't get it to work there. What do I need to change?
The current script I'm using is:
function onEdit(e) {
var sheetToWatch= 'Wrong Grading',
columnToWatch = 1,
columnToStamp = 2; //change all of these to your needs
if (e.range.columnStart !== columnToWatch
|| e.source.getActiveSheet().getName() !== sheetToWatch
|| !e.value)
return;
e.source.getActiveSheet()
.getRange(e.range.rowStart, columnToStamp)
.setValue(new Date());
}
For future readers, the code snippet in Paul's question is derived from code on the Google Docs help forum, which includes a detailed line-by-line explanation.
The function uses the variable sheetToWatch to identify one sheet (aka "tab") that the onEdit() function cares about. That is validated by this comparison:
|| e.source.getActiveSheet().getName() !== sheetToWatch
...and if the source of the current trigger event is not matched, the function exits without doing anything.
What do I need to change? If you want this function to work on all sheets in the Spreadsheet, then you can just eliminate this check altogether:
function onEdit(e) {
var columnToWatch = 1,
columnToStamp = 2; //change all of these to your needs
if (e.range.columnStart !== columnToWatch
|| !e.value)
return;
e.source.getActiveSheet()
.getRange(e.range.rowStart, columnToStamp)
.setValue(new Date());
}
If you want to have the onEdit() operate on a set of sheets, then you can change that above comparison to check if the current sheet's name is found in an array of sheet names. That will change the comparison to this:
|| sheetsToWatch.indexOf( e.source.getActiveSheet().getName() ) === -1
You can learn more about the indexOf() method here. What it's doing though, is getting the name of the event trigger source sheet, finding it in the sheetsToWatch array, and returning the found index. If the sheet does not appear in the array, indexOf() returns -1.
The resulting function is:
function onEdit(e) {
var sheetsToWatch= ['Wrong Grading',
'Something Else'],
columnToWatch = 1,
columnToStamp = 2; //change all of these to your needs
if (e.range.columnStart !== columnToWatch
|| sheetsToWatch.indexOf( e.source.getActiveSheet().getName() ) === -1
|| !e.value)
return;
e.source.getActiveSheet()
.getRange(e.range.rowStart, columnToStamp)
.setValue(new Date());
}
function onEdit() {
var openRequests = SpreadsheetApp.getActive().getSheetByName('Open Requests');
var lastRowOpen = openRequests.getLastRow();
var closedRequests = SpreadsheetApp.getActive().getSheetByName('Closed Requests');
var lastRowClose = closedRequests.getLastRow();
var closed = openRequests.getRange(2,8,lastRowOpen,1).getValues();
for (var i = 0; i < lastRowOpen; i++)
{
if (closed[i][0].toString() == 'Yes')
{
var line = i+2;
if (closedRequests.getLastRow() == 1)
{
openRequests.getRange(line,1,1,9).copyTo(closedRequests.getRange(2,1,1,9));
closedRequests.getRange(2,9,1,1).setValue(new Date());
openRequests.deleteRow(line);
}
else
{
openRequests.getRange(line,1,1,9).copyTo(closedRequests.getRange(lastRowClose+1,1,1,9));
closedRequests.getRange(lastRowClose+1,9,1,1).setValue(new Date());
openRequests.deleteRow(line);
}
}
}
}
I have set up a trigger to run onEdit. What it does is check a column called Closed to see if it says Yes. The Closed column has a data validation drop down menu with the value Yes in it.
So when I click on the drop down menu and select Yes, it should copy the whole row to another sheet called Closed Requests then delete that row from the spreadsheet called Open Requests.
The issue I am having is that about 50% of the time, it deletes the row I select Yes to but it ALSO deletes the row below it (and about 50% of the time when this happens, only some times does the second deleted row show up in Closed Requests, the other times the whole row just disappears forever unless I undo).
From what I can tell, the deleteRow() function deletes the whole row and shifts all rows below it up a row to fill in the blank. So the row below the one meant to be deleted gets shifted up to the same row and also gets deleted. I don't know why the function is getting called twice though.
I tried adding some delays but it does not seem to be working.
function onEdit(e) {
var eRange = e.source.getActiveRange();
var openRequests = SpreadsheetApp.getActive().getSheetByName('Open Requests');
var closedRequests = SpreadsheetApp.getActive().getSheetByName('Closed Requests');
var nextRowClose = (closedRequests.getLastRow()?closedRequests.getLastRow()+1:2);
if(eRange.getSheet().getName()=="Open Requests" && eRange.getColumn()==8 && eRange.getValue()=="Yes") {
openRequests.getRange(eRange.getRow(), 1, 1, 9)
.copyTo(closedRequests.getRange(nextRowClose, 1));
closedRequests.getRange(nextRowClose, 9).setValue(new Date());
openRequests.deleteRow(eRange.getRow());
}
}
Could try iterating backwards as it was mentioned to me. Throwing in a SpreadsheetApp.flush() after the delete may help too.
#Jack, I have a similar use case to you. My code is the backwards one that BryanP discusses. My code is more or less here: "Batch removal of task items where status = 'Done'". It is because I remove them in a batch that I use the backwards method whereby the removal of a row with a higher row number will not disturb the row number of any rows with a lower row number.
But you are not removing rows in batch mode, so maybe backwards shouldn't make a difference (perhaps unless two users use the sheet and delete at the same time?)
So thought I'd try your code. I shoe horned your code into the onedit() function that is already present on my spreadsheet (which is used to colour rows red after a period of inactivity, and to put in a timestamp once the task is actually attended).
Then to test I used a copy of one of our spreadsheet which had already 50 rows/tasks in it. I manually filled in the required cells in a row and selected Done from the cell with the dropdown (I changed your code to expect "Done" rather than "Yes"). I repeated this for 20 rows.
The Result: Your code succeeded as you had expected it to every one of the 20 times ... no double deletes, always copying data across. It worked for me without introducing delays nor SpreadsheetApp.flush().
I don't have a solid suggestion I am afraid. In passing I mention the known fault where the spreadsheet has not properly refreshed itself, so does not show the deleted rows; this can be checked for by manually refreshing the spreadsheet when this fault appears. (However, the indications of this fault does not seem to logically fit with your report about the double copying over of two sequential rows.)
Thread lock? Sounds like a thread lock problem. Try:
function onEdit() {
// ****** add lock code
var lock = LockService.getPublicLock();
var hasMutex = lock.tryLock(100);
if(hasMutex==false) {
return;
}
// *** end
var openRequests = SpreadsheetApp.getActive().getSheetByName('Open Requests');
var lastRowOpen = openRequests.getLastRow();
var closedRequests = SpreadsheetApp.getActive().getSheetByName('Closed Requests');
var lastRowClose = closedRequests.getLastRow();
var closed = openRequests.getRange(2,8,lastRowOpen,1).getValues();
for (var i = 0; i < lastRowOpen; i++)
{
if (closed[i][0].toString() == 'Yes')
{
var line = i+2;
if (closedRequests.getLastRow() == 1)
{
openRequests.getRange(line,1,1,9).copyTo(closedRequests.getRange(2,1,1,9));
closedRequests.getRange(2,9,1,1).setValue(new Date());
openRequests.deleteRow(line);
}
else
{
openRequests.getRange(line,1,1,9).copyTo(closedRequests.getRange(lastRowClose+1,1,1,9));
closedRequests.getRange(lastRowClose+1,9,1,1).setValue(new Date());
openRequests.deleteRow(line);
}
}
}
// ****** add lock code
lock.releaseLock();
// *** end
}
Questions:
1) how many people were using the spreadsheet at the time.
2) how often does it happen.