Google Apps Script that removes/deletes any empty rows - google-apps-script

Essentially I am looking for a script that keeps my single Google Sheet (titled, Main) free of any empty rows. If I have a row that contains data, and after some time, all of the data in that row is cleared, I want the script to notice and immediately delete that row. Any help/scripts appreciated.
I am managing the data in the sheet from an external app, and what happens is that I have the ability to "delete" a row (from the app). But what it actually does is just clear all the data from that row, it doesn't actually delete the row itself. So what I'm left with is a sheet that has scattered rows of data. For example, after a while, maybe there's data in row 3, and row 12, and row 125, and row 356, and what happens is the space between those rows (that contain data) are empty rows, when instead, using a script, it could keep checking for empty rows, and delete them as they become empty.
Edit - working solution:
function deleteEmptyRows() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName("Main");
var data = sheet.getRange(2, 1, sheet.getLastRow() - 1, sheet.getLastColumn()).getValues();
var row = sheet.getLastRow();
while (row > 2) {
var rec = data.pop();
if (rec.join('').length === 0) {
sheet.deleteRow(row);
}
row--;
}
var maxRows = sheet.getMaxRows();
var lastRow = sheet.getLastRow();
if (maxRows - lastRow != 0) {
sheet.deleteRows(lastRow + 1, maxRows - lastRow);
}
}

Have a look at Trigger. You will find the onEdit() trigger particularly useful.
Every time an edit happens in the associated Spreadsheet, the JavaScript function onEdit() will be run and it will get an event object passed to it. You can use the event object to detect which kind of event happend e.g. a row was deleted. Filter the kind of event you want to react to and then do whatever you need to do using the APIs Google App Script provides. For a list of available changeTypes (i.e. row deleted, column inserted etc.) of a event object and its properties for Google Sheets see the documentation.

Delete Empty Lines in Sheet Main every five minutes
function delMts() {
const ss = SpreadsheetApp.getActive();
ss.toast('', "Removing Empties", 5);
const sh = ss.getSheetByName("Main");
const vs = sh.getRange(1, 1, sh.getLastRow(), sh.getLastColumn()).getValues().filter(r => r.every(c => c != ''));
sh.deleteRows(vs.length + 1,sh.getMaxRows() - vs.length);
sh.getRange(1,1,vs.length,vs[0].length).setValues(vs);
}
//run this once
function createTriggerfordleMts() {
if(ScriptApp.getProjectTriggers().filter(t => t.getHandlerFunction() == "delMts").length == 0) {
ScriptApp.newTrigger("delMts").timeBased().everyMinutes(5).create();
}
}

This script grabs the sheet, iterates from the bottom up (starting from the last data containing row) and checks for empty rows, if any are found, it deletes them, it then checks for remaining empty rows beyond the last data containing row and deletes them all at once, if they are empty. Also, this script is setup with a corresponding onChange trigger so that the script runs anytime the sheet is edited, catching any newly cleared rows.
function deleteEmptyRows() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName("Main");
var data = sheet.getRange(2, 1, sheet.getLastRow() - 1, sheet.getLastColumn()).getValues();
var row = sheet.getLastRow();
while (row > 2) {
var rec = data.pop();
if (rec.join('').length === 0) {
sheet.deleteRow(row);
}
row--;
}
var maxRows = sheet.getMaxRows();
var lastRow = sheet.getLastRow();
if (maxRows - lastRow != 0) {
sheet.deleteRows(lastRow + 1, maxRows - lastRow);
}
}

Related

Data entry form that selects the next open row per specified column in google sheets

I am trying to create a data entry form that submits data to a data sheets first open row. The problem is that the data sheet has formula in one of the columns so it is not truly empty. This is causing the current script to take the cells with formula into consideration and only selecting the rows after it.
Could you guys please assist me with a workaround to the issue.
Current script looks like this:
function submitData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formSS = ss.getSheetByName("FORM"); //Form Sheet
var datasheet = ss.getSheetByName("DATA"); //Data Sheet
//Input Values
var values = [[formSS.getRange("D4").getValue(),
formSS.getRange("D8").getValue(),
formSS.getRange("D12").getValue(),
formSS.getRange("D16").getValue(),
formSS.getRange("D20").getValue(),
formSS.getRange("D24").getValue(),
formSS.getRange("D28").getValue(),
formSS.getRange("L32").getValue()]];
datasheet.getRange(datasheet.getLastRow()+1, 1, 1, 8).setValues(values);
}
You can find the first free row by evaluating the row contents
Sample
var freeRow;
var columnI = datasheet.getRange("I1:I" + datasheet.getLastRow()).getDisplayValues().flat();
for(var i = 0; i < columnI.length; i++){
if(columnI[i] == "") {
freeRow = i + 1;
break;
}
}
datasheet.getRange(freeRow, 1, 1, 8).setValues(values);
In addition, you are using a Form submit trigger, you use event objects
Sample
function submitData(e) {
var range = e.range;
var row = range.getRow();
// this is the row into which the latest form response has been inserted - do with it what you need
...
}

How can I conditionally move rows between 3 sheets in Google Sheets?

I have a list of tasks on a sheet, and I've been trying to find a simple way to move the rows with tasks to a different sheet depending on the status in one column (e.g. blank, done, paid). For instance, a task on row 2 is marked "done". I then need that row to be deleted and moved to the sheet labeled "done". From there, when the task is paid, i need that row deleted from the "done tab to the "paid" tab.
I've found some code that allows me to do this once for 1 row, but it doesn't repeat for the rows below it. Here is the code I've been using:
function ConditionalShimmy2(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName('not done');
var sheet2 = ss.getSheetByName('done');
var sheet3 = ss.getSheetByName('paid');
var cell = sheet1.getRange("f2:f277").getCell(1, 1);
var rowtoInsert = "2:277";
if(cell.getValue() == "done")
{
sheet2.insertRows(2);
var range2 = sheet2.getRange(2 ,1,1,sheet1.getLastColumn());
sheet1.getRange(cell.getRow(),1, 1, sheet1.getLastColumn()).copyTo(range2);
sheet1.deleteRow(cell.getRow());
}
}
Can anyone help me finish this code and let me know where I'm going wrong?
In order to achieve your task, you can try the below proposed solution that will suit your situation.
Snippet
function ConditionalShimmy2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var main = ss.getSheetByName("THE_NAME_OF_THE_MAIN_SHEET");
var sheet1 = ss.getSheetByName("not done");
var sheet2 = ss.getSheetByName("done");
var sheet3 = ss.getSheetByName("paid");
var taskNames = main.getRange(row, column, numRows, numColumns).getValues();
var taskStatus = main.getRange(row, column, numRows, numColumns).getValues();
var rowsDeleted = 0;
for (var i = 0; i < taskStatus.length; i++) {
if (taskStatus[i][0] == "not done") {
sheet1.appendRow([taskNames[i][0]]);
main.deleteRow((i + 1) - rowsDeleted);
rowsDeleted++;
} else if (taskStatus[i][0] == "done") {
sheet2.appendRow([taskNames[i][0]]);
main.deleteRow((i + 1) - rowsDeleted);
rowsDeleted++;
} else if (taskStatus[i][0] == "paid") {
sheet3.appendRow([taskNames[i][0]]);
main.deleteRow((i + 1) - rowsDeleted);
rowsDeleted++;
}
}
}
Explanation
The above snippet loops through all the tasks and their associated statuses and based on the status column, each row is then appended to the appropriate sheet by making use of the appendRow() method and then deleted from the main sheet by making use of the deleteRow() method.
Note
In order to make the above code snippet work for your situation, you will have to customize the parameters for the getRange method associated to both taskNames and taskStatus. Therefore, the row, column, numRows, numColumns parameters represent the range with the top left cell at the given coordinates with the given number of rows and columns.
Reference
Sheet Class - getRange();
Sheet Class - appenRow();
Sheet Class - deleteRow().

Google Script to remove duplicates from top to bottom

I have a script which combines three scripts to do the following:
1) Insert rows from one tab to the top of another tab
2) Remove duplicates from the tab in which the data was just added
3) Clear out the old tab from which the data was just ported over from
For the De-dupe script, it deletes rows starting at the bottom and then goes up. So I'm having established and existing data deleted. What I need it to do is start at the top and go down. So if new row records ported over from the first script are found to be a duplicate, it should delete those instead.
How can I get the de-dupe script to essentially process the opposite way?
I did find reverse logic with the below link, but I can't find a way to make it work with my script and keep getting errors. I'm also not sure if this would be the best methodology to fit in with my overall script.
Link: Removing Duplicate Rows in a google Spreadsheet from the end row
function Run(){
insert();
removeDuplicates();
clear1();
}
function insert() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getSheetByName('Candidate Refresh'); // change here
var des = ss.getSheetByName('Candidate Listing'); // change here
var sv = source
.getDataRange()
.getValues();
sv.shift();
des.insertRowsAfter(1, sv.length);
des.getRange(2, 1, sv.length, source.getLastColumn()).setValues(sv);
}
//Code in Question Start
function removeDuplicates() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getLastRow();
var firstColumn = sheet.getRange(1, 2, rows, 1).getValues();
firstColumn = firstColumn.map(function (e) {return e[0]})
for (var i = rows; i >0; i--) {
if (firstColumn.indexOf(firstColumn[i-1]) != i-1) {
sheet.deleteRow(i);
}
}
}
//Code in Question End
function clear1() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Candidate Refresh');
sheet.getRange('A2:K100').clearContent()
}
If new rows at the top of the sheet are found to be a duplicate, delete the new rows at the top.
try this:
function removeDuplicates() {
var sheet=SpreadsheetApp.getActiveSheet();
var rows=sheet.getLastRow();
var firstColumn=sheet.getRange(1, 2, rows, 1).getValues();
firstColumn = firstColumn.map(function(e){return e[0]})
var uA=[];
for (var i=rows;i>0;i--) {
if (uA.indexOf(firstColumn[i-1])!=-1) {
sheet.deleteRow(i);
}else{
uA.push(firstColumn[i-1]);
}
}
}

How do I add a formula next to a copied data range with Google Apps Script?

I have trouble understanding arrays, but I know it's what I need to do to fix my problem. Presently, I'm copying data I need from another sheet onto a tab, copying that data table, then pasting it into another tab on that sheet. I now need to add another column to the end of this finally produced data that is a formula in each cell adjacent, which is column AP starting in AP2. Here is the script:
function CopyDataToNewFile() {
var sss = SpreadsheetApp.openById('some id');
var ss = sss.getSheetByName('Outcome'); // ss = source sheet
var destination = SpreadsheetApp.openById('other id');
var target = destination.getSheetByName('Final');
ss.copyTo(destination);
var data = destination.getSheetByName('Copy of Outcome');
var infoTable = data.getRange(2, 1, data.getLastRow(), data.getLastColumn());
var lastRow = target.getLastRow();
infoTable.copyTo(target.getRange(lastRow + 1, 1), {contentsOnly: true});
destination.setActiveSheet(destination.getSheetByName('Copy of Outcome'));
destination.deleteActiveSheet();
}
The formula I want to append each copied row with is the following:
'=vlookup($I2, IMPORTRANGE("https://docs.google.com/spreadsheets/d/<yet another id>/edit#gid=1203869430", "Source_Hours!A:B"), 2, false)'
Here's how I tried to do it:
function CopyDataToNewFile() {
var sss = SpreadsheetApp.openById('some id');
var ss = sss.getSheetByName('Outcome'); // ss = source sheet
var destination = SpreadsheetApp.openById('other id');
var target = destination.getSheetByName('Final');
ss.copyTo(destination);
var data = destination.getSheetByName('Copy of Outcome');
var infoTable = data.getRange(2, 1, data.getLastRow(), data.getLastColumn());
var lastRow = target.getLastRow();
infoTable.copyTo(target.getRange(lastRow + 1, 1), {contentsOnly: true});
target.getRange(2,infoTable.getLastColumn()+1,infoTable.getLastRow(),1).setFormula('=vlookup($I2,IMPORTRANGE("https://docs.google.com/spreadsheets/d/1f7_unFqRkI6O2EHBLsNGLRCH5j9rlEXBdVnRLTgS_lM/edit#gid=1203869430","Source_Hours!A:B"),2,false)');
destination.setActiveSheet(destination.getSheetByName('Copy of Outcome'));
destination.deleteActiveSheet();
}
It works the first time I run the script, but the next time I run it, it does not add the formula in the last column. I am not a programmer, just know enough to be dangerous. :)
Your solution was close. My recommended approach is to create a reference to the first row your script will copy into -- target.getLastRow() + 1 -- and then use this reference for both the datatable copy, and the formula write. Your script also had a possible issue in that you assumed that the sheet name was always "Copy of Outcome" -- this is not strictly guaranteed to be true (consider copying a sheet twice without deleting the first copy). Instead, bind the return value of Sheet#copyTo, since it is the newly created worksheet copy. In this manner, the name of the copied sheet is irrelevant - the script doesn't need it to function as intended.
function CopyDataToNewFile() {
const source = SpreadsheetApp.openById('some id').getSheetByName('Outcome');
const destination = SpreadsheetApp.openById('other id');
const target = destination.getSheetByName('Final');
if (!source || !target)
throw new Error("Missing required worksheets (sheet names not found)");
// Copy the source sheet and get a reference to the copy.
var datasheet = source.copyTo(destination);
var infoTable = datasheet.getRange(2, 1, datasheet.getLastRow(), datasheet.getLastColumn());
// Copy the table contents to the end of the target sheet.
// Determine the first row we are writing data into.
const startRow = target.getLastRow() + 1;
infoTable.copyTo(target.getRange(startRow, 1), {contentsOnly: true});
// Set a column formula in the adjacent column.
target.getRange(startRow, 1 + infoTable.getNumColumns(), infoTable.getNumRows(), 1)
.setFormula("some formula");
// Remove the intermediate sheet.
destination.deleteSheet(datasheet);
}
New functions used:
Spreadsheet#deleteSheet (you don't have to activate a sheet to delete it)
Range#getNumColumns The width of the Range
Range#getNumRows The height of the Range
An alternate method, which uses JavaScript arrays to limit the number of Spreadsheet Service methods used, is possible since you appear to only desire the values to be transferred. This takes advantage of chaining, since Range#setValues returns a reference to the range that was written into. This assumes that values on the source sheet, "Outcome", don't depend on the workbook. If "Outcome" has formulas that refer to other worksheets in the same workbook, then naturally this won't work, as the values would be in reference to the original workbook, not the destination workbook.
...
const dataToCopy = source.getDataRange().getValues();
dataToCopy.shift(); // remove header row.
const startRow = target.getLastRow() + 1;
const numRows = dataToCopy.length;
const numColumns = dataToCopy[0].length;
target
.getRange(startRow, 1, numRows, numColumns)
.setValues(dataToCopy)
.offset(0, numColumns, numRows, 1) // offset is from the top-right cell of the range
.setFormula("some formula");
}
Range#offset
Array#shift

Google Apps Script - move data range based on for loop matching to value = "Yes"

I am attempting to find certain values in a datasheet using a for loop, and based on whether the value is = "Yes" it will move the row to another sheet. The loop should further look for the value of requesttype (in this case SRM, CC, or TM1) and move only certain parts of the row to the other sheet.
Hence, the for loop should copy the row based on inputs from two different columns in the data sheet: "All Outputs"
So while my code works in general when I run it, it only seems to execute the function for half of the rows where "Yes"is entered.
Each time I run the script half of the rows that should be moved, get moved but the other half stays in the original sheet. I can run the code until there is only one row left, in which case nothing happens anymore.
Here is what I have:
function myonEdit2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getActiveCell();
var sheetNameToWatch = "Open Requests";
var columnNumberToWatch = 38;
var watchColumn = 2;
var valueToWatch = "Yes";
var datarange = sheet.getRange(9, 2, 900, 36).getValues();
var sheetNameToMoveTheRowTo = "Closed Requests"
for (i = 0; i < datarange.length -1; i++) {
if (datarange[i][35] == "Yes") {
var targetSheet = ss.getSheetByName(sheetNameToMoveTheRowTo);
if (datarange[i][0] == "SRM") {
var targetRange3 = targetSheet.getRange(targetSheet.getLastRow() + 1, 2);
sheet.getRange(i, 2, 1, 8).moveTo(targetRange3);
sheet.deleteRow(rowstart + i);
}
else if (datarange[i][0] == "GC") {
var targetRange3 = targetSheet.getRange(targetSheet.getLastRow() + 1, 2);
sheet.getRange(i, 2, 1, 8).moveTo(targetRange3);
sheet.deleteRow(i);
}
else if (datarange[i][0] == "TM1") {
var targetRange4 = targetSheet.getRange(targetSheet.getLastRow() + 1, 2, 1, 6);
sheet.getRange(i, 2, 1, 6).moveTo(targetRange4);
var targetRange5 = targetSheet.getRange(targetSheet.getLastRow(), 10, 1, 1)
sheet.getRange(i, 13, 1, 1).copyTo(targetRange5)
sheet.deleteRow(i);
}
}
}
As I said the code works fine but it does not execute for all the rows it should execute for.
As you can see from above, I define the for loop range as the complete datasheet in which the table is in. Then I create the first (main) if condition namely the 36th column of the data range being "Yes". This should define for which rows the script will be executed.
The second if condition defines which exact columns of the row should be copied and depends on whether the first column is = SRM, CC or TM1.
If anybody could help me figure out why the code does not execute for all rows that would be great.
Example sheet
The similar kind of problem has happened with me and this also seems the issue here .
you are taking the data range and adding a for loop to it to process each row. Now, you are deleting the rows in between but there is a defined data array in which you have added the for loop. So, when the previous rows will get deleted, the for loop array structure will still remain same but the sheet structure will be changed as example consider the 2nd row got deleted and your condition matched at i=5 but the sheet structure would now have that 5th row moved upwards.
So, you can try adding the rows to keep in an array and clear the sheet once and add that array to the sheet in one go after the processing has been done to copy the sheet data or try something to remove the deleted rows from the sheet after processing has been done to move to another sheet