Re-triggering onEdit for each row when pasting multiple rows - google-apps-script

I'll start by saying that I'm a complete novice but I spent a few hours manipulating a bit of code I found online to fit a sheet I'm designing. I'm attempting to create a script that will generate a dynamic dropdown to the right of a cell when a value is selected from an existing dropdown.
function onEdit(event){
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var datass = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Lists")
var r = event.range;
if(r.getColumn() == 3 && r.getRow() > 1 && ss.getName() == "Main"){
r.offset(0, 1).clearContent().clearDataValidations();
var types = datass.getRange(1, 1, 1, datass.getLastColumn()).getValues();
var typeIndex = types[0].indexOf(r.getValue()) + 1;
if(typeIndex != 0) {
var validationRange = datass.getRange(2, typeIndex, datass.getLastRow());
var validationRule = SpreadsheetApp.newDataValidation().requireValueInRange(validationRange).build();
r.offset(0, 1).setDataValidation(validationRule);
}
}
}
Here's my process so far:
I created the initial script with var r = ss.getActiveCell(), which worked for a single cell at a time, but I need it to work when multiple rows are pasted. Version 1
I edited to var r = event.range, which successfully applied the script to all rows that were pasted. However, the script only generated the list for the top cell that I pasted and created that same list for all pasted cells. Version 2
What I need is for onEdit to essentially re-trigger for each row when data is pasted in. Goal Version

Rather than re-triggering onEdit make it to be able to handle both cases. For this, you could take advantage of Range.getRows(), Range.getColumns() as well of edit event object properties like (in this answer e is the event object):
e.range.rowStart
e.range.rowEnd
e.range.columnStart
e.range.columnEnd
If a single cell was edited, rowStart and rowEnd will have the same value, the same for columnStart and columnEnd.
i.e. you might replace
r.offset(0, 1).clearContent().clearDataValidations();
by (remember, I'm using e instead of event for the event object)
r.offset(0, 1, e.range.getRows()).clearContent().clearDataValidations();
Related
Google script onEdit in all pasted cells
How to customize onEdit() script to work on all rows modified when a list of values is pasted from the clipboard?

Related

OnEdit trigger fired simultaneously causing wrong calculation of last row

I'm new to Google App Script and trying to create a queue-like spreadsheet.
The current design is that: in sheet A, there's a column of checkboxs, and if any one of them is checked, the entire row would be moved to the end of another sheet, say sheet B.
The way I calculate the end of the sheet B is as followed:
(I read from a different stackoverflow post)
var Avals = dest.getRange("B:B").getValues();
var Alast = Avals.filter(String).length;
var lastRow = String(Alast + 1);
The issue I'm having here is that when I check two checkboxes quickly one by one, they are mapped to the same lastRow in sheet B, because the second checkbox triggers the onEdit trigger before the first one has moved the entire row to the bottom of the sheet B.
Does anyone know how to solve this issue?
Right now, the workaround is since it's a shared google sheet, I asked my colleagues not to click the checkbox column at the same time.
This seems to be working for what little testing I've done
function onEdit(e) {
LockService.getScriptLock().tryLock(2000)
const sh = e.range.getSheet();
if(sh.getName() == "Sheet0" && e.range.columnStart == 1 && e.range.rowStart > 1 && e.value == "TRUE") {
let tsh = e.source.getSheetByName("Sheet1");
let vs = sh.getRange(e.range.rowStart, 1, 1,sh.getLastColumn()).getValues();
tsh.getRange(tsh.getLastRow() + 1, 1 , vs.length, vs[0].length).setValues(vs);
}
LockService.getScriptLock().releaseLock();
}

I am wanting to run a function in script apps that automatically looks for a word in a column and automatically archives that row to another tab

I want to automatically have a row archive to another tab if column D contains the word Deprovisioned. I am not manually editing the cell, I am copy and pasting from a CSV export so the columns come pre-filled with information. I tried the below script, but received an error saying undefined. I am not sure if this has something to do with the fact that I am not manually editing anything on the sheet?
Below is the error I get
var s = event.source.getActiveSheet(); undefined.
Below is the script I am using
function onEDIT(event) {
// assumes source data in sheet named Needed
// target sheet of move to named Acquired
// test column with yes/no is col 4 or D
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "Copy of Zoominfo Usage" && r.getColumn() == 4 &&
r.getValue() == "DEPROVISIONED") {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("ZOOMINFO ARCHIVE");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).moveTo(target);
s.deleteRow(row);
}
}
function onEdit(e) {
const sh = e.range.getSheet();
if (sh.getName() == "Copy of Zoominfo Usage" && e.range.columnStart == 4 && e.value == "DEPROVISIONED") {
let tsh = e.source.getSheetByName("ZOOMINFO ARCHIVE");
let tgt = tsh.getRange(tsh.getLastRow() + 1, 1);
sh.getRange(e.range.rowStart, 1, 1, sh.getLastColumn()).moveTo(tgt);
sh.deleteRow(e.range.rowStart);
}
}
If you are trying to use simple edit trigger instead of onEDIT you should use onEdit.
An edit trigger, simple or installable will be triggered only when a user uses the Google Sheets UI to edit a cell or range. A paste will trigger the script but the script is not prepared to manage pasting multiple cells
r.getValue() returns only the value of top-left cell
r.getRow() returns only the row of the top row.
It might work when pasting a single row.
It will not work when calling the script from the script editor, as the function requires the event parameter, which is provide automatically when the function is called by the trigger but not when it's called form the script editor.
Please bear in in mind that the active sheet and active range will be the range that has being edited by the user using the Google Sheets UI. When running the function from the script editor, if the Google Apps Script project is opened from the bounded spreadsheet they will be the sheet / range that are active for the user running the function, but if Google Apps Script project were opened from other means like the https://script.google.com then the the methods calling the active sheet / range might return the first sheet / first cell or throw an error.
References
https://developers.google.com/apps-script/guides/triggers
Related
Is it possible to trigger onEdit function on formula
How can I test a trigger function in GAS?

Copy row to another tab, getFormulas, clearContent, setFormulas

I'm trying to make google sheets automatically copy a row and paste it to the bottom of another tab when Completed is selected from a dropdown in the 8th column. Then I want it to grab the formulas from that row, clear the row, and then replace the formulas. The code works as far as copying and pasting to the other tab but I can't get it to grab the formulas, clear the row and then replace the formulas. I can't even get it to clear the row. The code is correctly executing everything but the last four lines.
This isn't my code. I got it from a website and modified it to fit my sheet. I have a very basic understanding of javascript. I need the original row to remain with just the formulas populated so that it can be reused. I'm using copyTo instead of moveTo because the row is being referenced upstream by another sheet and moveTo causes the formulas on the upstream spreadsheet to auto update to the new location of the data instead of continuing to reference the original row. Thank you for any help.
function myStorage() {
// moves a row from a sheet to another when a value is entered in a column
var sheetNameToWatch = "In Progress";
var columnNumberToWatch = 8;
var valueToWatch = "Completed";
var sheetNameToMoveTheRowTo = "Storage";
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveCell();
if (sheet.getName() == sheetNameToWatch && range.getColumn() == columnNumberToWatch && range.getValue() == valueToWatch) {
var targetSheet = ss.getSheetByName(sheetNameToMoveTheRowTo);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sheet.getRange(range.getRow(), 1, 1, sheet.getLastColumn()).copyTo(targetRange);
// this is where it stops working. I'm not sure how to grab just the row of the active cell and do the following
var clearRange = sheet.getRange(range.getRow());
clearRange.getFormulas();
clearRange.clearContent();
clearRange.setFormulas();
}
}
Try this:
function CallItSomethingElse(e) {
var sh=e.range.getSheet();
if (sh.getName()=="In Progress" && e.range.columnStart==8 && e.value=="Completed") {
var targetSheet = e.source.getSheetByName("Storage");
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sh.getRange(e.range.rowStart,1,1,sh.getLastColumn()).copyTo(targetRange);
var clearRange=sh.getRange(e.range.rowStart,1,1,sh.getLastColumn());
var fA=clearRange.getFormulas();//returns a 2d array of formulas
clearRange.clearContent();
clearRange.setFormulas(fA);//you have to add that array back into this line
}
}
Because many people will take this code and try to run it from the editor just to return and tell me that they get the error of cannot find method getSheet of undefined. Let me first suggest that you can't run this code without an event object. The single e is the parameter that the event object will be place into. Without the e the program cannot run. Also unless there is a trigger to create the e the script cannot run. So if you wish to run this from the script editor then you will have to provide the event object. I don't choose to do that so when I debug these things I do it will a live trigger.

Moving rows between sheets based on cell edit + blank row found by column lookup not full row

I have been working on this for a few days and have turned the internet and these forums upside down, but can't find the answer anywhere. I've learned some scripting along the way, but have determined that what I need to do is way beyond my limits. Any help at all or guidance would be SUPER well received!!
I'm setting up an order sheet system for my and my wife's handmade 'business' (project). Essentially I need to move order 1 (columns a-w) from 'ready to ship" when "done" is entered into column X, onto the next blank row on "sent", discounting the formulas in columns w-z. So when its checking for a blank row, it should only check columns a-w.
This means that once the order info has moved across onto the new sheet, we have extra functionality added in those columns (I hooked it up to send an email apologising, if an order will be late - hope not, but better to be prepared, with our postal service :) )
Sample sheet here.
I originally learned how to write a 'move row if value added in column x - and learned about triggers and how to call the sheets etc.
Then, because column x, y and z have formulas, I either end up deleting them when I move the whole row - not cool. Or I end up (if i move only columns a- w) having the new (moved) row, showing up in row 9 million, because it sees the formulas as not blank rows. Ive tried editing a 'find row based on column' script, that seemed to work okay, but then i tried to put the two together and thats when it blew up.
Can anyone tell me where i'm going wrong here?
function lastRowForColumn(sheet, column){
// Get the last row with data for the whole sheet.
var ss = spreadSheetApp.getActiveSpreadSheet();// this gets you the
active spreadsheet in which you are working
var sheet = ss.getSheetByName('Sent');
var r = event.source.getActiveRange();
var data = sheet.getRange(1, "A", numRows).getValues();
var numRows = sheet.getLastRow();
// Iterate backwards and find first non empty cell
for(var i = data.length - 1 ; i >= 0 ; i--){
if (data[i][0] != null && data[i][0] != ""){
return i + 1;
var ss = spreadSheetApp.getActiveSpreadSheet();// this gets you the
active spreadsheet in which you are working
var sheet = ss.getSheetByName('Sent');
var r = event.source.getActiveRange();
if(s.getName() == "Ready To Send" && r.getColumn() == 25 &&
r.getValue() == "Sent") {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("Sent");
var lastRow = lastRowForColumn();
var target = targetSheet.getRange(targetSheet, i, 1);
s.getRange(row, 1, 1, 10).moveTo(target);
s.deleteRow(row);
}
}
}
}
Try this:
function onEdit(e){
var rg=e.range;
var sh=rg.getSheet();
var name=sh.getName();
var col=rg.columnStart;
if(name!='Sheet To Send'){return;}
if(col==24 && e.value=='done') {
var ss=e.source;
var sheet=ss.getSheetByName('Sent');
var row=rg.rowStart;
var srg=sh.getRange(row,1,1,23);
var data=srg.getValues();
var drg=sheet.getRange(sheet.getLastRow()+1,1,1,23);
drg.setValues(data);
sh.deleteRow(row);
}
}
I think what you want is to use getDisplayValues. That solves both your (formulas aren't blank problem (make sure your formulas display blank when the row is unused) and also the it isn't moving my numbers problem).
Try:
moveMe = [];
rowArray = s.getRange(row,1,1,10).getDisplayValues();
moveMe.push(rowArray);
targetSheet.appendRow(moveMe);
s.deleteRow(row);
You might have to wrestle the appendRow a bit but it is the cleanest. If it doesn't work as is try skipping the push step and doing
targetSheet.appendRow(rowArray);
BETTER (edit):
You need to change first your data row to:
var data = sheet.getRange(1, "A", numRows).getDisplayValues();
and then later you can simply do:
targetSheet.appendRow(data[i]);
s.deleteRow(row);
This function takes an edit event and checks if the values edited was changed to 'Sent'. It then copies that row into the sent sheet in the same spreadsheet, maintaining the values.
function onEdit(e){
if(e.value != 'Sent') return false; //If not changed to 'Sent' then stop.
var range = e.range; //get the range the edit occured in.
if(range.getColumn() != 25) return false;//see if it was column number 25, ie. Column Y
var sheet = range.getSheet();
if(sheet.getName() != 'Ready To Send') return false;
var editedRow = sheet.getRange(range.getRow(), 1, 1, sheet.getMaxColumns()); //Select the row.
var sent = sheet.getParent().getSheetByName('Sent');
sent.appendRow(editedRow.getValues()[0]); //Copy row to Sent sheet.
sheet.deleteRow(range.getRow()); //Deleted the original row
}

pull data to a specific sheet within a document

I'm a new to Script editor on google spreadsheet and not much of a programmer.
I've been working with google docs for sometime now and it has become an important tool in my daily activity.
What I'm trying to do, is the following:
Seek a whole document (with severall sheet such as "1", "2", "3", and so on, corresponding to the number of days a month can hold) and if the column 7 shows a determined value (in my case, it will be RECEBER), pull all the data in that row and write onto a sheet created for this purpose.
What's happening is that I'm using the the event onEdit to trigger this function. At first sight, it would be ideal, but in my case, I copy a lot of data from other spreadsheets and the paste command does not trigger the onEdit event. Instead, I have to edit the cell manually in order to get that row copied onto the other sheet.
I could run it just once, once tha whole days of the month were filled and there were any changes left to do, but what I really want to do is to make it immediately, as soon as the content is inserted into the spreadsheet.
There's also another problem with my code, it has to be fit and adapted to all the other sheets, as the if clause only performs the full operation if the active sheet equals "1". Anyway, I believe there is a simple solution to this.
Here's the code found on the net that already took me halfway:
function onEdit(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "1" && r.getColumn() == 7 && r.getValue() == "RECEBER") {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("money");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).copyTo(target);
}
}
I'll appreciate all the help you can give.
Thanks in advance.
Diogo Sousa
-- Updated 12Oct --
I've changed the logics on my code, and as patt0 suggested, I run the script from the menu created. I've tried adapting the code, but I believe there's some section wrong. The script runs, but isn't writing anything at all on my target sheet.
here's the code:
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [ {name: "RECEBER", functionName: "RECEBER"} ];
ss.addMenu("Scripts", menuEntries);
}
function mustBeCopied(sheetName) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetNumber = parseInt(sheetName);
if (sheetNumber <=31 && sheetNumber >=1)
return true;
return false;
}
function RECEBER() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getActiveSheet();
var r = ss.getActiveRange();
if(mustBeCopied(s.getName()) && r.getColumn() == 7 && r.getValue() == "RECEBER") {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("money");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).copyTo(target);
}
}
the function Mustbecopied, from what I believe, only sets the range of sheets (1 to 31) eligible;
the function RECEBER will determine if the value on the column 7 will satisfy the condition (RECEBER), so it can retrieve all the row information and move it to the target sheet.
Maybe the trouble is the active sheet thing.. can I us eit in my own advantage and apply the script to the selected sheet?
Also, if I could have both option (whether to apply it to the selected sheet or to the whole document), that wouuld be great and simplify my daily work a lot!
Your question is really multiple questions in one, and I will only deal with the second part, the first part has quite a number of dimensions (how to update summary sheets effectively), which we can discuss with another question maybe.
In order to determine if the data in a particular sheet needs to be copied, you could create a simple function that returns a boolean if the sheet satisfies the condition that it is a number between 1 and 31.
function mustBeCopied(sheetName) {
var sheetNumber = parseInt(sheetName);
if (sheetNumber <=31 && sheetNumber >=1)
return true;
return false;
}
Bear in mind that a sheet with the name "28.7" would satisfy the condition.
The first line of your onEdit() script would then look like this
if(mustBeCopied(s.getName()) && r.getColumn() == 7 && r.getValue() == "RECEBER")
Let me know if this works for you.
--- Oct 14 ---
As far as I can see, the issue that remains, is that when your function was running onEdit, the range (active range) was the cell that was just edited. So the range would be only one cell.
In order for the scrip to run, you would need to highlight each cell to be copied before selecting the menu, which would be tedious to say the least
Further if you have a defined range to be copied, which is the same on each page, we can copy that range every time, or iterate through each row in the column to look for "RECEBER" and get those rows copied.
Further We can try to update your function so that iterate through all the sheets is a many similar to this.
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (var i = 0; i < sheets.length; i::) {
var s = sheets[i];
if (mustBeCopied(s.getName()) {
var range = s.getRange(someRow, 7, someNumOfRows);
for (var j = 1; j <= someNumOfRows; j++) {
if (range.getCell(j,1).getValue() == "RECEBER") {
//YOUR COPY CODE
}
}
}
}
Let me know how this works.