How to automatically copy/paste data using App Script in Google Sheets - google-apps-script

I'm trying to write some code using App Scripts that will (via a daily trigger), copy/paste data from the cells F13:G13 to the first empty cell in column I. Here is my code:
function TrackCurrentValues()
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getSheets()[0];
var lastRow = getLastRowInColumn(sheet, 'I:I');
// Logger.log('I' + parseInt(lastRow + 1));
var pasteRange = sheet.getRange('I' + parseInt(lastRow + 1) );
pasteRange.activate();
// now that we have the first empty cell in column I, paste the values we found from cells F13:G13
spreadsheet.getRange('F13:G13').copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
};
function getLastRowInColumn(sheetObj, range) {
return sheetObj.getRange(range)
.getValues().filter (String).length + 1
}
When it runs, what happens is that the data is copied from the proper location, but it's always pasted to cell A1. Moreover, the number pasted appears is prefixed with the British pound sterling character (£).
What could be wrong? It (usually) works if I run it manually. The main thing is that it doesn't find the empty cell in I:I.

This code should do what you're looking for:
function TrackCurrentValues(){
var sheet = SpreadsheetApp.getActive().getSheets()[0];
var lastRow = getLastRowInColumn(sheet, 'I:I');
var pasteRange = sheet.getRange(lastRow + 1,9,1,2);
var copyRange = sheet.getRange(13,6,1,2)
pasteRange.setValues(copyRange.getValues())
};
function getLastRowInColumn(sheetObj, range) {
return sheetObj.getRange(range).getValues().filter(String).length
}
On your £ chracter question, that range is likely formatted to display that currency symbol. To update this, select the range, go to the toolbar and select Format > Number > Specify the format you would like
Additional Thoughts:
i) You are adding one to lastRow variable twice (once in getLastRowInCOlumn function and again in pasteRange definition)
ii) I would reocmmend not using "active ranges" to store a location, instead store that range in a variable
iii) It seems your copy range was 2 columns wide but your pasteRange was only 1 column wide

Related

.getLastRow on single column but return expanded range

I have 6 columns A-F.. I want to be able to copy them from one sheet to another automatically - That I am able to do. I am using .getLastRow() on the range, but in column D I have x references that are being seen by .getLastRow() - so it is copying lots of blank cells with references on column D. What I would like to do is modify my code so it will copy my range referencing column A (e.g. Column A,B,C,E&F have 5 rows, D still has x values, but only copy the data subject to .getLastRow on column A only).
I've spent hours working on it but I'm not having a lot of luck...
/** Transfer from one sheet to another **/
function transferData() {
// references
var logSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Log');
var appendSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Archive');
// Create range strings for the rows in Log and Archive sheets
var logSheetRange = "Log!9:" + logSheet.getLastRow();
var archiveLastRow = logSheet.getLastRow() + appendSheet.getLastRow();
var archiveAppendRange = "Archive!" + (appendSheet.getLastRow() + 1) + ":" + archiveLastRow;
// Get range of data to copy
var destRange = logSheet.getRange(archiveAppendRange);
// Copy to archive
var sourceDataValues = logSheet.getRange(logSheetRange).copyTo(destRange);
// Clear range arrays after transfer
logSheet.getRangeList(['A9:A59', 'C9:H59', 'K9:L59']).activate()
.clear({contentsOnly: true, skipFilteredRows: true});
// Return to first empty cell
logSheet.getRange('A9').activate();
};

Macro: Copying values from one sheet to another, depending on sheet name, and specific columns

First time posting here, pretty new to coding Google Scripts, so I appreciate whatever help I can get.
I'm trying to design a macro and have gone through some threads but haven't found something that fits. I've tried to design the process as program-friendly as I can imagine, hopefully it'll help. The setup is as follows:
Single Spreadsheet, multiple Sheets. A MainSheet, and an ever increasing amount of secondary Sheets (in the example, there's SheetA and SheetB) Example Sheet Structure
Column Structure for MainSheet is: SheetName, Date, Column1, Column2. Column Structure for the secondary sheets is always the same: Date, Column 1, Column2. MainSheet Column Structure Secondary Sheet Column Structure
The macro is meant to copy data from Column1 and Column2 on MainSheet, and based on the information provided on the SheetName and Date columns, paste it on the respective cells on the secondary sheets. Repeat this process for each entry on MainSheet.
I here an image example of the process: Example MainSheet Example SheetA Example SheetB
Again, I'd appreciate whatever help I can get. Thank you very much!
Edit: Here is a Link to the example Google Sheets spreadsheet.
--- Valraz
This function will look for columns in the Sheets A,B... that match columns in the master and append that data for Sheet Names in ColumnA. It doesn't care how the columns in the sheets are laid out. So it runs a little slower than assuming a fix layout. But I was getting bored doing it the same way all of the time.
function copyData() {
var ss=SpreadsheetApp.getActive();
var msh=ss.getSheetByName('MainSheet');
var mrg=msh.getRange(1,1,msh.getLastRow(),msh.getLastColumn());
var vA=mrg.getValues();
var hA=vA[0];
var hObj={};
hA.forEach(function(e,i){if(e){hObj[e]=i;}});
vA.forEach(function(row,i){
if(i>0) {
var pA=[];
var sh=ss.getSheetByName(row[hObj['SheetName']]);
var shA=sh.getRange(1,1,1,sh.getLastColumn()).getValues()[0];
var shObj={};
shA.forEach(function(e,i){if(e){shObj[e]=i;}});
row.forEach(function(c,j){
if(shObj.hasOwnProperty(hA[j])) {
pA.push(c);
}
});
sh.appendRow(pA);
}
});
}
This is the straight forward way to do it.
function copyData1() {
var ss=SpreadsheetApp.getActive();
var msh=ss.getSheetByName('MainSheet');
var mrg=msh.getDataRange();
var vA=mrg.getValues();
vA.forEach(function(r,i){
if(i>0) {
var sh=ss.getSheetByName(r[0]);
sh.appendRow([r[1],r[2],r[3]],r[4]);//Modified for more columns
}
});
}
Vital question I guess, how your Sheet A and Sheet B are receiving data? Is it someone who insert data or a script who is doing it?
In Sheet A put this formula:
=QUERY(MainSheet!A:D,"select * where A='Sheet A'",-1)
In sheet B put this formula:
=QUERY(MainSheet!A:D,"select * where A='Sheet B'",-1)
And you can do this in your macro or google app script too
Here as I said before to use Query, and this automatically create your sheet and get the next date:
function CopyDataFromMainSheet() {
var spreadsheet = SpreadsheetApp.getActive();
var mysheet;
mysheet=spreadsheet.getSheetByName('MainSheet');
//8 is spare columns that may be can used
var myrange=mysheet.getRange('A1').offset(0, mysheet.getLastColumn()+8);
myrange.setFormula('=Query(MainSheet!A2:A' + mysheet.getLastRow() + ',"Select A ,count(A) Group By A",0)');
var jojo=mysheet.getRange(1,myrange.getColumn(),mysheet.getLastRow(),1).getValues();
//Clear Query
myrange.clear();
for (a=1 ;a<jojo.length;a++){
//Create New Sheet That Not found included with header
if(spreadsheet.getSheetByName(jojo[a][0])==null) if (jojo[a][0]!='') {
spreadsheet.insertSheet(jojo[a][0]);
spreadsheet.getRange('MainSheet!1:1').copyTo(spreadsheet.getSheetByName(jojo[a][0]).getRange("A1"), SpreadsheetApp.CopyPasteType.PASTE_NORMAL, false);
}
if(spreadsheet.getSheetByName(jojo[a][0])!=null) if (jojo[a][0]!='') {
var myDate=new Date('1 jan 1900');
var DestLastRow = spreadsheet.getSheetByName(jojo[a][0]).getLastRow();
if (DestLastRow>1) myDate=new Date(spreadsheet.getSheetByName(jojo[a][0]).getRange('B' + DestLastRow).getValue());
var dateStr = [
myDate.getFullYear(),
('0' + (myDate.getMonth() + 1)).slice(-2),
('0' + myDate.getDate()).slice(-2)
].join('-');
myrange.setFormula('=Query(MainSheet!A:E,"Select * where A=\'' + jojo[a][0] + '\' and B>date \'' + dateStr + '\'",0)');
var RsltRange=myrange.getA1Notation() + ":" +
mysheet.getRange("A1").offset(mysheet.getLastRow()-1,
mysheet.getLastColumn()-1).getA1Notation();
//Copy Value
spreadsheet.getRange('MainSheet!' + RsltRange).copyTo(spreadsheet.getSheetByName(jojo[a][0]).getRange("A"+DestLastRow), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
//copy format
spreadsheet.getRange('MainSheet!' + RsltRange).copyTo(spreadsheet.getSheetByName(jojo[a][0]).getRange("A"+DestLastRow), SpreadsheetApp.CopyPasteType.PASTE_FORMAT, false);
myrange.clear();
}
}
};

How to increment simple formula by 1 in Google Apps Script?

Disclaimer: I'm a Google Apps Script newbie.
I'm trying to create a timesheet in Google Sheets that lets a user clock in & clock out to log hours on a given project. I've borrowed code from a YouTube video on the general structure of setting the whole thing up.
Here's what the blank time sheet looks like. It's pretty basic:
I've created a user button (off to the right) where the user presses "Start" and cell A2 will input a timestamp. Then the user can press an "End" button, and a second timestamp, this time in B2, will appear, along with a simple calculation in C2 that measures the delta in the two timestamps, thus giving a duration of time spent on a given task or project. Here's what it looks like:
When the user needs to press "Start" again, a new timestamp appears in cell A3, and so on so forth, along with a new delta calculation for each new row.
Problem: I'm unable to get the simple delta calculation in column C to increment down each new rows so that the setFormula function doesn't contain hardcoded references to cells A2 & B2. See below code for what I have so far:
function setValue(cellName, value) {
SpreadsheetApp.getActiveSpreadsheet().getRange(cellName).setValue(value);
}
function getValue(cellName) {
return SpreadsheetApp.getActiveSpreadsheet().getRange(cellName).getValue();
}
function getNextRow() {
return SpreadsheetApp.getActiveSpreadsheet().getLastRow() + 1;
}
function addStartRecord (a) {
var row = getNextRow();
setValue('A' + row, a);
}
function addEndRecord (b, c) {
var row = getNextRow()-1;
setValue('B' + row, b);
setValue('C' + row, c);
}
function punchIn() {
addSRecord(new Date());
}
function punchOut() {
addERecord(new Date(), '=B2-A2');
}
The problem is with the punchOut() function there at the bottom. Any idea on the best way to increment this delta calculator down each new row?
Note: I saw a pretty good answer to a similar question here, but the code is throwing an error in the script editor after the line containing data[i] = ['=A' + i+1.toString() + ' + 1 ' ]. Also, I don't want to set a definitive last row for the delta calculation (such as 20 in this example). I'd want the user to be able to record as many new start/end times for a project as they'd want.
Edit: Here's a link to the timesheet so you can test the code.
Try modifying your punchOut method like this:
function punchOut() {
var ss = SpreadsheetApp.getActiveSheet();
var row = ss.getLastRow();
addEndRecord(new Date(), '=B' + row + '-A' + row);
}
I tested it in the sheet and it worked well.
setFormula() - this enables you to describe the formula to be inserted into column C.
The following is two simple functions that handle "Punch in" and "Punch Out" (with its calculation).
function so5695101401in() {
// punchin
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var lR = sheet.getLastRow();
// Logger.log("DEBUG: the last row is "+lR);
var punchinRange = sheet.getRange(lR+1, 1);
// Logger.log("DEBUG: the punchinRange = "+punchinRange.getA1Notation());
punchinRange.setValue(new Date());
}
function so5695101401out() {
// punchout
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var lR = sheet.getLastRow();
//Logger.log("DEBUG: the last row is "+lR);
var punchoutRange = sheet.getRange(lR, 2);
// Logger.log("DEBUG: the punchoutRange = "+punchoutRange.getA1Notation());
punchoutRange.setValue(new Date());
var timeElapsed = sheet.getRange(lR, 3).setNumberFormat("hh:mm:ss");
timeElapsed.setFormula("=B2-A2");
}
setFormula
I use a workaround for this problem, via app script copy the cell with the formula to de new row or range!.
for you problem:
var formula1 = sheetDatos.getRange(lastRow, 3); //get the formula
var copyRange = sheetDatos.getRange(lastRow+1, 3);
formula1.copyTo(copyRange);
for me is more easy in this way, try to do in sheet to understand how this work.
you need a initial formula to go in this way ;)

Google Script to convert a Google Sheet to Excel and Email it

Every week I have a few different Google Sheets that I have to download as excel, delete hidden rows, fix data and then email out.
This can be very time consuming, so I would like to make/deploy a script that automates this process.
I did a ton of searching to try to get this to work, however I am running into a few issues.
First, the data in the sheet is pulled via a query/importrange formula, so once downloaded to excel all data looks like the following:
=IFERROR(__xludf.DUMMYFUNCTION("""COMPUTED_VALUE"""),"No")
This prevents being able to filter any of the data. Currently I'm fixing this by copying all data in excel and pasting values only. I'd like the excel file to be corrected so that I don't have to do that.
Also, I would like to be able to email the converted Excel file.
I found the following code that works for this:
function getGoogleSpreadsheetAsExcel(){
try {
var ss = SpreadsheetApp.getActive();
var url = "https://docs.google.com/feeds/download/spreadsheets/Export?key=" + ss.getId() + "&exportFormat=xlsx";
var params = {
method : "get",
headers : {"Authorization": "Bearer " + ScriptApp.getOAuthToken()},
muteHttpExceptions: true
};
var blob = UrlFetchApp.fetch(url, params).getBlob();
blob.setName(ss.getName() + ".xlsx");
MailApp.sendEmail("amit#labnol.org", "Google Sheet to Excel", "The XLSX file is attached", {attachments: [blob]});
} catch (f) {
Logger.log(f.toString());
}
}
This mostly works, but again runs into the issue above of the importrange data being incorrect and showing as the =IFERROR(__xludf.DUMMYFUNCTION.
There are also two columns (A and J) that are hidden and need to be deleted.
Additionally, I would love to be able to change the email address, subject and message via an GUI. But this is much less important and not needed.
I know that this is a big ask, but any help would be amazing!
This script addresses part of your question.
Create a copy of a sheet, and in the process convert formulas to values
Delete columns
Note: There are several options for converting the formula to values.
So far as changing the email address, subject and message via a GUI, I suggest using a custom sidebar.
function so5665379502(){
/*
// The purpose of this script is to copy the content of one sheet to another;
// converting formulas to values
// and deleting columns
*/
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetname = "ejbsheet01";
var sourcesheet = ss.getSheetByName(sheetname);
var destname = "ejbsheet02";
var destinationSheet = ss.getSheetByName(destname);
sourcesheet.activate();
var range = sourcesheet.getDataRange();
var rFC = range.getColumn();
var rLC = range.getLastColumn();
var rFR = range.getRow();
var rLR = range.getLastRow();
//Logger.log("DEBUG: the data range = "+range.getA1Notation()+", column 1 = "+rFC+", the last column = "+rLC+", first row = "+rFR+", last row = "+rLR);
// syntax copyValuesToRange(sheet, column, columnEnd, row, rowEnd)
range.copyValuesToRange(destinationSheet, rFC, rLC, rFR, rLR);
// delete column J first so that other column numbers don't chnage.
// column J = 10
destinationSheet.deleteColumn(10);
// Delete Column A
// column A = 1
destinationSheet.deleteColumn(1);
}
BEFORE
Note: Columns A and J are hidden
AFTER
Note: The hidden Columns are gone and the data ais values, not formula.

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