I have the following code, which works fine and does what it's supposed to do.
However my table has 2 rows at the top which doesn't interest me (they wouldn't match the if clause anyway so it doesn't affect me, just trying to figure this out) so I was trying to tailor my range to simply exclude them.
All I did was change the A1 to A3 inside the getRange and it's throwing the Cannot read property "1" from undefined (referring to the hardcoded 1 in the if statement).
The reason I don't understand why it's undefined is because changing the range like this shouldn't affect it at all, since I'm reducing my range from A1:B6 (6 rows in my sheet with the first 2 being either empty or not needed) to A3:B6, but the first value in the set (A3) should still end up at [0][0] inside 'data'.. unless getValues() messes something up that I don't know about..
var lastRow = sheet.getLastRow();
var myRange = sheet.getRange("A1:B" + lastRow);
var options = new Array();
var data = myRange.getValues();
for(var i = 0; i < lastRow; i++) {
if(data[i][1] == region)
{
options.push(data[i][0]);
}
}
Thanks!
Your bug stems from your for loop; it expects to iterate over 6 rows (i starts at 0 and increments by 1 up to 5), when what you really want is to iterate over 4 rows matching the range A3:B6. So the array only has 4 elements but your for loop tries to reference a 5th element that does not exist.
Simplest solution is to use the Array object's built in array methods (I'm using forEach in this case) to iterate over the items specifically in the range you defined as follows:
var range = sheet.getRange("A3:B"), // you automatically reference the last row with A3:B, no need for A3:B6
options = [],
region = '[some-region-value]';
range.getValues().forEach(function(row){
row[1] == region && options.push(row[0]); // exploit short-circuit mechanism in logical AND operator (in this case the operand on the right, the push, only gets executed if the operand on the left is true)
});
Array indices start with zero. The index of the last element is length of an array (total number of elements) - 1. Modify your 'for' loop like this and it will work
for(var i = 0; i < data.length; i++)
As the previous poster pointed out, you iterate from 0 to what the function getLastRow() returns. Remember, it returns the last row with data in a sheet. It has nothing to do with your array.
Related
I tried to get the values in a row into an array. For this I used the appscript in googlesheet. After checking the length of this rowtemp array, the answer is 1.
But I want to find the number of children inside. And if the element in "temp" is the same as the one in "rowtemp" then you need to find its column number
I used following code.
function rangeExa(){
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet4");
var temp = ss.getRange("A11:B22").getValues();
Logger.log(temp);
Logger.log(temp.length);
Logger.log(temp[3][0]);
var rowTemp = ss.getRange("D25:O25").getValues();
Logger.log(rowTemp);
Logger.log(rowTemp.length);
Logger.log(rowTemp[0][2]);
Logger.log(rowTemp);
for(i=0; i<=rowTemp.length; i++){
if(temp[3][0] == rowTemp[0][i]){
Logger.log("yessss");
}return;
}
}
As I explained in detail in the comment section, your goal is not clear.
However, there is a clear issue in the code an that relates to the length of rowTemp.
rowTemp is an array of a single array because it concers a single row. In other words, it has the format of [["High","Not High",..]]. Therefore, rowTemp.length will give you 1 since there is only one row in this array. If you want to iterate over the columns, you need to use rowTemp[0].length:
for(i=0; i<rowTemp[0].length; i++){
if(temp[3][0] == rowTemp[0][i]){
Logger.log("yessss");
Logger.log(i+4); // column number if there is a match
}
}
The above for loop will check if 36 appears in D25:O25 and it will output yessss if it does in the Logs page.
Also use i<rowTemp[0].length instead of i<=rowTemp[0].length because the last index of your array is rowTemp[0].length-1 otherwise you will get undefined.
Related:
What does the range method getValues() return and setValues() accept?
function checkWho(n,b)
{
// n and be are comparing two different cells to check if the name is in the registry
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var glr = sheet.getLastColumn();
var glr2 = sheet.getLastRow();
for(var i = 9; i <= glr; i++)
{
for(var z = 10; z<= glr2; z++)
{
if( n == b)
{
var courts = sheet.getRange(3,i).getValue();
var times = sheet.getRange(z,10).getValue();
return(b+ " "+"has booked"+" "+ courts+" "+"at"+times);
}
}
}
}
I am having issues printing out the values contained in var courts and var times. My code consists of two for loops iterating through columns and rows and eventually spitting out the users name, what court they've booked and at what time. As of now the name gets printed, but the courts and the times don't.
it currently prints: "(name) has booked at"
When I want it to print:" (name) has booked court 1 at 4:30"
Any help on the situation?
What is happening is that the the nested for statements are overwriting the result. It's very likely that the court and time are "" (empty strings) because the iteration is done from a start column/row and repeated for the next columns/rows. It's very common that the last column/rows are empty.
Side notes:
The script include a comment mentioning that custom function arguments are cells but custom function can't use cells as argument in the same sense of getCurrentCell(). Custom functions arguments types could be String, Number, Date or Array.
It doesn't make sense to compare the arguments inside the nested for statements as they doesn't change on each iteration.
Including a return inside the nested for statement will stop the iterations. As the arguments are not iteration dependent, only the first iteration is made for the case considered in the question.
If you return a string and your matter is to print those variables, then replace your return statement like this.(same as java script ES6 )
return(`${b} has booked ${courts} at ${times}`);
App script is grooming scripting language. My suggestion is working properly now.
so i have the following code that works perfectly for our purposes, it took me a long time to put together, and I know that is a horribly inefficient solution to my problem. In the interest of space, ive only linked the first if statement. This is repeated 14 more times with the other outcomes.
The problem is that we are going to have over 100 items soon, and while this works for 5, im afraid it is going to be far too much work and run very slowly to replicate.
function onEdit(event) {
var invinput = ss.getRange("L16").getValue();
var iteminput = ss.getRange("J16").getValue();
var item1 = ss.getRange("A2").getValue();
var item2 = ss.getRange("A3").getValue();
var item3 = ss.getRange("A4").getValue();
var item4 = ss.getRange("A5").getValue();
var item5 = ss.getRange("A6").getValue();
var inv1 = ss.getRange("B2").getValue();
var inv2 = ss.getRange("B3").getValue();
var inv3 = ss.getRange("B4").getValue();
var inv4 = ss.getRange("B5").getValue();
var inv5 = ss.getRange("B6").getValue();
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("invinputlog");
var itemList = ss.getRange(1,0,1000)
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
if(s.getName() == "IMI" && r.getColumn() == 14 && r.getValue() == "add" && (iteminput == item1)) {
ss.getRange("B2").setValue(invinput+inv1);
s.getRange(row, 1, 1, numColumns).copyTo(target);
ss.getRange("restockInput").clear();}
I've found a lot of solutions that are kind of helpful,some "for" statements that i tested, but I couldn't quite figure it out.
My question is that instead of having to compare "item1 == itemInput" for every item we have, I would like it to be something like
if iteminput == (anything in column A) {
ss.getRange("Cell B next to Cell A that matches iteminput").setValue
(invinput+"cell B that is next to cell A that matches iteminput");}
Thank you in advance if anyone has any ideas, I really enjoy these projects, so even getting me on the right track would be great. I am happy to figure it out myself, I'm just a little stuck.
If you are going to have 100 items, then it will be slow, as you say. What is slow is the access to the spreadsheet itself (what is fastest is the in-memory manipulation of every other 'command' which does not access the spreadsheet). So, for example, if you write the script to do ss.getRange("A3").getValue() one hundred times then this is going to be slow.
If I were to approach your scripting problem I would fetch all the values which are on the spreadsheet just the once (per onEdit of course) using something like var values = sheet.getDataRange().getValues();
This will create a 2Dimensional array, of the nature
[
[A1, B1, C1, ... L1 ],
[A2, B2, C2, ... L2 ],
...
...
[A100, B100, C100, ... L100]
]
You have the 'outer' array which holds multiple 'inner' arrays, each representing a row of the sheet, from the first† row up to the last row where there is any cell containing data and as wide as the first† column up to last column with any cell with data (every blank cell in between will be in the array too, which will be represented by empty string '' )
† .getDataRange() defines the range, from first to last-with-data, as above. You can use other range definitions too, e.g. getRange("A1:L100") ; nevertheless, the 2D nature of this range, and thus .getValues() will still be there.
Then you will have to manipulate this 2D array within google apps script. You can google search for loop in javascript OR google apps script to show you the code, which will be like:
for (var i = 0; i<values.length; i ++){
// YOUR CODE to manipulate this for..loop
}
The i variable within this for..loop increments each loop through YOUR CODE; thus it is the analog to each row (except it starts at zero i.e. one less than the row which starts at 1). So you will use i to reference each row in values. And you will have, as the second dimension, 0 as ColA, 1 as ColB etc to 11 as ColL
e.g. in the fourth loop, i will be 3, so values[i][1] will reference cell B4.
In this manner, withing one or a few line(s) of code within a for..loop, using i, you can compare 100 (or many more) rows.
You would do your if comparison as part of YOUR CODE within the for..loop.
When your if comparison finds a match, you should stop the loop running using the break statement. This will fix the var i, which you can then use within more script below the for..loop to write back to the spreadsheet. For this, I would consider using google apps script range.offset
(Note: your actual use case may be more complicated than I have indicated above, particularly in the code that you would need to write to achieve your required comparison (the if within the loop). However, your solution will almost certainly require the getValues() call to the spreadsheet so as to not make 100 getValue() calls, and thus you will have to work with 2D arrays.)
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!
I can't figure out how to check if a value exists in an Array. I assumed this should be trivially simple, but can't get there.
I have this code:
function findPlayer() {
//This section gets the values from the first row of all the columns
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Players");
var lastColumn = ss.getLastColumn()
var playerNameRow = ss.getRange(1, 2, 1, lastColumn - 3);
var playerNameValues = playerNameRow.getValues();
//This just sets the value we're looking for (which in this example is in the 7th Column "G"). And logs the Array, just to check it.
var findPlayer = "Dan";
Logger.log(playerNameValues)
//and here we just loop through the playerNameValues array looking for the findPlayer value
for(var n in playerNameValues){
if(playerNameValues[n][0]== findPlayer) {break}
}
//and here, if the above search does find the matching value it should log it
Logger.log(n);
}
What's happening is the playerNameValue is logging correctly, but n is always logging as 0. Which implies it's finding the value in the first item it checks, rather than column 7 where the value is.
I notice you mentioned that the player is column 7. Note that what you are actually checking is
Row 1 Column 1
Row 2 Column 1
Row 3 Column 1
And never actually touch any columns apart from the first one because you have [0] in if(playerNameValues[n][0]== findPlayer) {break}. Instead do this (I assume you have var i and var n already)
for (i = 0; i < playerNameValues.length; i++) {
for (n = 0; n < playerNameValues[i].length; n++) {
if (playerNameValues[i][n] == findPlayer) break
}
if (playerNameValues[i][n] == findPlayer) break
}
we do a second break to break out of the second loop, but there are better ways of writing that code, this is just to ilustrate what you need to do.
Your rows and columns are reversed, use:
for(var n in playerNameValues[0]){
if(playerNameValues[0][n]== findPlayer) {break}
}
Or another way is to use indexOf instead of above.
Logger.log(playerNameValues[0].indexOf(findPlayer));