Google Sheets Script - Move Rows from One Tab to Another - google-apps-script

I'm trying to modify the code from the below forum post to help fit my needs:
https://productforums.google.com/forum/#!topic/docs/ehoCZjFPBao/discussion
function onEdit() {
// 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 = "Transfers";
var columnNumberToWatch = 15; // column A = 1, B = 2, etc.
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());
}
}
What this code does is moves a row from one tab to another when a specific value is entered (in the code above, it moves a row from "Transfers" to "Archive" when the word "yes" is entered in column 15 (Column O).
My needs are a little bit different. I cannot have a user moving one row at a time because there may be multiple rows involved and combined together, they will need to meet certain criteria (e.g. do the amounts in all rows balance to 0, is the same account used, etc.). This I believe is a rudimentary data validation.
Therefore, I tried to make a formula so that if I entered the word yes in cell F8, it populates the word "Send" (used to be "yes", changed for clarity) to column O. If the word yes is entered in cell F8, then every row with the word "Send" should be moved to the Archive Column. The problem is, the above code relied on a function called OnEdit, and populating the rows with the word Send via formula does not trigger the script to run. The above code needs the user to manually enter the correct keyword in order to move the rows over.
Can someone help to modify or rewrite the code so that it looks for a user to manually type in the keyword "yes" in cell F8, and then have it move any row that had the word "Send" populated by a formula in Column O?
The below Google Sheet is a simplified example of what I'm trying to do. Additional comments can be found on the "MASTER - DO NOT EDIT" tab.
https://docs.google.com/spreadsheets/d/1iajS90qvwEOGVnl2lpDbVtcI532OO8n4NLZEBDUpVzA/edit#gid=398066315
Thanks for looking. If anyone needs additional info, please let me know.
~~~~~~~~~~~~~~~~~~~~~~~~~~~
EDIT: I tried out Cooper's code below, and it seems to work. I'm a novice at this, but I've managed to add a few things to make it run onEdit, and to delete the initial trigger word.
An example of this slightly tweaked code running can be found below:
function onEdit() {
archiveRows();
clearCells();
}
function archiveRows()
{
/*installable trigger rows 10-13 https://developers.google.com/apps-script/guides/triggers/installable
if(!projectTriggerExists()
{
ScriptApp.newTrigger('myFunction')
.forSpreadsheet(ss)
.onOpen()
.create();
}//by wrapping the trigger creation like this you don't have to worry about creating unwanted triggers.
*/
var ss=SpreadsheetApp.getActive();
var sh0=ss.getSheetByName('Transfers'); //sh0 = Transfers tab
var rg0=sh0.getDataRange(); //rg0 = Range of sh0, This is functionally equivalent to creating a Range bounded by A1 and (Range.getLastColumn(), Range.getLastRow()). https://developers.google.com/apps-script/reference/spreadsheet/sheet#getdatarange
var sh1=ss.getSheetByName('Archive'); //sh1 = Archive tab
var vals=rg0.getValues(); //Returns the rectangular grid of values for this range. Returns a two-dimensional array of values, indexed by row, then by column. The values may be of type Number, Boolean, Date, or String, depending on the value of the cell. Empty cells will be represented by an empty string in the array. Remember that while a range index starts at 1, 1, the JavaScript array will be indexed from [0][0]. https://developers.google.com/apps-script/reference/spreadsheet/range#getvalues
for(var i=vals.length-1;i>11;i--) //When deleting rows it is better to start from the bottom otherwise deleted rows will mess up your loop indexing
{
if(vals[i][14]=='SEND') //If column 14 in the range has the word 'SEND' then run the next lines of code. 14 is column O. Column 0 is 15, but see note above regarding JavaScript array being indexed from [0][0]
{
sh1.appendRow(vals[i]);
sh0.deleteRow(i+1);//indexes start at zero but rows start at one
}
}
}
//This could be accomplished with SpreadsheetApp.getActive().getActiveSheet().clear();
function clearCells()
{
//https://stackoverflow.com/questions/9268570/i-need-a-button-to-clear-cells-in-a-google-spreadsheet
var ss=SpreadsheetApp.getActive();
ss.getRange('A1:A1').clearContent()
}
function projectTriggerExists(functionName)
{
if(functionName)
{
var allTriggers=ScriptApp.getProjectTriggers();
var funcExists=false;
for(var i=0;i<allTriggers.length;i++)
{
var trigger=allTriggers[i];
if(allTriggers[i].getHandlerFunction()==functionName)
{
funcExists=true;
break;
}
}
}
return funcExists;
}
The nature of the spreadsheet is that I will need to duplicate the "Transfers" tab in the original post a dozen times so that it can be used by multiple people at once (data being entered in by the user essentially over 12 "forms"). All the data put into these dozen "Transfers" tabs should rout to the same "Archive" tab. However, I'd also like to build in a rudimentary routing system, so that instead of having everything rout to one tab, different code words send the lines to different "Archive" tabs. For instance, "OK+Send+Staff1" sends the line to a tab called Staff1Archive, "OK+Send+Staff2" sends to a tab called Staff2Archive, "OK+Send+Staff3" sends to a tab called Staff3Archive, etc.
Can anyone help with this code as well?

I think this will do it for you.
function archiveRows()
{
var ss=SpreadsheetApp.getActive();
var sh0=ss.getSheetByName('Transfers');
var rg0=sh0.getDataRange();
var sh1=ss.getSheetByName('Archive');
var vals=rg0.getValues();
for(var i=vals.length-1;i>11;i--)
{
if(vals[i][14]=='SEND')
{
sh1.appendRow(vals[i]);
sh0.deleteRow(i+1)
}
}
}

Related

Changing info on a different sheet in the same spreadsheet

I have two ranges of equal size on different sheets in the same spreadsheet. I am trying to find a row (based off of user input) in the first sheet and then use that index to modify a table in the second sheet that counts how many times that certain index has been used before (to make a nice looking pie chart).
This code runs but will not produce results on the second sheet. I've gone through the debugging process and my best guess is that for some reason, my for in loop is not running through. Attached is my code that takes in the beforementioned index and attempts to perform the second half of my goal.
function acceptToEncounterChart(ghostrow) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
SpreadsheetApp.setActiveSheet(ss.getSheets()[1]);
ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Average Encounter Chart");
var range = sheet.getRange("B3:B14")
for(var i in range) {
if(ghostrow == i) {
var before = range[i][0].getValue()
range[i][0].setValue(before + 1);
}
}
SpreadsheetApp.setActiveSheet(ss.getSheets()[0]);
};
Explanation:
I am not entirely sure what is your goal.
However, here is some fixes / improvements starting from the beginning:
You define 2 times the same variable ss with exactly the same value.
You don't need to set the active sheet, if your goal is to just get the sheet, therefore this line is redundant:
SpreadsheetApp.setActiveSheet(ss.getSheets()[1]);
Variable range is not an array but a range object. You can't index it and therefore you can't also use a for loop to iterate over a single object. For the same exact reason, the code inside the if statement is wrong, you can't index range. But you don't see any errors because the if statement evaluates to false.
In JavaScript and in many other programming languages, array indexes start from 0. Since your range starts from cell B3 or row 3, you need to use i+3 to match the data with the range.
For the same reason as the previous point, ghostrow is an index, not a row. The if statement compares an array index i with ghostrow, so ghostrow should not be confused with the actual sheet row. For example, if you choose ghostrow=5 then the current script will increment the value of the cell B8 (remember i+3) by 1.
Solution:
Here is a workable code snippet:
function acceptToEncounterChart(ghostrow) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Average Encounter Chart");
var data = sheet.getRange("B3:B14").getValues().flat();
data.forEach((v,i)=>{
if(ghostrow == i){
sheet.getRange(i+3,2).setValue(v+1)
}
});
ss.setActiveSheet(ss.getSheets()[0]);
}
Related:
Please explore the official google apps script documentation.

Google Sheets - Identify duplicates and overwriting data

I've heavily edited the original question I posted, as i have solved some of the issue myself. I'm now stuck on just one thing.
function payINVOICE() {
var ss = SpreadsheetApp.getActive();
var ds = SpreadsheetApp.openById("14imcEob2qIZbH6AjGYtf16MJxbnfkhQn1ae4jR-Nzq4");
var srcSheet = ss.getSheetByName("INVOICE_ENTRY");
var dstSheet = ds.getSheetByName("INVOICE_ENTRY");
var data_range = srcSheet.getRange('B4:J100');
var data_data = data_range.getValues();
var data_clean = data_data.filter(function (r) {return r[1]});
var clear_range = srcSheet.getRange('B4:I100');
var lr = dstSheet.getLastRow();
dstSheet.getRange(lr+1, 2,data_clean.length,9).setValues(data_clean);
clear_range.clear();
}
This code checks the range B4:J100 for a value in Column B.
If there is a value and the script is run, it copies those rows onto dstSheet.
My role is marking invoices as paid or not.
The dstSheet will already contain the data, which is pulled back into the srcSheet with a query. Column K is not part of the original query.
If I mark a row as "PAID" in column K on the srcSheet, I want the code to take the data_data variable and overwrite what is already in the dstSheet, so that the query then pulls the data back into srcSheet with column J then showing "PAID".
It means I can then change column K to "NOT PAID", run the script again and it will over-write the "PAID".
This makes better sense than my last post and I am so close to achieving what I need, just stuck on this last bit.
If you simply want to monitor the changes between the two mentioned sheets, it would be much easier to use an onEdit(e) trigger which will tell you which cell has been edited.
Snippet
function payINVOICE(e) {
var srcSheet = SpreadsheetApp.getActiveSheet(); //gets the active sheet which is supposed to be source sheet
var dstSheet = SpreadsheetApp.openById('DEST_SHEET_ID').getSheetByName('INVOICE_ENTRY'); //gets the destination sheet
if (e.range.getSheet().getName() == 'INVOICE_ENTRY' && e.range.getColumn() == 11) { //e specifies where the edit has been made - therefore this if condition checks if the edit is in the INVOICE ENTRY sheet and if the column is the K column
var row =e.range.getRow(); //this gathers the row at which the edit has been made
var data = srcSheet.getRange(row, 2, 1, 10).getValues(); //this gathers the data corresponding to the row at which the edit has been made
dstSheet.getRange(row, 2, 1, 10).setValues(data); //this sets the data into the corresponding row in the destination sheet
}
Explanation
The above code uses the onEdit(e) installable trigger and the e event object. In this way, when an edit is being made on the srcSheet on the 11th column (aka K column) and the sheet name is "INVOICE_ENTRY", then the row at which the change has been made is kept in the row variable. Afterwards, the corresponding row of data is kept in the data variable; the getRange(row, 2, 1, 10) references the range for the row at which the change on the K column has been made. In order to update the dstSheet, the data value is set to the according range using setValues(data).
Installing the trigger
To make the payINVOICE(e) function trigger on an edit action, you need to install an onEdit trigger.
This is being done by accessing the project's triggers by clicking this icon:
After that, you just need to create a new trigger by clicking the Add trigger button and create a trigger with the following settings:
Trying the function
In order to try the behavior for this, you just need to make an edit on the srcSheet on the K column and this change will be reflected in the destSheet.
Note
The ranges that have been used in this script are chosen considering the fact that:
K column consists of the PAID/NOT PAID text;
The srcSheet and the dstSheet have the data wanted in the same ranges.
You might need to customize these according to your sheet and add the needed formulas/filters you have mentioned.
Reference
Apps Script Installable Triggers;
Apps Script Event Objects.

Get Range from GetRangeByName, activate range

My goal is to automate adding 10 rows to a Google Sheet used by a nonprofit organization's business and then replicate the formulas sequences needed in the newly created rows. I had all the code working to accomplish the task and prevent users from messing up the spreadsheet formulas when they manually insert rows. However, the code time out due to the number of rows in the spreadsheet with the looping use of getRange(). My new approach is to jump to a named cell as a starting point instead of the the really slow looping cell search.
I have created a name "EndData", read all the stuff I can find online, trialed and errored the syntax for hours to get the named_cell range into myrange and then activate the range on the worksheet...
Here is the current coding attempt (which leaves the cursor at the top of the column and an
"TypeError: Cannot find function getRangeByName in object Sheet. (line 170, file "macros")"
//Get EOD range, select, index up 3 rows to start row insertions
function getEOD() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
Logger.log(ss); //lOG is not helpful, says, "sheet", not SheetName
var MyRange = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRangeByName("EndData");
Logger.log(MyRange); //lOG is not helpful, says, "Range", not RangeAddress
//Activate the named cell, moves with the spreadsheet
MyRange.activate();
};
Had a new idea after I asked for help, here's the working code that gets the job done:
//Get EOD range, select, index up 3 rows to start row insertions
function getEOD() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
//Logger.log(ss); //lOG is not helpful, says, "sheet", not SheetName
var rg = SpreadsheetApp.getActiveSpreadsheet().getRangeByName('EndData');
if (rg != null) {
Logger.log(rg.getNumColumns());
}
//Logger.log(rg); //lOG is not helpful, says, "Range", not RangeAddress
//Referenced the Named the EOD cell
//Future, Trying to create a debug status bar
//SpreadsheetApp.getActiveSpreadsheet().toast("looking at row" & i "now")
//Activate the named cell, which moves with spreadsheet growth, down
rg.activate();
//Uncomment for testing purposes, places a Yes on the row 4 columns over
//sheet.setActiveRange(sheet.getRange(0, 4)).setValue('Yes');
//turned off durning testing, writes in data range with this trial code
//Reposition from named cell to insert lines location
ss.getRange(sheet.getCurrentCell().getRow() -2, 1, 1,
sheet.getMaxColumns()).activate();
//Insert ten lines, copy and paste special formulas only
Insert_10_Lines()
//https://stackoverflow.com/questions/59772934/get-range-from-
getrangebyname-activate-range
};
My answer is I have tenacity, I try to limit the variables one at a time to prove how things really work, moved the code that did work in the test script file to a production script file and it didn't work for copy of what I had in test, went back to the test script and it didn't work either, after it had... There are variables in play that seem to be happening at the scripting, savings and running steps that change the interactive responses I am getting. Trial and error again through the production environment came up with a working combination by going to the simples code suggested and getting a combination that works. Here is the the code that is running in product to the main need of my question...
function InsertRows() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
//Get range from named cell
var range = SpreadsheetApp.getActiveSpreadsheet().getRangeByName("EOD");
if (range != null) {
Logger.log(range.getNumColumns());
}
//Select the cell as a starting point for code to follow
range.activate(); //start location cell is active and row is selected
Thanks for those that responded for the help! Looks like it is going to take a while to recognize the patterns and figure out what to do to get consistent results in a timely manner...
Try this:
function getEOD() {
var ss=SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var MyRange=ss.getRangeByName("EndData");
MyRange.activate();
}

Google app script: find first empty cell in a row

Im learning Google app script while building a dashboard. I'm collecting data from several sheets. My goal is to see by how many rows each sheet grows every week. This gives me insight in how my business is doing.
I can get the length of all the sheets I want to check, however I cant find any code which helps me to find the first empty cell in a specific row. I want to place the length of each sheet there (in my dashboard datacollection sheet) to create a graphs later on.
What I have is:
var range = ss.getRange(2, 1, 1, 1000);
var waarden = range.getValues();
Logger.log(waarden);
var counter = 0
for (var j = 0; j < ss.getLastColumn(); j++) {
Logger.log(waarden[0][j]);
if (waarden[0][j] == ""){
break
} else {
counter++;
}
Logger.log(counter);
}
This works but I can't image this being the best solution (or quickest solution). Any tips in case my length goes beyond 1000 without me noticing it (although it would take a couple of years to do so in this case ;) )?! Why does getLastColumn() behave so much different than getLastRow()?
Thanks for helping me learn :)
*** edited I figured out I have to use if (waarden[0][j] === ""){ with three = otherwise if my sheet in the row that I use as a check has a length of 0 than this is also counted as empty with two =operators.
Try indexOf()
function firstEmptyCell () {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
var range = ss.getRange(2, 1, 1, ss.getMaxColumns());
var waarden = range.getValues();
// Get the index of the first empty cell from the waarden array of values
var empty_cell = waarden[0].indexOf("");
Logger.log("The index of the first empty cell is: %s", empty_cell);
}
This will give you the column position of the empty cell starting from a 0 index. So if the returned index is 4, the column is "E".
edit: As for the getLastColumn() question; you could use getMaxColumns() instead. Updated code to get all columns in the sheet.

I have a pseudocode, but can't code yet

thank you and sorry for my incredibly unexperienced question in advance. So, I want to make a code and I know what I want it to do, I just don't know how to program. What I need is:
function GenPre()
1.- delete range Presupuesto!A12:C42
2.- copy range Imp!A2:Imp!C33 VALUES in Presupuesto!A12:Presupuesto!C42 (Imp cells are formulas, and I want to copy just the values)
3.- show only used rows in column A in Presupuesto!A12:A42 (consider some rows will be already hidden, so unhiding them first would be an idea)
4.- go to sheet Presupuesto (once I do this function, I want to end up on the sheet Presupuesto
end Generar
This function will be runned by a button in another sheet in the same spreadsheet.
and so far, I have this:
function GenPre() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetbyname(Presupuesto);
//next step is to select and delete the content of the range on the sheet
}
I know I'm asking for much, I just can't find much about selecting defined cells... and I really don't know how to program yet.
Thanks a bunch!!
Edit
So, I started tweaking with what k4k4sh1 answered and got this (AND reading other posts on hiding rows containing "x" on a given cell):
function GenPre() {
var sheetp = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Presupuesto') //name a variable to the sheet where we're pasting information
var sheetc = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Imp') //name a variable to the sheet frome where we're copying information
sheetp.getRange('a12:c41').clearContent() //delete all values in the range where we're copying
sheetc.getRange('A2:C31').copyValuesToRange(sheetp,1,3,12,41); //copy from source range to destination range
sheetp.showRows(12,41); //make sure all rows in the destination range are shown
for( i=12 ; i<=41 ; i++) {
if (sheetp.getRange('A'+i).getValue() == '') { // status == ''
sheetp.hideRows(i);
}
}
}
Te script is running how it should, but now, I want it to run faster (takes 12 seconds to run, when it doesn't really look that heavy), and is there a function to switch my view to sheetp? thank you all!
You're asking us to do all the work :)
Let's start from your piece of code:
the method .getSheetByName(shName) accepts a string as argument, so you should change it to
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Presupuesto');.
Mind that JavaScript is case-sensitive, so .getSheetbyname is not the same as .getSheetByName().
According to Sheet Class Reference use sheet.getRange() to get your Range Object. Take a look to Range Class Reference: to clear the range content including formats use .clear(), to clear just the content leaving the formatting intact use .clearContent().
To hide unused rows try:
function hideRows(sheetName, column) {
var s = SpreadsheetApp.getActive().getSheetByName(sheetName);
s.showRows(1, s.getMaxRows());
s.getRange(column)
.getValues()
.forEach(function (r, i) {
if (r[0] == '') {s.hideRows(i + 1);}
});
}
// hideRows('Presupuesto', 'A12:A42');