getLastRow() doesn't work correctly in my script - google-apps-script

Ok so this doesn't work:
var range = mainList.getRange(1, 1, 50, 5).getValues();
var numRows = range.getLastRow()-1;
But this does
var numRows = mainList.getLastRow()-1;
range is CLEARLY a range? So why I am I getting a typeError?
TypeError: Cannot find function getLastRow in object DATA DATA DATA DATA DATA

In this situation we can tell that the variable range is actually a 2D array of data from the error, so there is no method getLastRow(). There is a range.length however.
var range = mainList.getRange(1, 1, 50, 5).getValues();
var numRows = range.getLastRow()-1;
It's actually kind of hard to see the value of your second example since we can't tell where mainList came from. Take a look at mcve
If mainList is a spreadsheet then we can go to the spreadsheet documentation here and in the example here we see that we can define the range in A1Notation by specifying the name of the sheet( sometimes called tab) as so:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var range = ss.getRange("Invoices!A1:D4");
Personally I usually like to take the time to do something like this:
var spreadsheet = SpreadsheetApp.getActive(); // or SpreadsheetApp.openById('ssid'); I Google Drive the ids are the primary key to locating files not names. And you can get the id of a spreadsheet right of of the url whenever you open it up.
var sheet = spreadsheet.getSheetByName('sheetname');
var range = sheet.getRange(1,1,50,5);
var valuesArray = range.getValues();
Then you can loop through values array as shown below:
for(var i=0;i<valuesArray.length;i++){
for(var j=0;j<valuesArray[i].length;j++){
var cellvalue=valuesArray[i][j];
}
}

Related

Improving Apps Script flexibility by using a column of sheet data instead of hard-coded IDs

Background: My coworkers originally each had a worksheet within the same Google Sheets file that makes a lot of calculations (and was getting unusable). Now, everyone has their own (known) Google Sheets file. To run the same calculations, we need to consolidate all that data into a master sheet (image ref below). We tried =importrange(...), but it's too heavy and breaks often (i.e., Loading... and other unfilled cells).
I've written some code to do this import, but right now its only manual: manually repeating the code and manually add the sheet IDs and changing the destrange.getRange(Cell range) each time. We have 80+ analysts, and fairly high turnover rates, so this would take an absurd amount of time. I'm new to Sheets and Apps Script, and know how to make the script use a cell as reference for a valid range or a valid ID, but I need something that can move a cell down and reference the new info.
Example:
Sheet 1 has a column of everyone Sheet ID
Script Pseudocode
get first row's id(Row 1), get sheet tab, get range, copies to active sheet's corresponding row(Row 1).
gets second row's id(Row 2), get sheet tab, get range, copies to active sheet's corresponding row (Row 2)
etc.
My script understanding is way to low to know how to process this. I have no idea what to read and learn to make it work properly.
function getdata() {
var confirm = Browser.msgBox('Preparing to draw data','Draw the data like your french girls?', Browser.Buttons.YES_NO);
if(confirm == 'yes'){
// I eventually want this to draw the ID from Column A:A, not hard-coded
var sourcess = SpreadsheetApp.openById('1B9sA5J-Jx0kBLuzP5vZ3LZcSw4CN9sS6A_mSbR9b26g');
var sourcesheet = sourcess.getSheetByName('Data Draw'); // source sheet name
var sourcerange = sourcesheet.getRange('E4:DU4'); // range
var sourcevalues = sourcerange.getValues();
var ss = SpreadsheetApp.getActiveSpreadsheet(); //
var destsheet = ss.getSheetByName('Master Totals'); //
// This range needs to somehow move one down after each time it pastes a row in.
var destrange = destsheet.getRange('E4:DU4');
destrange.setValues(sourcevalues); // Data into destsheet
}
}
Any suggestions are greatly appreciated!
Thanks to tehhowch for pointing me in the right direction!
function getdata() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var destsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Master Totals');
var confirm = Browser.msgBox('Drawing Data','Would you like to update the sheet? It may take 2 to 5 minutes.', Browser.Buttons.YES_NO);
if(confirm =='yes'){
var lr = ss.getLastRow();
for (var i = 4; i<=lr; i++) {
var currentID = ss.getRange(i, 1).getValue();
var sourcess = SpreadsheetApp.openByUrl(currentID);
var sourcesheet = sourcess.getSheetByName('Data Draw');
var sourcerange = sourcesheet.getRange('E4:DU4');
var sourcevalues = sourcerange.getValues();
var destrange = destsheet.getRange('E' +i+':'+ 'DU'+ i);
destrange.setValues(sourcevalues);
I just had to learn how to use a variable loop.
Edit: thanks also to Phil for making my question more presentable!
Now that you've figured out one way to do it, I'll offer an alternative that uses batch methods (i.e. is much more time- and resource-efficient):
function getData() {
var wb = SpreadsheetApp.getActive();
var ss = wb.getActiveSheet();
var dest = wb.getSheetByName('Master Totals');
if (!dest || "yes" !== Browser.msgBox('Drawing Data', 'Would you like to update the sheet? It may take 2 to 5 minutes.', Browser.Buttons.YES_NO))
return;
// Batch-read the first column into an array of arrays of values.
var ssids = ss.getSheetValues(4, 1, ss.getLastRow() - 4, 1);
var output = [];
for (var row = 0; row < ssids.length; ++row) {
var targetID = ssids[row][0];
// Open the remote sheet (consider using try-catch
// and adding error handling).
var remote = SpreadsheetApp.openById(targetID);
var source = remote.getSheetByName("Data Draw");
var toImport = source.getRange("E4:DU4").getValues();
// Add this 2D array to the end of our 2D output.
output = [].concat(output, toImport);
}
// Write collected data, if any, anchored from E4.
if(output.length > 0 && output[0].length > 0)
dest.getRange(4, 5, output.length, output[0].length).setValues(output);
}
Each call to getRange and setValues adds measurable time to the execution time - i.e. on the order of hundreds of milliseconds. Minimizing use of the Google interface classes and sticking to JavaScript wherever possible will dramatically improve your scripts' responsiveness.

Looping through sheets in Google Spreadsheet and using getSheetName()

I'm writing a script to loop through each sheet in one spreadsheet and copy data from specific cells into a corresponding sheet on another spreadsheet. I am getting an error on line 18 of the below code, however, stating that it can't call the getLastRow method of null. I used a couple of Logger.log lines to check my variables and see that targetSheet is coming back as null. Any advice on what I've got wrong?
//Export each sheet's daily data to another sheet *Test Version*
function exportReports() {
var sourceSS = SpreadsheetApp.getActiveSpreadsheet();
//Open Back Production Record *Test Version*
var targetSS = SpreadsheetApp.openById("1ZJKZi-UXvqyGXW9V7KVx8whxulZmx0HXt7rmgIJpUY4");
var allSourceSheets = sourceSS.getSheets();
//For-Loop to loop through hourly production sheets, running the move data for-loop on each
for(var s in allSourceSheets){
var loopSheet = allSourceSheets[s];
var loopSheetName = loopSheet.getSheetName();
var targetSheet = targetSS.getSheetByName(loopSheetName);
Logger.log(s);
Logger.log(loopSheet);
Logger.log(targetSheet);
Logger.log(loopSheetName);
var targetRow = targetSheet.getLastRow()+1;
var currentDate = Utilities.formatDate(new Date(), "GMT-5", "MM/dd/yy");
targetSheet.getRange(targetRow, 1).setValue(currentDate);
//For-Loop to move data from source to target
for(var i=6;i<=10;i++){
var sourceRange = sourceSheet.getRange(i, 2);
var targetRange = targetSheet.getRange(targetRow, i-4);
var holder = sourceRange.getValue();
targetRange.setValue(holder);
}
}
}
Per the documentation on getSheetByName, if the target sheet name does not exist, then you get null as a return value.
getSheetByName(name)
Returns a sheet with the given name.
If multiple sheets have the same name, the leftmost one is returned. Returns null if there is no sheet with the given name.
So, the desired sheet with name specified by loopSheetName does not exist in the target workbook. Perhaps someone has created a new sheet, or renamed an existing sheet in the source workbook.
You haven't asked about it, but you can improve the performance of your copy code as well, by reading the inputs as a multi-row range array, creating a row array to hold the results, and writing that once:
var sourceData = sourceSheet.getRange(6, 2, 5, 1).getValues(); // (6,2) through (10, 2)
var output = [];
// Transpose row array to column array (could use other functions, but this is easier to understand)
for(var i = 0; i < sourceData.length; ++i) { output.push(sourceData[i][0]); }
targetSheet.getRange(targetRow, 2, 1, output.length).setValues([output]); // i0 = 6 -> 6 - 4 = 2

Copy a row to new sheet based on value in a cell

I'm trying to create a Google Script that copies rows from one Google Sheet into different sheets based on the value of a cell.
The cell value are states in the United States. The master spreadsheet has data from a registration form that is being imported into it all the time. When a new registration happens (and the data is imported into the master sheet), I'd like the script to run and copy that row of data into the appropriate state sheet.
Here's where I'm at:
Get master sheet
Find the last row
Get the value of the cell in the column "state"
Copy that row into one of 50 different sheets depending on what state it is.
Run the script every time the master sheet is updated (via an API).
Any help would be appreciated. I'm definitely a newbie when it comes to scripting and this is just hurting my head.
Here's the code I have so far:
function myFunction() {
// Get Source Spreadsheet
var source = SpreadsheetApp.getActiveSpreadsheet();
// Get Source Sheet from Spreadsheet
var source_sheet = source.getActiveSheet();
// Get Active Range from Sheet
var lastRow = sheet.getLastRow();
// Get the Value of the State Cell
var cellValue = Range.getCell(lastrow,3);
// Copy Last Row to Appropriate State Sheet
if ( cellValue == 'Alaska') {
var target = SpreadsheetApp.openById("");
var target_sheet = target.getSheetByName("Sheet1");
target_sheet.appendRow(lastRow);
}
}
With this code:
// Get Active Range from Sheet
var lastRow = sheet.getLastRow();
The getLastRow() method returns an integer. Then further down in your code, you are using the lastRow variable as the data for appendrow() which won't work;
target_sheet.appendRow(lastRow);
The code should probably be something like this:
var target = SpreadsheetApp.openById("");
var target_sheet = target.getSheetByName("Sheet1");
var lastRowOfData = source_sheet
.getRange(source_sheet.getLastRow(), 1, 1, source_sheet.getLastColumn())
.getValues(); //Returns a two dimensional array
var oneD_Array = lastRowOfData.join().split(","); //Creates a one D array
target_sheet.appendRow(oneD_Array);
The appendRow() method takes a one dimensional array. The getValues() method returns a 2 Dimensional array, so it must be converted. Because you are only getting one row of data, it's easy to convert. The outer array only has one inner array. The one inner array has all the cell values of the row.
Here's the answer for what I came up with for the code:
function myFunction() {
// Get Source Spreadsheet
var source = SpreadsheetApp.getActiveSpreadsheet();
// Get Source Sheet from Spreadsheet
var source_sheet = source.getActiveSheet();
// Get Last Row
var lastRow = source_sheet.getLastRow();
// Get Last Column
var lastColumn = source_sheet.getLastColumn();
// Get Last Row of Data
var lastRowOfData = source_sheet.getRange(lastRow, 1, 1, lastColumn).getValues();
// Creates a one dimensional array
var oneD_array = lastRowOfData.join().split(",");
// Get the Value of the State Cell
var cellValue = source_sheet.getRange(2,7).getValue();
// Copy Last Row to Appropriate State Sheet
if ( cellValue == "New York" ) {
var target = SpreadsheetApp.openById("");
var target_sheet = target.getSheetByName("Sheet1");
target_sheet.appendRow(oneD_array);
}
}

Copy a row with specific cells to another spreadsheet

I have been searching for a way to copy certain cells from a row and paste them into another spreadsheet, but all I can seem to find are ways to do that just from sheet to sheet within one spreadsheet or questions that deal with code way above my head. I'm just now learning to code in Google Spreadsheets and I can't seem to get it right. Here is my code for now, I'm working on just copying one cell first and after I can do that I'll get a loop to iterate through the row and pick the cells I want to copy.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var master = SpreadsheetApp.openById('0AgcCWQn-aoI1dFpLVE4tSENwcThrYnlMUzhuRmdWU2c');
var target = SpreadsheetApp.openById('0AgcCWQn-aoI1dF96X2dBT2dVVFZ2SU1NRWdYTDJhT2c');
var master_sheet = master.getSheetByName("Steve2");
var target_sheet = target.getSheetByName("Corbin1");
var master_range = master_sheet.getRange("A1");
var target_range = target_sheet.getRange("A1");
master_range.copyTo(target_range);
Right now it is giving me an error saying that it cannot call the getRange method of null. I'm not sure if I'm using OpenById correctly or if I can even use that in this situation.
OK I've got it working now. It copies the row perfectly and it copies only the cells I want. Now I need help with the pasting part. It copies everything fine and puts it into the other sheet great, but I need it to copy into the next available row and into the first available columns. So if row 5 is the last column of data, I need it to paste into row 6 and take up the first 6 or so columns.
This is what I have so far.
function menuItem1() {
var sss = SpreadsheetApp.openById('0AgcCWQn-aoI1dFpLVE4tSENwcThrYnlMUzhuRmdWU2c'); // sss = source spreadsheet Steve2
var ss = sss.getSheetByName('Sheet1'); // ss = source sheet
//Message box asking user to specify the row to be copied
var answer = Browser.inputBox('What row would you like to copy?');
var range = parseInt(answer);
var array = [1,2,3,9,12,30];
//Runs a loop to iterate through array, using array elements as column numbers
for(var i = 0; i < array.length; i++){
var SRange = ss.getRange(answer,1,1,array[i]);
//get A1 notation identifying the range
var A1Range = SRange.getA1Notation();
//get the data values in range
var SData = SRange.getValues();
}
var tss = SpreadsheetApp.openById('0AgcCWQn-aoI1dF96X2dBT2dVVFZ2SU1NRWdYTDJhT2c'); // tss = target spreadsheet Corbin1
var ts = tss.getSheetByName('Sheet1'); // ts = target sheet
//set the target range to the values of the source data
ts.getRange(A1Range).setValues(SData);
//Confirmation message that the row was copied
Browser.msgBox('You have successfully copied Row: ' + answer);
}
You are getting this error maybe because your spreadsheet does not have a sheet called Steve2 or Cobin1. Try using the method master.getSheets()[0], this way you will get the first sheet without using their name.
You can algo use this piece of code to check the sheets names:
for(var x in master.getSheets()) {
Logger.log(master.getSheets()[x].getSheetName());
}
best,

Accessing to sheet with gid

sorry if my question is answered somewhere else, I've been looking for answers on google all afternoon but I'm still too newbie.
I'm trying to use Google spreadsheets' scripts to access to a different spreadsheet.
The only information I have is the spreadsheet's URL, where it has the key and the gid (some kind of chronological index for multi-sheet spreadsheets - only information i could find is here).
The sheet URL is something like https://docs.google.com/spreadsheet/ccc?key=abc123#gid=178
And the sheet it links to is the first sheet in the spreadsheet.
How do I find the sheet that maches the gid?
The following doesn't work, since it's based on the sheets' order, not the time they are created:
var ss = SpreadsheetApp.openById("abc123");
var sheet = ss.getSheets();
Browser.msgBox(sheets[178].getIndex());
I would do something simple like this:
var refSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SHEET NAME HERE");
var refSheetId = refSheet.getSheetId().toString();
And then append the refSheetId to the end of the getUrl string
var ssUrl = SpreadsheetApp.getActiveSpreadsheet().getUrl()+"#gid="+refSheetId;
Hope this helps!
This is how i do it:
function getSheetByGid(spreadsheet, gid){
gid = +gid || 0;
var res_ = undefined;
var sheets_ = spreadsheet.getSheets();
for(var i = sheets_.length; i--; ){
if(sheets_[i].getSheetId() === gid){
res_ = sheets_[i];
break;
}
}
return res_;
}
var sheet = SpreadsheetApp.openById("YOUR_SHEET_ID_HERE");
var seetWithGid = getSheetByGid(cpSheet, "YOUR_GID_HERE");
I know this is late, and may not be useful to original requester anymore. But I saw this question as I was working on the functionality, but ended up solving it myself later. You can look up a sheet by gid, and the fact that it doesn't change when you delete the sheet is a good thing.... if that's how you intend to use it. Basically I get all sheets, and loop through sheet names and sheet gid until the gid matches, and then I call the sheet by name.
Here's part of some code I use to take a row, from a aggregated sheet on edit, and push to the sub sheet as part of a two way sync. If you have just the gid and key stored, you can skip the steps i showed, and just reference those values. It seems to work very quick, it's a more complicated script then running from the workbook with the broken out sheets but both scripts take almost exactly 2 seconds to push, which is acceptable for me.
function Stest()
{
SpreadsheetApp.flush();
var sheet = SpreadsheetApp.getActiveSheet();
var r = sheet.getActiveRange();
var lastColumnRow = sheet.getLastColumn();
var activeRow = r.getRow();
var dataRange = sheet.getRange(activeRow,1,1,lastColumnRow);
var data = dataRange.getValues();
var sskeytemp = data[0][10].split("=")[1]; //the sheet url is in column k
var sskey = sskeytemp.split("#")[0]; //these first two steps get the sheet key
var ssid = data[0][10].split("gid=")[1]; //this last step gets the gid
var wrkbk = SpreadsheetApp.openById(sskey).getSheets(); //this gets the sheets from the workbook
for (var i = 0 ; i < wrkbk.length ; i++ ) {
if( ssid == wrkbk[i].getSheetId() ){ //this is to say if the spreadsheet gid, which is the gid of the sheet i know, matches the gid in the workbook from the link, thats the sheet i'm looking for
var ssname= wrkbk[i].getName();
var ss = SpreadsheetApp.openById(sskey).getSheetByName(ssname); //give me that sheet by name
var sslastColumn = ss.getLastColumn();
var sslastRow = ss.getLastRow();
var dataRange = ss.getRange(1,1,sslastRow,sslastColumn);
var data2 = dataRange.getValues(); //here's your data range, you can proceed freely from here
It is best not to rely on the gid for many reasons
A deleted sheet will not show up in your getSheets() method but will have accounted for in the gid
For example:
Create Sheet1 (by default, gid#1)
Create Sheet2 (gid#2)
Delete Sheet2
Create Sheet3 (gid#3)
The order will be jumbled up if the user decides to move sheets around in Spreadsheet UI. getSheets returns the sheets in the order they are arranged in the spreadsheet.
The only way you can get to the sheet is by its name. ALternatively, if you know of some content in the sheet, you can search through each sheet.
Alternatively this works for me perfect:
function _getSheetId() {
var getSpreadsheetURL = SpreadsheetApp.getActiveSpreadsheet().getUrl();
var mat = getSpreadsheetURL.match(/^(https:\/\/docs\.google\.com\/spreadsheets\/d\/)([a-zA-Z0-9]+\/)/g);
Logger.log(mat.toString()+"#gid="+SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getSheetId());
}