Apps Script: Getting a range of columns without taking into account rows - google-apps-script

I have a Google Sheet that links to a Google Form and on each response the information is directed to the relevant team by email.
Recently, a VLookup was added to the sheet (in the end column) which has thrown off the script as it currently displays a "N/A" error (which will remain until the linked sheet has information added to it).
I am trying to limit the number of columns that the script looks at, in the hopes that this error will no longer affect the running of my script - however I'm not sure of the best way to enter the range...nothing seems to be working. This is what I currently have, where EM is the last column that my form data is in before the vlookup...
var sheet = SpreadsheetApp.getActive().getSheetByName("Form responses 1");
var range = sheet.getDataRange("A:EM");
var values = range.getValues();
var lastRow = range.getLastRow();
var lastRowValues = values[lastRow-1];
Logger.log(lastRowValues);
I have tried adding A1:EM but that doesn't seem to work either so I assume I am using the wrong "get"?

Solution:
var sheet = SpreadsheetApp.getActive().getSheetByName("Form responses 1");
var range = sheet.getDataRange();
var values = range.getValues();
var lastRow = range.getLastRow();
var lastRowValues = values[lastRow-1];
Logger.log(lastRowValues);
Explanation:
You have two options:
Use getDataRange() with no parameters:
sheet.getDataRange();
Or use getRange("A:EM") to specify the exact range:
sheet.getRange("A:EM");
References:
getRange
getDataRange

getDataRange() does not require parameters
Try this instead:
function myfun() {
var sheet = SpreadsheetApp.getActive().getSheetByName("Sheet0");
var range = sheet.getRange("A:EM");
var values = range.getValues();
var lastRow = range.getLastRow();
var lastRowValues = values[lastRow-1];
Logger.log(lastRowValues);
}

Related

getLastRow() doesn't work correctly in my 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];
}
}

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.

Hide Last Row not hiding from form submit

I am attempting to write a script that on form submit will send two sets of data to two different spreadsheets. The data is derived from one form. It is supposed to copy the most recent entry and then hide the row to prevent duplication. I am finding that on some occasions it is not hiding the last row and multiple copies are being copied. I realize my script is probably not the most efficient, for example, I could not get a range to work!
The sleep utilities were inserted as I thought there may have been a delay in the writing of the form's input to the spreadsheet and the script was running before this was complete.
The script is attached to the form which writes to a Form Responses sheet, this is then split to a company spreadsheet and a customer spreadsheet. Well that is the intention anyway!
function copyRow(){
Utilities.sleep(2000)
var ss = SpreadsheetApp.openById('1_pnsvWtqB4CUivQZE65nFR0AG9zTOgqBVSlPZW4YCUQ'); //Source Form Input
var sheet = ss.getSheetByName('Responses');
var lastrow = ss.getLastRow();
var rowIdx = sheet.getLastRow();
var rowValues = sheet.getRange(lastrow,1,1,74).getValues();
Logger.log(rowValues);
Utilities.sleep(5000)
var destValues = []; //Data to AUSJET
destValues.push(rowValues[0][0],rowValues[0][1],rowValues[0][2],rowValues[0][3],rowValues[0][4],rowValues[0][5],rowValues[0][6],rowValues[0][7]
,rowValues[0][8],rowValues[0][9],rowValues[0][10],rowValues[0][11],rowValues[0][12],rowValues[0][13],rowValues[0][14],rowValues[0][15]
,rowValues[0][16],rowValues[0][17],rowValues[0][18],rowValues[0][19],rowValues[0][20],rowValues[0][21],rowValues[0][22],rowValues[0][23]
,rowValues[0][24],rowValues[0][25],rowValues[0][26],rowValues[0][27],rowValues[0][28],rowValues[0][29],rowValues[0][30],rowValues[0][31]
,rowValues[0][32],rowValues[0][33],rowValues[0][34],rowValues[0][35],rowValues[0][36],rowValues[0][37],rowValues[0][38],rowValues[0][39]
,rowValues[0][40],rowValues[0][41],rowValues[0][42],rowValues[0][43],rowValues[0][44],rowValues[0][45],rowValues[0][46],rowValues[0][47]
,rowValues[0][48],rowValues[0][49],rowValues[0][50],rowValues[0][51],rowValues[0][52],rowValues[0][53],rowValues[0][54],rowValues[0][55]
,rowValues[0][56],rowValues[0][57],rowValues[0][58],rowValues[0][59],rowValues[0][60],rowValues[0][61],rowValues[0][62],rowValues[0][63]
,rowValues[0][64],rowValues[0][65],rowValues[0][66],rowValues[0][67],rowValues[0][68],rowValues[0][69],rowValues[0][70],rowValues[0][71]
,rowValues[0][73],rowValues[0][74]);
// copy data from col A to col BU
var dest = SpreadsheetApp.openById('1KQ43DJsJY0RpSnFHDcVfgAWLRfLKE6OD28CFkbjpJoU').getSheetByName('Form Responses');[1];
dest.getRange(dest.getLastRow()+1,1,1,destValues.length).setValues([destValues]);
var destValues = []; //DATA to CONCATENATOR
destValues.push(rowValues[0][0],rowValues[0][1],rowValues[0][2],rowValues[0][3],rowValues[0][4],rowValues[0][5],rowValues[0][6],rowValues[0][7]
,rowValues[0][8],rowValues[0][9],rowValues[0][10],rowValues[0][11],rowValues[0][12],rowValues[0][13],rowValues[0][14],rowValues[0][15]
,rowValues[0][16],rowValues[0][17],rowValues[0][18],rowValues[0][19],rowValues[0][20],rowValues[0][21],rowValues[0][22],rowValues[0][23]
,rowValues[0][24],rowValues[0][25],rowValues[0][26],rowValues[0][27],rowValues[0][28],rowValues[0][29],rowValues[0][30],rowValues[0][31]
,rowValues[0][32],rowValues[0][33],rowValues[0][34],rowValues[0][35],rowValues[0][36],rowValues[0][37],rowValues[0][38],rowValues[0][39]
,rowValues[0][40],rowValues[0][41],rowValues[0][42],rowValues[0][43],rowValues[0][44],rowValues[0][45],rowValues[0][46],rowValues[0][47]
,rowValues[0][48],rowValues[0][49],rowValues[0][50],rowValues[0][51],rowValues[0][52],rowValues[0][53],rowValues[0][54],rowValues[0][55]
,rowValues[0][56],rowValues[0][57],rowValues[0][58],rowValues[0][59]);
// copy data from col A to col BH
var dest = SpreadsheetApp.openById('1qB4OnKF3-OXFR1kR3jewHgYeslLdmIiE3maQNx0MPbk').getSheetByName('ROAMES');[1];//
dest.getRange(dest.getLastRow()+1,1,1,destValues.length).setValues([destValues]);
Utilities.sleep(2000)
sheet.hideRows(lastrow)
I am assuming, that as the data is coming from the form so the first column will be 'Timestamp'.
So, I have copied the last row from the source spreadsheet and then compared the value of column A of this last row with the value of column A of the last row of Destination sheet, if both matches then I haven't copied the data...
Only thing that may cause an issue here is if two or more responses are submitted exactly at a same time
Try the following code:
function copyRow(){
Utilities.sleep(2000);
var ss = SpreadsheetApp.openById('1_pnsvWtqB4CUivQZE65nFR0AG9zTOgqBVSlPZW4YCUQ');
var sheet = ss.getSheetByName('Responses');
var lastrow = sheet.getLastRow();
var destValues = sheet.getRange('A'+lastrow+':BU'+lastrow).getValues();
var dest = SpreadsheetApp.openById('1KQ43DJsJY0RpSnFHDcVfgAWLRfLKE6OD28CFkbjpJoU').getSheetByName('Form Responses')
if( dest.getRange(dest.getLastRow(), 1,1,1).getValue().toString() != destValues[0][0].toString() )
dest.getRange(dest.getLastRow()+1,1,1,destValues[0].length).setValues(destValues);
var destValues = sheet.getRange('A'+lastrow+':BH'+lastrow).getValues();
var dest = SpreadsheetApp.openById('1qB4OnKF3-OXFR1kR3jewHgYeslLdmIiE3maQNx0MPbk').getSheetByName('ROAMES')
if( dest.getRange(dest.getLastRow(), 1,1,1).getValue().toString() != destValues[0][0].toString() )
dest.getRange(dest.getLastRow()+1,1,1,destValues[0].length).setValues(destValues);
};

How could you retrieve a range of user highlighted cells in Google Sheets using Google Apps Script?

At the moment this is the function I'm using but I have no way of testing if it will work in the spreadsheet until I publish the application.
function readSelection() {
//The commented lines aren't needed if the sheet is open already
//var sheetid = "sheet id here";
//var spreadsheet = SpreadsheetApp.openById(sheetid);
//SpreadsheetApp.setActiveSpreadsheet(spreadsheet);
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
//sheet.setActiveSelection("B2:B22");
var activerange = sheet.getActiveRange();
var activecells = activerange.getValues();
return activecells;
};
I assume you mean highlighted == selected
The result depends on whether the cells are contiguous or not (non contiguous cell selection is available in the new spreadsheets features http://googleblog.blogspot.co.nz/2013/12/new-google-sheets-faster-more-powerful.html
For contiguous cells selected your code returns the values of the selection as an array, for non-contiguous cells your code will return the an array with the single value of the LAST selected item.
I suggest that this is a bug in the implementation of the new spreadsheet. If it is important to you, I suggest you raise an issue. For the old spreadsheets, you can only select contiguous cells (eg B2:B22) so it will work as you expect.
The easiest way to answer this Q is to run the code you have written! You don't have to publish anything just run the code in the script editor of the spreadsheet you are examining
and look at the log.
function readSelection() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var activerange = sheet.getActiveRange();
var activecells = activerange.getValues();
Logger.log(activecells)
return
};
There is no way to do this at the moment or to obtain the selected ranges from a script.
A request is pending and you can support it here : https://code.google.com/p/google-apps-script-issues/issues/detail?id=4056
by adding a star to the request.
If/when this function is implemented your code would look as follows:
function readSelection() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var activeranges = sheet.getSelectedRanges();
var activecells = [] ;'
for (var ar in activeranges)
activecells = activecells.concat(activeranges[ar].getValues()) ;
Logger.log(activecells)
return ;
}
note that selected ranges may overlap, so some cell contents could be added twice.

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());
}