How to add new rows when archiving a row in Google Sheets - google-apps-script

I currently have working code inside of my Google Sheet. The code moves certain rows in the sheet over to another sheet when labeled as "Archive" in a drop down menu.
The problem I have is that when this happens, the entire row gets deleted. I only need the information from column C:O (C2:O) to be archived. Another problem that this creates, when it deletes the row the other rows move up, thus deleting the set amount of rows I have created for input.
I need it to automatically replace the archive rows with another row with all the same conditional formatting and functions so that it does not disrupt the rest of the sheet and there is no need to go in and manually create more rows.
Please help, thank you very much in advance.
Current code used in Google App Scripts is attached below.
function myFunction() {
// moves a row from a sheet to another when a magic value is entered in a column
// adjust the following variables to fit your needs
// see https://productforums.google.com/d/topic/docs/ehoCZjFPBao/discussion
var sheetNameToWatch = 'Campaigns';
var columnNumberToWatch = 6;
// column A = 1, B = 2, etc…
var valueToWatch = 'Archive';
var sheetNameToMoveTheRowTo = 'Archive';
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()).moveTo(targetRange);
sheet.deleteRow(range.getRow());
}
}

You just need to use copyTo() [1] function instead of moveTo() [2] function which is cutting and pasting the source row. Also, you need to remove the deleteRow function if you just want to leave the row cells with same format and functions set. You only would need to change this:
sheet.getRange(range.getRow(), 1, 1, sheet.getLastColumn()).moveTo(targetRange);
sheet.deleteRow(range.getRow());
To this:
sheet.getRange(range.getRow(), 1, 1, sheet.getLastColumn()).copyTo(targetRange);
Moreover, if you just want to copy the values and not the formulas from the cells to the target sheet, use the copyTo function with options [3], like this:
sheet.getRange(range.getRow(), 1, 1, sheet.getLastColumn()).copyTo(targetRange, SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
[1] https://developers.google.com/apps-script/reference/spreadsheet/range#copytodestination
[2] https://developers.google.com/apps-script/reference/spreadsheet/range#movetotarget
[3] https://developers.google.com/apps-script/reference/spreadsheet/range#copytodestination-copypastetype-transposed

If you want to clear the values without deleting the row, you'll want to change your approach in the following ways:
obtain a Range which includes the data you want to archive
to populate the archive sheet, use copyTo() instead of moveTo()
obtain a Range which includes only the cells you want to clear out
use range.clearContent() to clear that range
Here's a reimplementation of your function. I've extracted the configurable bits into global variables, which makes adjusting them down the line a bit easier to do.
var SOURCE_SHEET_NAME = 'Campaigns';
var TARGET_SHEET_NAME = 'Archive Campaigns';
// The cell value that will trigger the archiving action.
var ARCHIVE_VALUE = 'Archive';
// The column number where you expect the ARCHIVE_VALUE to appear.
var ARCHIVE_COLUMN_NUMBER = 4;
// The starting and ending columns for the range of cells to archive.
var ARCHIVE_START_COLUMN = 1;
var ARCHIVE_END_COLUMN = 15;
// The starting and ending columns for the range of cells to clear.
var CLEAR_START_COLUMN = 1;
var CLEAR_END_COLUMN = 10;
function archiveRow() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var activeCellRange = sheet.getActiveCell();
if (sheet.getName() != SHEET_NAME ||
activeCellRange.getColumn() != ARCHIVE_COLUMN_NUMBER ||
activeCellRange.getValue() != ARCHIVE_VALUE) {
return;
}
var activeCellRow = activeCellRange.getRow();
// Get the range for the data to archive.
var archiveNumColumns = ARCHIVE_END_COLUMN - ARCHIVE_START_COLUMN + 1;
var archiveRowRange = sheet.getRange(activeCellRow, ARCHIVE_START_COLUMN, 1, archiveNumColumns)
// Get the range for the archive destination.
var targetSheet = ss.getSheetByName(TARGET_SHEET_NAME);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1, 1, archiveNumColumns);
// Copy the data to the target range.
archiveRowRange.copyTo(targetRange);
// Get the range for the data to clear.
var clearNumColumns = CLEAR_END_COLUMN - CLEAR_START_COLUMN + 1;
var clearRowRange = sheet.getRange(activeCellRow, CLEAR_START_COLUMN, 1, clearNumColumns);
// Clear the row.
clearRowRange.clearContent();
}

Related

How to change code to apply to a named sheet tab, instead of the first sheet tab in a Google spreadsheet

I have a great piece of code which copies out a row of data to the a certain sheet in a spreadsheet when the value 'Assigned' is entered in another column. So the user will select the sheet it must move to, and then changes the status to assigned and it gets moved out. It works great but now I want to modify it to work on a different sheet in that spreadsheet. How do i change the code to work on a different sheet that i specify instead of just the first one? I have pasted my working code below that applies to the first tab. I want to make another script to apply to the other tabs to work with different values and move to another. I am very new to this, so would appreciate any help.
I know I need to update the ss.getActiveSheet() to include getSheetByName(); but im not sure how to edit the end bit of the code to get it to work.
function onEdit(e) {
var ss = e.source;
var s = ss.getActiveSheet();
var r = e.range;
// to let you modify where the action and move columns are in the form
responses sheet
var actionCol = 19;
var nameCol = 18;
// Get the row and column of the active cell.
var rowIndex = r.getRowIndex();
var colIndex = r.getColumnIndex();
// Get the number of columns in the active sheet.
// -1 to drop our action/status column
var colNumber = s.getLastColumn()-1;
// if our action/status col is changed to ok do stuff
if (e.value == "Assigned" && colIndex == actionCol) {
// get our target sheet name - in this example we are using the priority
column
var targetSheet = s.getRange(rowIndex, nameCol).getValue();
// if the sheet exists do more stuff
if (ss.getSheetByName(targetSheet)) {
// set our target sheet and target range
var targetSheet = ss.getSheetByName(targetSheet);
var targetRange = targetSheet.getRange(targetSheet.getLastRow()+1, 1, 1,
colNumber);
// get our source range/row
var sourceRange = s.getRange(rowIndex, 1, 1, colNumber);
// new sheets says: 'Cannot cut from form data. Use copy instead.'
sourceRange.copyTo(targetRange);
// ..but we can still delete the row after
s.deleteRow(rowIndex);
// or you might want to keep but note move e.g. r.setValue("moved");
}
}
}
I believe you could do something like this:
function onEdit(e) {
var ss = e.source;
var r = e.range;
var s = r.getSheet().getName(); // Get the sheet on which the change was made
var sheetname = s.getName();
// to let you modify where the action and move columns are in the form responses sheet
var actionCol, nameCol, valueToCheck;
switch (sheetname) {
case "xxxx":
actionCol = 19;
nameCol = 18;
valueToCheck = "Assigned";
break;
case "yyyy":
actionCol = 19;
nameCol = 18;
valueToCheck = "Assigned";
break;
default:
return ; // if none of the names aboves, no instructions, so quit withoud doing anything
}
// Get the row and column of the active cell.
var rowIndex = r.getRowIndex();
var colIndex = r.getColumnIndex();
// Get the number of columns in the active sheet.
// -1 to drop our action/status column
var colNumber = s.getLastColumn()-1;
// if our action/status col is changed to ok do stuff
if (e.value == valueToCheck && colIndex == actionCol) {
// get our target sheet name - in this example we are using the priority column
var targetSheet = s.getRange(rowIndex, nameCol).getValue();
// if the sheet exists do more stuff
if (ss.getSheetByName(targetSheet)) {
// set our target sheet and target range
var targetSheet = ss.getSheetByName(targetSheet);
var targetRange = targetSheet.getRange(targetSheet.getLastRow()+1, 1, 1, colNumber);
// get our source range/row
var sourceRange = s.getRange(rowIndex, 1, 1, colNumber);
// new sheets says: 'Cannot cut from form data. Use copy instead.'
sourceRange.copyTo(targetRange);
// ..but we can still delete the row after
s.deleteRow(rowIndex);
// or you might want to keep but note move e.g. r.setValue("moved");
}
}
}
In the switch operation, just edit the values based on what you need, and repeat the case parts (until, including, the break; command) for each sheet you need to consider.

Auto Archive Schedule; Time-based Trigger

I have been asked to create a live Google Sheets Spreadsheet to track the work schedule at our yard. I have no experience with a script but found out I could program my sheet instead of hiding formulas and it would yield a cleaner result. I have been able to make the sheet organize itself and I was able to make it Archive manually (onEdit). What I'm looking for is to have it automatically run the code at 1 am so when we arrive at work it archives based on a cell value in a certain column.
This is an example of my onEdit script that works, but when someone is trying to check off the "YES" column there is some lag and can cause the wrong cell to be checked, which I then manually correct.
function onEdit() {
var sheetNameToWatch = "Schedule";
var columnNumberToWatch = 28;
var valueToWatch = "Yes";
var sheetNameToMoveTheRowTo = "Archive";
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()).moveTo(targetRange);
sheet.deleteRow(range.getRow());
}
}
So this code runs at 100% failure but saves and executes, and I honestly don't know why. Could be I misunderstand the values I need to insert after the "function" area. I did have this setup with an "Auto Archive" trigger that created a menu button with a "Run" option on the sheet, but when you click that it only does the last row with "Yes" in column 28 (every press of the button will move 1 row until all rows are moved) and the button won't work for the other users of the sheet.
function createTrigger() {
ScriptApp.newTrigger("Move Archive") //Move Archive is the name of the script
.timeBased()
.everyMinutes(1) // only set to 1 minute for testing, I can change this out for a daily timer
.create();
}
function myFunction() {
var sheetNameToWatch = "Schedule"; // "schedule" is the sheet we enter info on
var columnNumberToWatch = 28; //Column is "AB"
var valueToWatch = "Yes";
var sheetNameToMoveTheRowTo = "Archive"; //"Archive is the sheet the info is sent to"
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()).moveTo(targetRange); // I get its programmed for last row with "Yes" here, unsure on how to change this.
sheet.deleteRow(range.getRow());
function myfunction() {
ScriptApp.deleteTrigger("Move Archive"); // could have wrong value here
}
}
}
All I want is the sheet to "Archive" based on a "Yes" value in Column 28 (AB). I want every row with that column value to Archive at 1 am automatically. Any help is appreciated. If someone even wants to recommend a book or digital instruction for beginners that would be great.
You have four project files each containing scripts of the same name, performing the same or similar tasks. You are experiencing lagging because you have multiple simple and installable scripts of the same name. Essentially, they are all trying to execute at the same time.
The following answer should be considered as one possible solution to your situation.
The elements of this script are:
There is a single project file. Unnecessary project files have been deleted.
There is a single function.
The function is a single "simple" trigger (onEdit(e)) which takes advantage of the various event objects returns by onEdit. There are no installable triggers, and any/all installable triggers have been deleted.
The function updates the "Schedule" and "Archive" sheets as described in the question; and then sorts both the "Schedule" and "Archive" sheets.
If there is a change on the "Railcars" sheet, the function sorts that sheet.
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var testrange = e.range;
var testsheet = testrange.getSheet();
var testsheetname = testsheet.getSheetName();
var testrow = testrange.getRow();
var testcolumn = testrange.getColumn();
var testvalue = e.value;
var testsheetLC = testsheet.getLastColumn();
var testsheetRange = testsheet.getRange(testrow,1,1,testsheetLC);
//Logger.log("DEBUG: the test sheet range is "+testsheetRange.getA1Notation());
//Logger.log("DEBUG: Range: "+testrange.getA1Notation());
//Logger.log("DEBUG: The row is "+testrow+", and the column is "+testcolumn);
//Logger.log("DEBUG: The spreadheetsheet is "+e.source.getName()+", the sheet name is "+testsheet+", the range = "+testrange.getA1Notation()+", and the new value = "+testvalue);
//Logger.log(JSON.stringify(e));
// Copy/Paste to Schedule/Archive
var sheetNameSchedule = "Schedule";
var colNumberSchedule = 28;
var valueSchedule = "Yes";
var sheetNameArchive = "Archive";
// Sort Schedule
var sortSchedule = [{column: 1, ascending: true},{column: 2, ascending: true},{column: 7, ascending: false}];// date // Appt (time) // Type (Out/In/RR)
// Sort Railcars
var sheetNameRailcars = "Railcars";
var sortRailcars = [{column: 1, ascending: true}];
if (testsheetname === sheetNameSchedule && testcolumn === colNumberSchedule && testvalue === valueSchedule){
// this is a match
// Logger.log("DEBUG: this was a match");
// copy/paste to archive
var targetSheet = ss.getSheetByName(sheetNameArchive);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
//Logger.log("DEBUG: the target range is "+targetRange.getA1Notation());
testsheetRange.moveTo(targetRange);
testsheet.deleteRow(testrow);
// sort the Schedule Sheet
var Avals = testsheet.getRange("A1:A").getValues();
var Alast = Avals.filter(String).length;
//Logger.log("DEBUG: The last row of content (in column A) = "+Alast+", and the last column = "+testsheetLC);
var sortrange = testsheet.getRange(2,1,Alast-1,testsheetLC);
//Logger.log("DEBUG: the sort range = "+sortrange.getA1Notation());
sortrange.sort(sortSchedule);
// sort the Archive Sheet
var ATvals = targetSheet.getRange("A1:A").getValues();
var ATlast = ATvals.filter(String).length;
//Logger.log("DEBUG: The last row of content (in column A) = "+ATlast+", and the last column = "+testsheetLC);
var sortrange = targetSheet.getRange(2,1,ATlast-1,testsheetLC);
//Logger.log("DEBUG: the sort range = "+sortrange.getA1Notation());
sortrange.sort(sortSchedule);
}
else if (testsheetname === sheetNameRailcars){
// sort the sheet
var Avals = testsheet.getRange("A1:A").getValues();
var Alast = Avals.filter(String).length;
//Logger.log("DEBUG: The last row of content (in column A) = "+Alast+", and the last column = "+testsheetLC);
var sortrange = testsheet.getRange(2,1,Alast-1,testsheetLC);
//Logger.log("DEBUG: the sort range = "+sortrange.getA1Notation());
sortrange.sort(sortRailcars);
}
}
You can set up an installable trigger to run this function at 1am every morning:
function scheduleToArchive() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var schedule = sheet.getSheetByName("Schedule");
var archive = sheet.getSheetByName("Archive");
var scheduleData = schedule.getRange(2, 1, (sheet.getRange('A:A').getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow()), 28);
for (row = 2; row <= scheduleData.getNumRows(); row++){
var data = schedule.getRange(row, 1, 1, 28).getValues();
if (data[0][27] == 'Yes'){
archive.getRange((archive.getLastRow() + 1), 1, 1, 28).setValues(data);
}
schedule.getRange(row, 1, 1, 28).clear();
}
}
In your spreadsheet, when getDataRange() is run on the sheet named 'Schedule', it returns row 631 as the last row with data even though there is only data in the first 20 rows, so to get around that I've used SpreadsheetApp.Direction.DOWN instead and run that on column A.

If AND statements in google script to move data to another sheet based on change in one cell and value in another

OK, I'm fairly new at doing this. I have a google spreadsheet that I populate and then run a script to create forms are created to complete tasks. My end users mark the task as completed. I am wanting entries in the spreadsheet that change to yes AND are Validate tasks to copy to another sheet (Log). I can get the entries to copy if I just have a watcher for yes but am having problems putting the other if statement for the task in there. Here is what I have for a test...
function onChange() {
// moves a row from any sheet to an archive sheet when a magic value is entered in a column
var columnNumberToWatch = /* column D */ 4; // column A = 1, B = 2, etc.
var valueToWatch = "yes";
var sheetNameToMoveTheRowTo = "LOG";
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet2");
var cell = sheet.getActiveCell();
if ( sheet.getName() != sheetNameToMoveTheRowTo && cell.getColumn() == columnNumberToWatch
&& cell.getValue().toLowerCase() == valueToWatch) {
if (cell.getColumn() == 2 && cell.getValue() == "Validate") {
var targetSheet = ss.getSheetByName(sheetNameToMoveTheRowTo);
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sheet.getRange(cell.getRow(), 1, 1, sheet.getLastColumn())
.copyTo(targetRange);
}
}
}
It looks like you are trying to watch two separate locations with the same cell variable. You start by initializing cell as the active cell, then check that the column is #4, then in the second if statement you use cell to getColumn 2.
Maybe create a new set of variables to use for the validation check:
var activeRow=cell.getRow();
var valCheck=sheet.getRange(activeRow, 2);
then the if statement changes to:
if (valCheck.getValue() == "Validate") {

Google Sheet Copy rows to different spreadsheets

I put together this test:
https://docs.google.com/a/strandvinduspuss.no/spreadsheets/d/1WZTYq-WiQq8asXZjHMMN9bsGiQKbPKmlRrsnkAsadU4/edit?usp=sharing
The function: in the List tab, when you change the name of col B, part of the row is copied to the matching sheet.
What I need is to copy them to separate spreadsheets (one for each employer). Is that possible?
My code:
// http://victorwyee.com/gapps/move-row-on-cell-value-google-spreadsheets/
/**
* Moves row of data to another spreadsheet based on criteria in column 6.
* Each spreadsheet can be a "source" spreadsheet, or the "target" spreadsheet.
* Assumes there as many spreadsheets as there are criteria values, e.g.
* if the criteria are "R", "P", and "D", then there are three spreadsheets
* named "R", "P" and "D".
* Assumes that each spreadsheet has the same structure.
*/
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getActiveSheet();
var r = SpreadsheetApp.getActiveRange();
// Get the row and column of the active cell.
var rowIndex = r.getRowIndex();
var colIndex = r.getColumnIndex();
// Get the number of columns in the active sheet.
var colNumber = s.getLastColumn();
// Move row based on criteria in column 6, and if row is not the header.
if (colIndex == 2 && rowIndex != 1) {
// Get value from column 6, in the active row.
var status = s.getRange(rowIndex, colIndex).getValue();
// Do nothing if criteria value is not actually changed to something else.
if (s.getName() != status) {
// The target sheet is the one with the same name as the criteria value.
var targetSheet = ss.getSheetByName(status);
var target = targetSheet.getRange(targetSheet.getLastRow() +1, 1);
// Set the range and copy
// s.getRange(rowIndex, 10, 1, colNumber).copyTo(target);
targetSheet.insertRowsAfter(targetSheet.getLastRow(), 1);
s.getRange(rowIndex, 1, 1, 16).copyTo(target);
// s.deleteRow(rowIndex);
}
}
}
Have made some changes so the comments are not updated.
You can copy sheets to other Spreadsheets, but (to my knowledge, based off of the documentation and errors I've received) you can not copy a range to a separate spreadsheet. I have a similar use case to you, and my workaround is to move what I want to copy to a separate sheet, and then copy that sheet to the new/other spreadsheet:
var newSpreadsheet = SpreadsheetApp.create("I am a copy");
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Copy Me");
sheet.copyTo(newSpreadsheet);
This wouldn't work in your case if you needed to keep the same sheet/data where you are moving the data to however, since it would be overwritten. An option in that case is to copy the sheet over, and then move whatever data you want, and then delete the excess sheet.
var newSpreadsheet = SpreadsheetApp.create("I am a copy");
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var projectname = SpreadsheetApp.getActiveSpreadsheet();
sheet.copyTo(newSpreadsheet);
var dest = newSpreadsheet.getSheetByName("Copy of Sheet1").getRange("E7");
newSpreadsheet.getSheetByName("Copy of Sheet1").getRange("B1").copyTo(dest);
var notNeeded = newSpreadsheet.getSheetByName("Copy of Sheet1");
newSpreadsheet.deleteSheet(notNeeded);
Hopefully that helps get you moving in the right direction...
I believe copyTo does not work between different spreadsheets. What you can do is use getValue and setValue to copy the information over.
Instead of s.getRange(rowIndex, 1, 1, 16).copyTo(target); try:
var values = s.getRange(rowIndex, 1, 1, 16).getValue();
targetSheet.getRange(target).setValue(values);
Check out the range documentation page for more info, there are explanations of setValue and getValue there.

Need to return the displayed value not formula when copying a row between sheets

Im currently using google sheets and a script to move a portion of row when i select "ok" in a data validation column , the problem is that it copies the formulas of each cell and not the displayed value, and ideas, Im not the best at this so any help is HUGE.
/**
* Moves row of data to another spreadsheet based on criteria in column 6 to sheet with same name as the value in column 4.
*/
function onEdit(e) {
// see Sheet event objects docs
// https://developers.google.com/apps-script/guides/triggers/events#google_sheets_events
var ss = e.source;
var s = ss.getActiveSheet();
var r = e.range;
// to let you modify where the action and move columns are in the form responses sheet
var actionCol = 6;
var nameCol = 4;
// Get the row and column of the active cell.
var rowIndex = r.getRowIndex();
var colIndex = r.getColumnIndex();
// Get the number of columns in the active sheet.
// -1 to drop our action/status column
var colNumber = s.getLastColumn()-1;
// if our action/status col is changed to ok do stuff
if (e.value == "ok" && colIndex == actionCol) {
// get our target sheet name - in this example we are using the priority column
var targetSheet = s.getRange(rowIndex, nameCol).getValue();
// if the sheet exists do more stuff
if (ss.getSheetByName(targetSheet)) {
// set our target sheet and target range
var targetSheet = ss.getSheetByName(targetSheet);
var targetRange = targetSheet.getRange(targetSheet.getLastRow()+1, 1, 1, 6); //6 represents Numer of Columns to Copy
// get our source range/row
var sourceRange = s.getRange(rowIndex, 1, 1, 6); //6 represents Numer of Columns to Copy
// new sheets says: 'Cannot cut from form data. Use copy instead.'
sourceRange.copyTo(targetRange);
// ..but we can still delete the row after
// or you might want to keep but note move e.g. r.setValue("moved");
}
}
}
Add the option to sourceRange.copyTo(targetRange); as described here:
https://developers.google.com/apps-script/reference/spreadsheet/range#copyTo(Range,Object)
Please look at the docs before posting, thanks. Good luck.