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().
Related
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);
}
}
I created a simple form in google sheets that adds my inputs from Cells B1-B12 to a table. It works perfectly, except instead of adding my entry to the next blank row, it adds it to row 501. The reason for this is because several columns include formulas that go down to row 500, so it seems getlastrow() is taking into consideration all cells that include no values, but formulas.
I think this can be solved by either:
1.) Specifying getlastrow() starting from column G, which doesn't include any formula fields
2.) Somehow ignoring formula fields when using the getlastrow() function
I've exhausted my google/stack overflow searches, so if anyone has any ideas on how I can tweak my code to accomplish 1 or 2, I would very much appreciate it!
// Clear Form
function ClearCell() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formS =ss.getSheetByName("Copy of Sold"); //FormSheet
var rangesToClear = ["B1","B2","B3","B4","B5","B6","B7","B8","B9","B10","B11","B12"];
for (var i=0; i<rangesToClear.length; i++) {
formS.getRange(rangesToClear[i]).clearContent();
}
}
//---------------------------------------------------------
function SubmitData(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formS = ss.getSheetByName("Copy of Sold"); //Data Entry Sheet
var dataS = ss.getSheetByName("Copy of Sold"); //Data Entry Sheet / Data
var values = [[formS.getRange("B1").getValue(),
formS.getRange("B2").getValue(),
formS.getRange("B3").getValue(),
formS.getRange("B4").getValue(),
formS.getRange("B5").getValue(),
formS.getRange("B6").getValue(),
formS.getRange("B7").getValue(),
formS.getRange("B8").getValue(),
formS.getRange("B9").getValue(),
formS.getRange("B10").getValue(),
formS.getRange("B11").getValue(),
formS.getRange("B12").getValue()]];
dataS.getRange(dataS.getLastRow()+1,7,1,12).setValues(values);
ClearCell()
}
I believe your goal as follows.
You want to retrieve the values from the cells "B1:B12" and want to put the next row of the last row of the column "G" from the column "G" to the column direction. And, you want to clear the cells of "B1:B12".
Modification points:
At ClearCell(), I think that the process cost might be able to be reduced a little using the range list.
At SubmitData(), I think that the values of cells "B1:B12" can be retrieved by one call of getValues.
getLastRow returns the last row of data range including the values and formulas. It seems that this is the current specification.
In this modification, the last row of the column "G" is retrieved using isBlank().
ss.getSheetByName("Copy of Sold") can be used one time.
When above points are reflected to your script, it becomes as follows.
Modified script:
function ClearCell(formS) {
var rangesToClear = ["B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "B10", "B11", "B12"];
formS.getRangeList(rangesToClear).clearContent();
}
function SubmitData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formS = ss.getSheetByName("Copy of Sold");
var values = formS.getRange("B1:B12").getValues().flat();
for (var r = formS.getLastRow(); r >= 1; r--) {
if (!formS.getRange("G" + r).isBlank()) {
formS.getRange(r + 1, 7, 1, 12).setValues([values]);
break;
}
}
ClearCell(formS);
}
References:
getRangeList(a1Notations)
getLastRow()
isBlank()
function SubmitData(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formS = ss.getSheetByName("Sold"); //Data Entry Sheet
var dataS = ss.getSheetByName("Sold"); //Data Entry Sheet / Data
var values = [[formS.getRange("B1").getValue(),
formS.getRange("B2").getValue(),
formS.getRange("B3").getValue(),
formS.getRange("B4").getValue(),
formS.getRange("B5").getValue(),
formS.getRange("B6").getValue(),
formS.getRange("B7").getValue(),
formS.getRange("B8").getValue(),
formS.getRange("B9").getValue(),
formS.getRange("B10").getValue(),
formS.getRange("B11").getValue(),
formS.getRange("B12").getValue()]];
dataS.getRange(getLastRowByDirectionUp(dataS, "G")+1,7,1,12).setValues(values);
ClearCell();
}
function getLastRowByDirectionUp(sheet, col) {
if(sheet.getRange(col + sheet.getLastRow()).getValue()=="") {
return sheet.getRange(col + sheet.getLastRow()).getNextDataCell(SpreadsheetApp.Direction.UP).getRow();
}
else {
return sheet.getLastRow();
}
} function SubmitData(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var formS = ss.getSheetByName("Sold"); //Data Entry Sheet
var dataS = ss.getSheetByName("Sold"); //Data Entry Sheet / Data
var values = [[formS.getRange("B1").getValue(),
formS.getRange("B2").getValue(),
formS.getRange("B3").getValue(),
formS.getRange("B4").getValue(),
formS.getRange("B5").getValue(),
formS.getRange("B6").getValue(),
formS.getRange("B7").getValue(),
formS.getRange("B8").getValue(),
formS.getRange("B9").getValue(),
formS.getRange("B10").getValue(),
formS.getRange("B11").getValue(),
formS.getRange("B12").getValue()]];
dataS.getRange(getLastRowByDirectionUp(dataS, "G")+1,7,1,12).setValues(values);
ClearCell();
}
function getLastRowByDirectionUp(sheet, col) {
if(sheet.getRange(col + sheet.getLastRow()).getValue()=="") {
return sheet.getRange(col + sheet.getLastRow()).getNextDataCell(SpreadsheetApp.Direction.UP).getRow();
}
else {
return sheet.getLastRow();
}
}
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
...
}
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
}
If I want my Google Apps Script app get the current selected row in a spreadsheet, I use something like this:
function getCurrentRow() {
var currentRow = SpreadsheetApp.getActiveSheet().getActiveSelection().getRowIndex();
return currentRow;
}
But what if I want to get the first cell in this row (to put a comment in it), how do I proceed?
You can use offset to get a range relative to the active range.
function getFirstCellInRow() {
var sheet = SpreadsheetApp.getActiveSheet();
var activeCell = sheet.getActiveCell();
var firstCell = activeCell.offset(0, 1-activeCell.getColumn());
return firstCell;
}
Or just use getRange(A1notation), starting with what you had:
function getFirstCellInRow2() {
var sheet = SpreadsheetApp.getActiveSheet();
var currentRow = sheet.getActiveSelection().getRowIndex();
return sheet.getRange("A"+currentRow);
}
To write into the first cell in the row:
getFirstCellInRow().setValue('Tada');
There is a getCell() method you can use ( https://developers.google.com/apps-script/reference/spreadsheet/range#getCell(Integer,Integer) )
function getFirstCell() {
var firstCell= SpreadsheetApp.getActiveSheet().getActiveSelection().getCell(1,1);
return firstCell;
}
The following script will insert "My Info" in the first row available in column A.
function FirstCellColumnA() {
// replace with destination ID
var s = SpreadsheetApp.openById('spreadsheetID');
// replace with destination Sheet tab name
var ss = s.getSheetByName('sheetname');
// In Column A first ROW Available ss.getLastRow()+1 = find the
// first row available, 1 = start in column 1, 1 = rows to start,
// 1 = column where to put the data.
ss.getRange(ss.getLastRow() + 1, 1, 1, 1).setValue('My Info');
}
I hope this help you.