Copy row to another tab, getFormulas, clearContent, setFormulas - google-apps-script

I'm trying to make google sheets automatically copy a row and paste it to the bottom of another tab when Completed is selected from a dropdown in the 8th column. Then I want it to grab the formulas from that row, clear the row, and then replace the formulas. The code works as far as copying and pasting to the other tab but I can't get it to grab the formulas, clear the row and then replace the formulas. I can't even get it to clear the row. The code is correctly executing everything but the last four lines.
This isn't my code. I got it from a website and modified it to fit my sheet. I have a very basic understanding of javascript. I need the original row to remain with just the formulas populated so that it can be reused. I'm using copyTo instead of moveTo because the row is being referenced upstream by another sheet and moveTo causes the formulas on the upstream spreadsheet to auto update to the new location of the data instead of continuing to reference the original row. Thank you for any help.
function myStorage() {
// moves a row from a sheet to another when a value is entered in a column
var sheetNameToWatch = "In Progress";
var columnNumberToWatch = 8;
var valueToWatch = "Completed";
var sheetNameToMoveTheRowTo = "Storage";
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()).copyTo(targetRange);
// this is where it stops working. I'm not sure how to grab just the row of the active cell and do the following
var clearRange = sheet.getRange(range.getRow());
clearRange.getFormulas();
clearRange.clearContent();
clearRange.setFormulas();
}
}

Try this:
function CallItSomethingElse(e) {
var sh=e.range.getSheet();
if (sh.getName()=="In Progress" && e.range.columnStart==8 && e.value=="Completed") {
var targetSheet = e.source.getSheetByName("Storage");
var targetRange = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
sh.getRange(e.range.rowStart,1,1,sh.getLastColumn()).copyTo(targetRange);
var clearRange=sh.getRange(e.range.rowStart,1,1,sh.getLastColumn());
var fA=clearRange.getFormulas();//returns a 2d array of formulas
clearRange.clearContent();
clearRange.setFormulas(fA);//you have to add that array back into this line
}
}
Because many people will take this code and try to run it from the editor just to return and tell me that they get the error of cannot find method getSheet of undefined. Let me first suggest that you can't run this code without an event object. The single e is the parameter that the event object will be place into. Without the e the program cannot run. Also unless there is a trigger to create the e the script cannot run. So if you wish to run this from the script editor then you will have to provide the event object. I don't choose to do that so when I debug these things I do it will a live trigger.

Related

Google Sheets Script - Reference a specific cell in a row

I have a sheet where when I change a specific cell to "YES", I need a template sheet to be copied to a new version and named as per the value of a cell on the current row.
I'm having trouble working out how to get the value of the first cell in the row selected. This is what I have so far (I know this is wrong):
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var currentCell = sheet.getCurrentCell();
if (currentCell = "YES")
{
SpreadsheetApp.getActiveSpreadsheet().toast("New change control sheet added to workbook.","Change Control",15);
var sourceRow = ss.getActiveRange().getRowIndex();
var tabName = ss.getRange(cell,1).getValues();
ss.getSheetByName("CCTemplate").showSheet()
.activate();
ss.setActiveSheet(ss.getSheetByName('CCTemplate'), true);
ss.duplicateActiveSheet();
ss.setActiveSheet(ss.getSheetByName('CCTemplate'), true);
ss.getActiveSheet().hideSheet();
ss.setActiveSheet(ss.getSheetByName('Copy of CCTemplate'), true);
ss.getActiveSheet().setName("CC" & tabName);
}
}
Any ideas?
function onEdit(e) {
var sh=e.range.getSheet();
if(sh.getName()=='Your Sheet Name' && e.value=="YES") {
e.source.toast="New change control sheet added to workbook.","Change Control",15);
var tabName=sh.getRange(e.range.rowStart,1).getValue();
var tsh=e.source.getSheetByName('CCTemplate');
var csh=tsh.copyTo(e.source);
csh.setName('CC'+tabName);
}
}
You should avoid using activate in your scripts especially in simple triggers where you have to finish in 30 seconds. I think this code does the same thing that you intended for your code. One significant difference is that I use the information that comes in the event object that comes with the trigger. You should add the code Logger.log(JSON.stringify(e)) and then look at the logs you will see that there is a lot of information available to you which removes the need to run extra functions to get things like a spreadsheet.
Use event objects
onEdit offers among others the event objects range and value which are helpful to retrieve the range that has been edited and its value.
Also
When you want to a cell and compare it against a value, like in if (currentCell = "YES") - you need to retrive its value (either currentCell.getValue() or just event.value) and you need to use == instead of = for comparison.
Be careful with getValues() vs getValue(). The former gives you a 2D array and is not necessary if you want to retrieve the value of a single cell.
There is no need to set your sheet to active in order to change its name.
You can rewrite your code as following:
function onEdit(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var currentCell = event.range;
var value = event.value;
if (value == "YES")
{
...
var sourceRow = range.getRowIndex();
var tabName = ss.getRange(sourceRow, 1).getValue();
...
ss.getSheetByName('Copy of CCTemplate').setName("CC" + tabName);
}
}

Need trigger to run when change occurs within cell of a certain range

I have a spread sheet where users can update a cell's within Column 'J' with notes, however when they update that cell I need my script to run.
This will then copy all the information from Columns 'I' & 'J', and paste them into another cell, then put a Vlookup formula back into 'J' where it has just taken the information from.
It may sound a complicated way about going around things, but because I have various sheets pulling information from different workbooks I need it to happen in this way.
However I have tried numerous solutions and can not get it to run when a cell is changed.
I have tried a installable on change trigger, but this works on the whole workbook and often ends up deleting the information it should be copying.
I have pasted the code I am working in below, any help would be grateful.
function onChange(e)
{
var editRange = { // J:J
top : 2,
bottom : 2000,
left : 10,
right : 10
};
// Exit if we're out of range
var thisRow = e.range.getRow();
if (thisRow < editRange.top || thisRow > editRange.bottom) return;
var thisCol = e.range.getColumn();
if (thisCol < editRange.left || thisCol > editRange.right) return;
//Where Is The Data Stored That You Want To Copy
var ss = SpreadsheetApp.openById("1ZQLKnCHD9acTXCKpTeOnJshHEz43FDPdKZ-U6Me-k_k")
var West = ss.getSheetByName("West Orders")
var WestNotes = ss.getSheetByName("West Notes")
//Ranges of The Data You Want To Copy
var CopyWestNotes = West.getRange(2, 9,West.getLastRow(),2).getValues()
//Clear The Data Before Overiding
var ClearNotes = WestNotes.getRange(2,1,WestNotes.getLastRow(),2).clear()
//Ranges Of The Data You Want To Store
var PasteWestId = WestNotes.getRange(2,1,West.getLastRow(),2).setValues(CopyWestNotes);
//Put a formula in to update Notes;
var formula = '=IFerror(VLOOKUP(i2,\'West Notes\'!A:B,2,false)," ")'
var WestFormula = West.getRange(2,10,West.getLastRow(),1).setValue(formula)
}
The onEdit event
The change trigger is not a simple trigger. You have to install it yourself. There is also no e.range in this event object. I would guess that if you had checked your executions that you would see some errors.
I modified your code to an onEdit() simple trigger. But you may have to make it installable if you're doing anything that requires permissions. And it looks like you are. So now you should be able to continue debugging your code.
function onEdit(e) {
var editRange = {top:2,bottom:2000,left:10,right:10};
var thisRow=e.range.rowStart;
if (thisRow<editRange.top || thisRow>editRange.bottom)return;
var thisCol=e.range.columnStart;
if (thisCol<editRange.left || thisCol>editRange.right)return;
var ss = SpreadsheetApp.openById("id");
var West = ss.getSheetByName("West Orders");
var WestNotes = ss.getSheetByName("West Notes");
var CopyWestNotes = West.getRange(2, 9,West.getLastRow(),2).getValues();
var ClearNotes = WestNotes.getRange(2,1,WestNotes.getLastRow(),2).clear();
var PasteWestId = WestNotes.getRange(2,1,West.getLastRow(),2).setValues(CopyWestNotes);
var formula = '=IFerror(VLOOKUP(i2,\'West Notes\'!A:B,2,false)," ")';
var WestFormula = West.getRange(2,10,West.getLastRow(),1).setValue(formula);
}
I should have mentioned that you'll need to add the id in line 7.
A simple change to line 7 of the script has eradicated the problem, instead of using .openById, I used .getActiveSpreadsheet

Moving rows between sheets based on cell edit + blank row found by column lookup not full row

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
}

Script to move entire row to a new tab and then selected columns to a different doc

I have the following script running on a document (I found on the forum and unfortunately I cannot remember who it was to give credit to but it works great), pulling the whole rows data into a new sheet then deleting the row from its current location;
function onEdit(event)
{
// assumes source data in sheet named Schedule
// target sheet of move to named Invoiced
// test column with "9 - Invoiced" is col 1
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "AllOpps" && r.getColumn() == 1 && r.getValue() ==
"Outreached")
{
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("InitialOutreach");
if(targetSheet.getLastRow()+1 == targetSheet.getMaxRows())
{
targetSheet.insertRowsAfter(targetSheet.getLastRow(), 5);
//inserts 5 rows after last used row
}
var target = targetSheet.getRange(targetSheet.getLastRow()+1, 1);
s.getRange(row, 1, 1, numColumns).moveTo(target);
s.deleteRow(row);
}
}
What I am looking for is an amendment to this and I have no idea how to make it work as my script knowledge is extremely limited, in fact, I am not even sure if it is possible but here goes.
Upon selection of "Outreached" I would like that row to do 2 things, firstly for the whole row to be copied across to the InitialOutreach tab, as it does now, and at the same time column 11,12,13 and 14 to be copied into a completely different document, for ease of use I have added it in the shared sample doc as Mail. This second copy will need to be into the respectively named columns
You can find a sample sheet here. Please, note that AllOpps and Initial Outreach are in the same document and represents the initial transfer while Mail Merge tab will be the second transfer. Once again the second transfer ie. only select columns will be in a completely different document.
https://docs.google.com/spreadsheets/d/1x_NFLXC2doWAgCWcAbxpuAh1vW-IclMZ1prN4loWdDA/edit?usp=sharing.
Once again I am not even sure running 2 different tasks on 1 trigger is even possible.
NOTE: This reply has been modified from previous versions, to deal with various changes and problems as reported by the OP.
The code below should do what you need. Note carefully the changed function name myOnEdit which also appears in the onOpen event, where it is named as an "installable" trigger. See later notes on reason for this.
function onOpen() {
ScriptApp.newTrigger('myOnEdit')
.onEdit()
.create();
}
function myOnEdit(event)
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "AllOpps" && r.getColumn() == 1 && r.getValue() == "Outreached")
{
var row = r.getRow();
var numColumns = s.getLastColumn();
// first copy and paste the "Outreached" row in entrity
var targetSheet = ss.getSheetByName("InitialOutreach");
if(targetSheet.getLastRow()+1 == targetSheet.getMaxRows())
{
targetSheet.insertRowsAfter(targetSheet.getLastRow(), 5);
//inserts 5 rows after last used row
}
var target = targetSheet.getRange(targetSheet.getLastRow()+1, 1);
var values = s.getRange(row, 11, 1, 5).getValues(); // save the values to copy to "mail Merge"
s.getRange(row, 1, 1, numColumns).moveTo(target);
Logger.log(values);
// Copy the required columns to "Mail Merge" spreadsheet"
var mailMergeSS= SpreadsheetApp.openById('--copy your sheet ID here--');
targetSheet = mailMergeSS.getSheetByName("Mail Merge");
if(targetSheet.getLastRow()+1 == targetSheet.getMaxRows())
{
targetSheet.insertRowsAfter(targetSheet.getLastRow(), 5);
//inserts 5 rows after last used row
}
target = targetSheet.getRange(targetSheet.getLastRow()+1,1,1,5);
target.setValues(values);
s.deleteRow(row);
}
}
1) Make the new target spreadsheet for the mail merge lines and make a new tag called "Mail Merge". Make
2) To find the ID of the new sheet open it and look at the URL. It should look something like this:
https://docs.google.com/spreadsheets/d/14NP6TUyj1xLCPjKsPdGS6fiO0Bvzv1u4696LFd3tA7I/edit#gid=0
The ID is the emboldened bit ie 14NP6TUyj1xLCPjKsPdGS6fiO0Bvzv1u4696LFd3tA7I in the example above,. Yours will be different, so copy and paste it so that your code line looks like:
var mailMergeSS= SpreadsheetApp.openById('14NP6TUyj1xLCPjKsPdGS6fiO0Bvzv1u4696LFd3tA7I');
To avoid this error:
SpreadsheetApp.openById([0AjqSnE_p3nFqdDN0LWpFbjFqVDRwNmFGOV91QzZrZc])
[0 seconds] Execution failed: You do not have permission to perform
that action.
.. which arises when you try to open an external sheet, you need to use "Installable Triggers". The appropriate references are here:
www.developers.google.com/apps-script/guides/triggers/installable
developers.google.com/apps-script/guides/triggers/events
Installable triggers can be installed in 2 ways:
In your CopyCols sheet, go to the script editor and select Current
Project Triggers. Add a new trigger with settings as follows: Choose
which function to run: myOnEdit Which runs at deployment:
head Select Event Source: from Spreadsheet Select
event type: onEdit Then save and restart your sheet..
or....In your code add this function lines:
function onOpen() {
ScriptApp.newTrigger('myOnEdit')
.onEdit()
.create();
}

pull data to a specific sheet within a document

I'm a new to Script editor on google spreadsheet and not much of a programmer.
I've been working with google docs for sometime now and it has become an important tool in my daily activity.
What I'm trying to do, is the following:
Seek a whole document (with severall sheet such as "1", "2", "3", and so on, corresponding to the number of days a month can hold) and if the column 7 shows a determined value (in my case, it will be RECEBER), pull all the data in that row and write onto a sheet created for this purpose.
What's happening is that I'm using the the event onEdit to trigger this function. At first sight, it would be ideal, but in my case, I copy a lot of data from other spreadsheets and the paste command does not trigger the onEdit event. Instead, I have to edit the cell manually in order to get that row copied onto the other sheet.
I could run it just once, once tha whole days of the month were filled and there were any changes left to do, but what I really want to do is to make it immediately, as soon as the content is inserted into the spreadsheet.
There's also another problem with my code, it has to be fit and adapted to all the other sheets, as the if clause only performs the full operation if the active sheet equals "1". Anyway, I believe there is a simple solution to this.
Here's the code found on the net that already took me halfway:
function onEdit(event) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "1" && r.getColumn() == 7 && r.getValue() == "RECEBER") {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("money");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).copyTo(target);
}
}
I'll appreciate all the help you can give.
Thanks in advance.
Diogo Sousa
-- Updated 12Oct --
I've changed the logics on my code, and as patt0 suggested, I run the script from the menu created. I've tried adapting the code, but I believe there's some section wrong. The script runs, but isn't writing anything at all on my target sheet.
here's the code:
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [ {name: "RECEBER", functionName: "RECEBER"} ];
ss.addMenu("Scripts", menuEntries);
}
function mustBeCopied(sheetName) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetNumber = parseInt(sheetName);
if (sheetNumber <=31 && sheetNumber >=1)
return true;
return false;
}
function RECEBER() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getActiveSheet();
var r = ss.getActiveRange();
if(mustBeCopied(s.getName()) && r.getColumn() == 7 && r.getValue() == "RECEBER") {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("money");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).copyTo(target);
}
}
the function Mustbecopied, from what I believe, only sets the range of sheets (1 to 31) eligible;
the function RECEBER will determine if the value on the column 7 will satisfy the condition (RECEBER), so it can retrieve all the row information and move it to the target sheet.
Maybe the trouble is the active sheet thing.. can I us eit in my own advantage and apply the script to the selected sheet?
Also, if I could have both option (whether to apply it to the selected sheet or to the whole document), that wouuld be great and simplify my daily work a lot!
Your question is really multiple questions in one, and I will only deal with the second part, the first part has quite a number of dimensions (how to update summary sheets effectively), which we can discuss with another question maybe.
In order to determine if the data in a particular sheet needs to be copied, you could create a simple function that returns a boolean if the sheet satisfies the condition that it is a number between 1 and 31.
function mustBeCopied(sheetName) {
var sheetNumber = parseInt(sheetName);
if (sheetNumber <=31 && sheetNumber >=1)
return true;
return false;
}
Bear in mind that a sheet with the name "28.7" would satisfy the condition.
The first line of your onEdit() script would then look like this
if(mustBeCopied(s.getName()) && r.getColumn() == 7 && r.getValue() == "RECEBER")
Let me know if this works for you.
--- Oct 14 ---
As far as I can see, the issue that remains, is that when your function was running onEdit, the range (active range) was the cell that was just edited. So the range would be only one cell.
In order for the scrip to run, you would need to highlight each cell to be copied before selecting the menu, which would be tedious to say the least
Further if you have a defined range to be copied, which is the same on each page, we can copy that range every time, or iterate through each row in the column to look for "RECEBER" and get those rows copied.
Further We can try to update your function so that iterate through all the sheets is a many similar to this.
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (var i = 0; i < sheets.length; i::) {
var s = sheets[i];
if (mustBeCopied(s.getName()) {
var range = s.getRange(someRow, 7, someNumOfRows);
for (var j = 1; j <= someNumOfRows; j++) {
if (range.getCell(j,1).getValue() == "RECEBER") {
//YOUR COPY CODE
}
}
}
}
Let me know how this works.