Getting server error message when referencing getLastRow()+1 - google-apps-script

I had a sheet that was working using some of the same code... In the last few days trying to work on my current project, stuff that used to work no longer works and I'm getting errors for all sorts of different stuff.
Currently, I can't move on until I figure this one out.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var clientList = ss.getSheetByName("ClientList");
var tracker = ss.getSheetByName("Tracker");
var tr_activeRow = tracker.getActiveCell().getRow();
var tr_clientName = tracker.getRange(tr_activeRow,1);
var tr_birthDate = tracker.getRange(tr_activeRow,5);
function addNewName() {
if(tr_clientName.getValue() == "New" && tr_birthDate.getValue() != ""){
tracker.getRange(tr_activeRow, 2, 1, 4).copyValuesToRange(clientList, 1, 4, clientList.getLastRow()+1, 1);
}
}
When trying to run the code I get:
We're sorry, a server error occurred. Please wait a bit and try again.
I've tried a few things and know that it's getLastRow()+1 that's the problem. I also tried getLastRow() & getLastRow()-1 to see if the code runs and it runs just fine. Only seems to be a problem when I'm using a positive integer...
What am I doing wrong?!? or is this a problem with sheets/apps script?

The parameters for copyValuesToRange() are:
copyValuesToRange(sheet, column, columnEnd, row, rowEnd)
You are designating the row end to be row 1.
.copyValuesToRange(clientList, 1, 4, clientList.getLastRow()+1, 1);
The rowEnd should be a number that is bigger than or the same as (for one row) the row parameter.
Your are only getting one row,
tracker.getRange(tr_activeRow, 2, 1, 4).copyVa . . . .
so the row and rowEnd should be the same.
var theLastRow = clientList.getLastRow()+1;
tracker.getRange(tr_activeRow, 2, 1, 4)
.copyValuesToRange(clientList, 1, 4, theLastRow, theLastRow);

Related

Copy specific Columns data from Last Row and paste to another sheet

I was trying to copy/fetch data from specific columns of the last row from my "Total" sheet, and pass those to another sheet named "vnSheet". The code below which i found earlier works fine. But, it copies all the row & column data from the mother sheet, which I don't want to happen.
function copySheet() {
var sourceSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Total");
var destSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("vnSheet");
var columns_to_be_copied = ['A', 'B', 'D', 'F'];
var columns_to_be_pasted = ['A', 'B', 'D', 'F'];
for (column in columns_to_be_copied) {
var copy_range_string = columns_to_be_copied[column] + ':' + columns_to_be_copied[column];
var paste_range_string = columns_to_be_pasted[column] + ':' + columns_to_be_pasted[column];
var source = sourceSheet.getRange(copy_range_string);
var destination = destSheet.getRange(paste_range_string);
source.copyTo(destination, {contentsOnly:true});
}
}
Here, I want to Copy 'A', 'B', 'D', 'F'(except 'C' & 'E' ) column data only from Last row of "Total" and paste those data into the same columns of the "vnSheet" but in the Last row. I searched almost all over the internet and found no solution of my problem, all I found was similar to the code above.
As I'm new to this, my coding experience is limited but I'm a gd learner :) Any help to solve that problem will be greatly appreciated.
Thank you.
So there are a few things
You currently are not getting the last row, as far as I can tell
You currently have duplicate variables to hold the same array
You currently are copying an entire row, which is not what you want
Assuming there are already values in columns C and E in your Target Sheet there are a few ways to approach a solution:
You get the existing values from Target Sheet and then overwrite the columns you want to overwrite, then you update the entire row using one array
You only overwrite the particular cells you want to update, leaving the rest as is
Option 1 above will be quicker, but we are speaking fractions of a second.
Therefore I recommend option 2, as it does not touch an existing row with field you don't want to change.
My proposal is here:
function copyHandler(){
const activeSheet = SpreadsheetApp.getActive()
var sourceSheet = activeSheet.getSheetByName("Total");
const sourceLastRowNum = sourceSheet.getLastRow()
var destSheet = activeSheet.getSheetByName("vnSheet");
const destLastRowNum = destSheet.getLastRow() + 1
const lastRow = sourceSheet.getRange(sourceLastRowNum, 1, 1, 6).getValues()[0]
lastRow.forEach( (value, index) => {
// Skip column C and column E
if( index == 2 || index == 4) return
destSheet.getRange(destLastRowNum, index+1).setValue(value)
})
}
Use sourceSheet.getLastRow() before the for loop to get the row number of the last row in your "Total" sheet, then add this value after each column letters.
Let's say that you assign the result of the above to a lastRow, then you can use this variable this way:
var copy_range_string = columns_to_be_copied[column] + lastRow + ':' + columns_to_be_copied[column] + lastRow;
NOTE:
You might find helpful to read https://developers.google.com/apps-script/guides/support/best-practices. This article suggest to avoid doing calls to Google Apps Script classes as they are slow. One way to reduce the number of calls do achieve the result that you are looking is by using getRangeList at it allows to get multiple ranges at once. Another way is by using the Advanced Sheets Service as it allow to do batch updates.

Google Scripts function not doing anything (Google Script tied to Google Sheets)

I have this code on a dice roller sheet I'm trying to make. In column 1, I'm putting the number of dice being used, in column 3, I'm putting the type of dice used (d4 is 4, d6 is 6, d8 is 8, etc.), in column 5, I'm putting the interior modifier (mod for every dice), and in column 7, I'm putting the outside modifier (overall modifier after everything is rolled).
In column 9, everything is supposed to be consolidated as shown in the code below... But nothing's happening on the sheet itself.
Note: No listed errors, it seems to be running properly, but nothing's being displayed.
Quick edit, just for completeness' sake, I replaced all instances of " with ', nothing happened, which I figured, but I'm a bit new haha
function onEdit() { // Column 7
var ss = SpreadsheetApp.getActiveSpreadsheet(); // Spreadsheet
var sheeta = "Rolls";
var sheet = ss.getSheetByName(sheeta); // Active sheet
var numberrange = sheet.getActiveCell();
numberrange = (numberrange.getRow(),1);
var typerange = (numberrange.getRow(),3);
var modrange = (numberrange.getRow(),5);
var outermodrange = (numberrange.getRow(),7);
var finalrange = (numberrange.getRow(),9);
var results = "=" + numberrange.getValue() +"*(TRUNC(RAND()*(" + typerange.getValue() + "-1)+" + modrange.getValue() + "))+" + outermodrange.getValue(); // =1*(TRUNC(RAND()*(6-1)+0))+0
finalrange.setValue(results);
}
This is not range numberrange = (numberrange.getRow(),1); rather this is just a (X,Y).
I don't know why you didn't mentioned, I think you must be getting error while executing the script.
To convert this into range you should do something like numberrange = sheet.getRange(numberrange.getRow(),1);
Here finalrange.setValue(results); results is a formula I guess, so kindly use finalrange.setFormula(formula) function.
Let me know if this doesn't solve your issue or if you have any doubt.

Autofill google forms based on user input

Alright stack friends,
I'm working on my first projects using google scripts and it's been pretty fun so far. My project is to create a form for data entry that can either accept an ID number and fill in the rest of the fields, or let the user fill out the entire form. Basically my method to fill in the other fields is just to have a lookup table on the second sheet. When the user submits a form, the script runs, looks for the ID of the last row, scans the reference table for the ID, and then fills in the details.
I think the problem I'm having is the assumption that the data from the form is already in the sheet when the script runs. The problem I noticed is that the script sometimes fails to fill in the gaps. I tried creating form submissions in a loop with the same ID and they function somewhat erratically but it seems like the last sumbission always works which would make sense if the script executions are not matching up with the form submissions. Here's the script for reference:
function fillGaps() {
// First take in the appropriate spreadsheet objects and get the sheets from it
var ss = SpreadsheetApp.openById(id);
var sheet = ss.getSheets()[0];
var refSheet = ss.getSheets()[1];
// Here's the last rows' index
var lastRow = sheet.getLastRow();
var lastRowRef = refSheet.getLastRow();
// now this is an array of values for the last row and the student ID entered
var response = sheet.getRange(lastRow, 1, 1, 7).getValues();
var enteredID = response[0][1];
// Next we're going to try to load up the lookup table and scan for the ID
var stuIDs = refSheet.getRange(2, 4, refSheet.getLastRow()).getValues();
var row = 0;
while(enteredID != stuIDs[row] && row <= lastRowRef){
row++;
}
// Okay at this point the row variable is actually -2 from what the sheet index
// is that I'm thinking of. This is because we didn't load the first row (names)
// and the way arrays are indexed starts with 0.
row++;
row++;
// now assuming that it found a match we'll fill in the values
if(row < refSheet.getLastRow()){
// Alright now we need to wrangle that row and format the data
var matchedRow = refSheet.getRange(row, 1, 1, 6).getValues();
// modify the response
var replacement = [response[0][0],enteredID, matchedRow[0][1],matchedRow[0][0],matchedRow[0][2],matchedRow[0][4],matchedRow[0][5]];
sheet.getRange(lastRow, 1, 1, 7).setValues([replacement]) ;
}
}
So I'm wondering:
Does this seem like the right diagnosis?
If so, what would be the best way to remedy? I thought of adding a little delay into the script as well as trying to capture the submissions timestamp (not sure how to do that)
Thank you much!
The following code gives a 2D array:
var stuIDs = refSheet.getRange(2, 4, refSheet.getLastRow()).getValues();
Also,refSheet.getLastRow gives the last row, lets say it is 10 in this case. The syntax for getRange is getRange(row, column, numRows) and the last argument is the number of rows, not the last column. So in the above code the selected range would be row 2 - 11 rather than 2- 10. Unless that is what you intended, modify the code like so:
var stuIDs = refSheet.getRange(2, 4, refSheet.getLastRow()-1).getValues();
To access the values in stuIDs you should use stuIDs[row][0] (2D array) to check for matching ID. Assuming your ID was to be matched was in column 1.
Secondly, in the loop you are using the following to check for the last index in array row <= lastRowRef which will cause it go out of range(because array starts at 0 and sheet row at 1) instead use this row < stuIDs.length
Finally, in case you don't find a match you will end up with the last row and your code will end you taking the last row as the matched index. This can be prevented by using a boolean variable to check for a match.
var foundId = false
var row = 0;
var i = 0;
for (i in stuIDs){
if(stuIDs[i][0] == enteredID)
foundID = true
break
}
}
row = i + 2
if (foundID){
var matchedRow = refSheet.getRange(row, 1, 1, 6).getValues();
// modify the response
var replacement = [response[0][0],enteredID, matchedRow[0][1],matchedRow[0][0],matchedRow[0][2],matchedRow[0][4],matchedRow[0][5]];
sheet.getRange(lastRow, 1, 1, 7).setValues([replacement]) ;
}
PS: You can also use event objects to get the values of response (eventObj.values). As mentioned here: https://developers.google.com/apps-script/guides/triggers/events
Hope that helps!

Service Error: Spreadsheets (getLastRow)

I am at my wit's end here. I had three scripts working beautifully, and then overnight they all failed with the dreaded "Service Error".
The error always references getLastRow as the offending line. I've tried adding and removing rows at the bottom of the sheet, removing some of the rows with data in them, unfreezing headers and columns, etc. with no luck whatsoever. I also tried reverting back to last week's version of the script, long before the failure (first was Monday night), to no avail.
I've done a lot of research and I've noticed most often the thing that fixes these Service Errors is something completely random. Some people say that this error is caused by memoization on Google's end. If so, what can I do to fix this?
A huge project is basically crashing and burning because these scripts failed...any help would be awesome. Thanks.
For reference, one of my scripts (the one that failed first).
function timeStamp2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[4];
var numRows = sheet.getLastRow()-1; // Number of rows to process
Logger.log(numRows)
var startRow = 2; // First row of data to process
// Fetch the range of cells
var dataRange = sheet.getRange(startRow, 1, numRows, sheet.getLastColumn());
// Fetch values for each row in the Range.
var data = dataRange.getValues();
//Logger.log(data)
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var schedulingStatus = row[4];
var timeStamp = row[3];
var scheduled = "Scheduled";
if(timeStamp === '') {
if(schedulingStatus == scheduled ) {
sheet.getRange(startRow + i, 3).setValue(new Date());
}
}
}
}
Whether this constitutes an 'Answer' I cannot judge, but rest assured you are not alone in noticing the sporadic behavior of certain intrinsic GAS functions – notably a few in common use with spreadsheets to append new rows or locate the last row/column of data – which has plagued my own code and others' due to extremely long execution times, often as high as 20 seconds per instance!
I insist there must be a hidden timing flaw in getLastRow(), as shown below. The problem now routinely leads to Timeouts in my workflow and generates frequent failures and Service error: Spreadsheets messages.
For example, if you run the following code function every minute (via a timed Trigger)...
function getLastRow_TEST() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("getLastRow_TEST");
var timeBegin = Date.now();
var nextRow = sheet.getLastRow() + 1;
var timeEnd = Date.now();
var range = sheet.getRange("A" + nextRow + ":B" + nextRow);
var timeElapsed = (timeEnd - timeBegin) / 1000;
range.setValues([[nextRow - 1, timeElapsed]]);
}
...on your test Spreadsheet, containing a sheet named 'getLastRow_TEST', you will soon notice clustered delays (> 1 sec.), as shown in the resulting data (collected at 10:55am on Monday 6/13/2016), but with no discernible pattern of occurrence:
TEST # SECONDS
1 0.041
2 10.242 *
3 10.256 *
4 5.194 *
5 0.055
6 0.178
NOTE: This testing was performed with an otherwise pristine (empty) spreadsheet, with no other code running, no timed Triggers, nor script locking active.
I am attempting to submit this issue as a bug report on Google's issues tracker, but felt compelled to share my experience with you here (I posted elsewhere on SO about this glitch).
Are you counting your array positions correctly?
ss.getSheets()[4]
means it's the fifth sheet. I also noticed that although you initialise timeStamp as the value in the fourth column of row:
var timeStamp = row[3]
later on you:
sheet.getRange(startRow + i, 3).setValue(new Date())
in the third column of the row. ie row[3] is one cell to the right of sheet.getRange(startRow + i, 3).
I had a similar issue with a script I wrote.
It gave Service Error: Spreadsheets said the problem was with the line containing getLastRow.
My fix ended up making sure there was at least one row at the bottom without any data. Haven't had a problem since.
I had the same problem recently.
My attempts to fix would be:
Try to replace getLastRow with getMaxRows — get the last
row of a sheet
Try to use the construction like:
sheet.getDataRange().getLastRow(); → assuming my data range starts
at row #1, the result should be the same.

Format row color based on cell value

I am trying to adapt the example script from this previous, related question. For rows where the cell value in column K is zero, I want to make the row yellow.
Here is my current adapted code:
function colorAll() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 3;
var endRow = sheet.getLastRow();
for (var r = startRow; r <= endRow; r++) {
colorRow(r);
}
}
function colorRow(r){
var sheet = SpreadsheetApp.getActiveSheet();
var c = sheet.getLastColumn();
var dataRange = sheet.getRange(r, 1, 1, c);
var data = dataRange.getValue();
var row = data[0];
if(row[0] === "0"){
dataRange.setBackground("white");
}else{
dataRange.setBackground("yellow");
}
SpreadsheetApp.flush();
}
function onEdit(event)
{
var r = event.source.getActiveRange().getRowIndex();
if (r >= 3) {
colorRow(r);
}
}
function onOpen(){
colorAll();
}
My problem is, I can't figure out how to reference column K. In the linked answer above, the script's creator claims, "[h]ere is a Google Apps Script example of changing the background color of an entire row based on the value in column A." First, and most importantly, I can't figure out where he's referencing column A. I thought changing "var dataRange = sheet.getRange(r, 1, 1, c);" to "var dataRange = sheet.getRange(r, 11, 1, c);" would do it, but that just added 10 blank columns to the end of my sheet, and then the script crashed. I do not understand why.
Secondly, but more as an aside, his claim that the script affects entire rows is inaccurate, as his original "var dataRange = sheet.getRange(r, 1, 1, 3);" only colored the first three columns - which is why I added "var c" and changed "3" to "c".
Furthermore, when I play/debug the script, or run "onEdit" from the spreadsheet script manager, I get "TypeError: Cannot read property "source" from undefined." I can see that "source" is undefined - I had mistakenly assumed it was a Method at first - but I'm not sure how to fix this issue either.
Lastly, column K will not always be the reference column, as I mean to add more columns to the left of it. I assume I'll have to update the script every time I add columns, but there is a column heading in row 2 that will never change, so if someone can help me devise a bit of code that will look for a specific string in row 2, then get that column reference for use in function colorRow(), I would appreciate it.
I can't tell if this script is structured efficiently, but ideally, I want my spreadsheet to be reactive - I don't want to have to rerun this script after editing a driving cell, or upon opening; it reads like it's supposed to do that (were it not buggy), but this is my first attempt at using Google Apps Script, and I don't feel certain of anything.
I'm not great with scripting, but I took a programming fundamentals/Python class in grad school back in 2006, and spent 4 years working with Excel & Access shortly after that, often creating and adapting Macros. I can't really design from scratch, but I understand the basic principles and concepts, even if I can't translate everything (e.g., I don't understand what the "++" means in the third argument in the "for" statement I'm using: "for (var r = startRow; r <= endRow; r++)." I think I'm allegorically equivalent to a literate Spanish speaker trying to read Italian.
Help, and educational explanations/examples, will be much appreciated. Thank you kindly for reading/skimming/skipping to this sentence.
Rather than rewriting the code which you have already got some help with, I will try to give you explanations to the specific questions you asked. I see that you have some of the answers already but I am putting thing in completely as it helps understanding.
My problem is, I can't figure out how to reference column K.
Column A = 1, B = 2,... K = 10.
I can't figure out where he's referencing column A.
You were close when you altered the .getRange. .getRange does different things depending on how many arguments are in the (). With 4 arguments it is getRange(row, column, numRows, numColumns).
sheet.getRange(r, 1, 1, c) // the first '1' references column A
starts at row(r) which is initially row(3), and column(1). So this is cell(A3). The range extends for 1 row and (c) columns. As c = sheet.getLastColumn(), this means you have taken the range to be 1 row and all the columns.
When you changed this to
var dataRange = sheet.getRange(r, 11, 1, c) // the '11' references column L
You have got a range starting at row(3) column(L) as 11 = L. This runs to row(3) column(getLastColumn()).
This is going to do weird things if you have gone out of range.
You may have pushed it in to an infinite for loop which would cause the script to crash
Secondly, but more as an aside, his claim that the script affects entire rows is inaccurate, as his original "var dataRange = sheet.getRange(r, 1, 1, 3);"
only colored the first three columns - which is why I added "var c" and changed "3" to "c".
You are correct. The (3) says that the range extend for 3 columns.
"TypeError: Cannot read property "source" from undefined."
What is happening here is not intuitively clear. You can't run the function onEdit(event) from the spreadsheet script manager because it is expecting an "event".
onEdit is a special google trigger that runs whenever any edits the spreadsheet.
it is passed the (event) that activated it and
event.source. refers to the sheet where the event happened so
var r = event.source.getActiveRange().getRowIndex(); gets the row number where the edit happened, which is the row that is going to have its color changed.
If you run this in the manager there is no event for it to read, hence undefined. You can't debug it either for the same reasons.
Lastly, column K will not always be the reference column, as I mean to
add more columns to the left of it. I assume I'll have to update the
script every time I add columns, but there is a column heading in row
2 that will never change, so if someone can help me devise a bit of
code that will look for a specific string in row 2, then get that
column reference for use in function colorRow(), I would appreciate
it.
Before I give you code help her, I have an alternative suggestion because you are also talking about efficiency and it is often faster to run functions in the spreadsheet than using scripts. You could try having column A as an index columns where ColumnA(Row#) = ColumnK(Row#). If you put the following into cell(A1), ColumnA will be an exact match of Column K.
=ArrayFormula(K:K)
Even better, if you add/remove Columns between A and K, the formula will change its reference without you doing anything. Now just hide columnA and your sheet is back to its originator appearance.
Here is your code help, utilizing some of your own code.
function findSearchColumn () {
var colNo; // This is what we are looking for.
var sheet = SpreadsheetApp.getActiveSheet();
var c = sheet.getLastColumn();
// gets the values form the 2nd row in array format
var values = sheet.getRange(2, 1, 1, c).getValues();
// Returns a two-dimensional array of values, indexed by row, then by column.
// we are going to search through values[0][col] as there is only one row
for (var col = 0; col < data[0].length; col++) { // data[0].length should = c
if (data[0][col] == value) {
colNo = col;
break; // we don't need to do any more here.
}
}
return(colNo);
}
If break gives you a problem just delete it and let the look complete or replace it with col = data[0].length;
I can't tell if this script is structured efficiently, but ideally, I
want my spreadsheet to be reactive - I don't want to have to rerun
this script after editing a driving cell, or upon opening; it reads
like it's supposed to do that (were it not buggy), but this is my
first attempt at using Google Apps Script, and I don't feel certain of
anything.
It is ok, the fine tuning of efficiency depends on the spreadsheet. function onEdit(event)
is going to run every time the sheet is edited, there is nothing you can do about that. However the first thing it should do is check that a relevant range has been edited.
The line if (r >= 3) seems to be doing that. You can make this as specific as you need.
My suggestion on a hidden index column was aimed a efficiency as well as being much easier to implement.
I'm not great with scripting,
You are doing ok but could do with some background reading, just look up things like for loops. Unfortunate Python is grammatically different from many other languages. A for loop in google script is the same as VBA, C, JAVA, and many more. So reading about these basic operations is actually teaching you about many languages.
I don't understand what the "++" means in the third argument in the "for" statement
It is why the language C++ gets its name, as a programmer joke.
r++ is the same as saying r = r+1
r-- means r = r-1
r+2 means r = r+2
So
for (var r = startRow; r <= endRow; r++)
means r begins as startRow, which in this case is 3.
the loop will run until r <= endRow, which in this case is sheet.getLastRow()
after each time the loop runs r increments by 1, so if endRow == 10, the loop will run from r = 3 to r = 10 => 8 times
1.The onEdit is a special function that is automatically called when you edit the spreadsheet. If you run it manually, the required arguments won't be available to it.
2.To change the colour of the entire row when column K is 0, you have to make simple modifications to the script . See below
function colorRow(r){
var sheet = SpreadsheetApp.getActiveSheet();
var c = sheet.getLastColumn();
var dataRange = sheet.getRange(r, 1, 1, c);
var data = dataRange.getValues();
if(data[0][10].toString() == "0"){ //Important because based on the formatting in the spreadsheet, this can be a String or an integer
dataRange.setBackground("white");
}else{
dataRange.setBackground("yellow");
}
SpreadsheetApp.flush();
}