Google Apps Script double deleting rows in spreadsheet - javascript

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.

Related

Skipping cell in loop, and tweaking a function to move range

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.

wants to know about some Javascript function explanation

Hy,this is my first post here
I am following a front end dev. class
Recent task was to create a pixel art page,was my first ever bigger task with javascript,i asked around and solved parts what I didn't know how,but now I do not understand everything,would appreciate if any experienced user could help me.
// Select color input
// Select size input
// When size is submitted by the user, call makeGrid()
//function makeGrid(row,colm) {
function makeGrid() {
let gridRows, cell;
let rows = $("#inputHeight").val();
let cols = $("#inputWidth").val();
let table = $("#pixelCanvas");
table.children().remove();
for (let i = 0; i < rows; i++) {
table.append("<tr></tr>");
}
gridRows = $("tr");
for (let j = 0; j < cols; j++){
gridRows.append("<td></td>");
}
cell = table.find("td");
table.on("click", "td", function() {
var color = $("input[type='color']").val();
$(this).attr("bgcolor", color);
});
}
//when size is submitted call makeGrid()
$("input[type='submit']").click(function(e) {
e.preventDefault();
makeGrid();
});
This is my code,parts which I do not understand:
table.children().remove();-removes tables child element(?what exactly removes,why this needed to be implemented?
cell = table.find("td");-I know that .find is a jquery element which allows us to search trough descendant but I do not understand why I needed this here.
The project is also uploaded on my codepen
https://codepen.io/MelindaB/pen/xWgJqY
Thank you for the help
table.children().remove(); is used to clear the current generated table to make it possible to create a new one.
You can test this by removing the line, generating a 1x1 table, and generate another 1x1 after that. You will see the grid now actually consists of 3 cells instead of the specified 1.
As far as I can see cell = table.find("td"); has no use since cell is not used anywhere and this line can be removed.

Finding an empty cell in a column using google sheet script

I am trying to find an empty cell in a specific column in google sheets. I am familiar with getLastRow(), but it returns the last row in whole sheet. I want to get the first empty cell in a specific column. So, I used a for loop and if. But I don't know why it is not working. The problem is that for loop does not return anything.
I am getting 10 rows of the column H (position 8) in the sheet test (line2). first 5 rows already have content. Data will be added to this cell later using another code. so, I want to be able to find the first empty cell in this column to be able to put the new data.
var sheettest = SpreadsheetApp.getActive().getSheetByName("test");
var columntest = sheettest.getRange(4, 8, 10).getValues();
for(var i=0; columntest[i]=="" ; i++){
if (columntest[i] ==""){
var notationofemptycell = sheettest.getRange(i,8).getA1Notation();
return notationofemptycell
}
}
As I said, I want to find the first empty cell in that column. I defined the for loop to go on as long as the cell is not empty. then if the cell is empty, it should return the A1 notation of that cell.
It seems the for loop does go on until it find the empty cell, because I get no definition for the var "notationofemptycell" in debugging.
This may be faster:
function getFirstEmptyCellIn_A_Column() {
var rng,sh,values,x;
/* Ive tested the code for speed using many different ways to do this and using array.some
is the fastest way - when array.some finds the first true statement it stops iterating -
*/
sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('test');
rng = sh.getRange('A:A');//This is the fastest - Its faster than getting the last row and
//getting a specific range that goes only to the last row
values = rng.getValues(); // get all the data in the column - This is a 2D array
x = 0;
values.some(function(ca,i){
//Logger.log(i)
//Logger.log(ca[0])
x = i;//Set the value every time - its faster than first testing for a reason to set the value
return ca[0] == "";//The first time that this is true it stops looping
});
Logger.log('x: ' + x)//x is the index of the value in the array - which is one less than
//the row number
return x + 1;
}
You need to loop through the length of columntest. Try this:
function myFunction() {
var sheettest = SpreadsheetApp.getActive().getSheetByName("test");
var lr=sheettest.getLastRow()
var columntest = sheettest.getRange(4, 8, lr).getValues();
for(var i=0; i<columntest.length ; i++){
if (columntest[i] ==""){
var notationofemptycell = sheettest.getActiveCell().getA1Notation();
Logger.log( notationofemptycell)
break;//stop when first blank cell on column H os found.
}
}
}

Google Script to remove duplicates exceeds processing time

I have a Google Sheet that has 10k+ rows of data. While it should be rare, there could be instances of duplicate data being entered into the tab, and I have written a script to search for and remove those duplicates. For a while, this script has been running nicely and doing exactly what I expected it to do. But now that the tab has grown to over 10k rows, the script is exceeding the 6 minute time limit.
I've based this function on this tutorial.
// remove duplicates on Ship Details Complete
function duplicateShipDetailsComplete() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = ss.getSheetByName("Shipment Details Complete");
var sourceRange = sourceSheet.getRange(2, 1, sourceSheet.getLastRow(), 16)
var sourceData = sourceRange.getValues();
var keepData = new Array();
var deleteCount = 0;
for(i in sourceData) { // look for duplicates
var row = sourceData[i];
var duplicate = false; // initialize as not a duplicate
for(j in keepData) { // compare the current row in data to the rows in newData
if(row[2] == keepData[j][2] // duplicate Partner Invoice?
&& row[4] == keepData[j][4] // duplicate vPO?
&& row[5] == keepData[j][5] // duplicate SKU?
&& row[7] == keepData[j][7]) { // duplicate qty?
duplicate = true; // only if ALL criteria are duplicate, set row as a duplicate
}
}
if(!duplicate) { // If the row is NOT a duplicate
keepData.push(row); // add to newData
} else {
deleteCount++; // keep track of duplicates being deleted
}
}
sourceRange.clear();
sourceSheet.getRange(2, 1, keepData.length, keepData[0].length).setValues(keepData); // paste the keepData into the Working sheet
return deleteCount;
}
I've thought about breaking it up into pieces; process 1/3 of the data in each of 3 different calls. But this is actually being called from a different function that emails the returned deleteCount value, if it's greater than 0.
// Nightly email after checking for duplicates in Ship Details Complete
function sendEmailShipDetails() {
var deleted = duplicateShipDetailsComplete();
var update = parseFloat(SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Dept/Class").getRange(1,6).getValue()).toFixed(2);
if(deleted > 0) {
MailApp.sendEmail(
"me#myoffice.com",
"Shipment Details Cleaned Up",
deleted + " Shipment Detail line(s) were deleted from Complete as duplicates.\n" +
"Updated Value: " + update + "\n"
);
}
}
Even without that email function, it's exceeding the limit when I call duplicateShipDetailsComplete() directly. I suppose I could write three different functions (first 1/3, second 1/3, third 1/3) and update a cell somewhere with the results for each, and then call the email function separately to get that value. I'd feel a little better about that if I could write 1 function and pass parameters to it, but this is all coming from a Time Based Trigger, and you can't pass parms from those. But before I started to do that, I thought I'd check to see if someone had other suggestions on how I could make the existing code more efficient. Or, see if someone had totally different ideas on how I can do this.
Thanks

Using row ID to delete rows in a loop 'Cannot read property 'remove' of null'

I have a script that adds and subtracts rows from a table. As I add new rows, I assign the row ID a number. And when I subtract rows, I decrease that number. When I decide to select a row to delete, I delete the row by using the ID number.
I am stuck on one function 'clearfields' that is supposed to clear all rows up to row 11 when a button is pushed. In my loop I am going from a start point of 12 to the end of the table length.
My issue is if I delete a row sort of in the middle lets say row 18 out of 22, I get a remove null error when the button is pushed and I run this function. Any way around this? Can I just delete all rows no matter what the ID is up until a row ID of 11 is reached?Any help is most appreciated. Thanks
Javascript
function clearFields(){
buttonLim=0;
var tableClr = document.getElementById('table2').rows.length;
var subJob=tableClr;
var subJob2=tableClr-10;
var numb=11;
var getter= $("#num1").val();
i=11;
for (var i = 0; i <= tableClr-1; i++) {// this is where I empty the cells
var adde=i+4;
$("#num"+adde+"Sun").val("");
$("#num"+adde+"Mon").val("");
$("#num"+adde+"Tue").val("");
$("#num"+adde+"Wed").val("");
$("#num"+adde+"Thu").val("");
$("#num"+adde+"Fri").val("");
$("#num"+adde+"Sat").val("");
added=0;
}
if(tableClr>11){ //comment here... this is where I delete the rows
for (var j = 12; j <= tableClr; j++) {
document.getElementById("row" + j).remove();
}
}
else
{
}
}
Check the for loop variables and their constraints i am no expert but i get a feeling that your problem might be solved when you correct the for loop with different variables and conditions(just give var to j not like a number(12) and update the var) see what i am saying just relax and try after a bit you'll figure it out for yourself, it happens with me at all the times.

Categories