Simple Google Script not working - javascript

I have decided to use Google Scripts when I can when using spreadsheets, just to start increasing knowledge. BUT I am untrained and got stuck quite quickly.
I saw some code on youtube for a different script but I thought I extracted what I needed and could get this to work...
function y_add_up_function() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var range = sheet.getRange["I2:J15"];
var data = range.getValues();
var sum = 0;
for (var i = 0; i < 14; ++i)
{
var row = data[i];
if(row[1] == "y")
{
sum = sum + row[0];
}
}
return sum;
}
This is my spreadsheet. I am trying to just simply get the SS to add up where I have ticked y next to a price, but I am getting an error that reads:
TypeError: Cannot call method "getValues" of undefined. (Line 5)
Please teach me what I am doing wrong ^_^!!!
Cheers!

The error means that you are trying to call a method on an undefined object. If you look at your code, you have range.getValues(). This means that range is undefined. Therefore you need to look at the assignment of range: var range = sheet.getRange["I2:J15"]. And here is the problem, getRange is a method and need to be called with parenthesis, that is getRange("I2:J15").
Here is the working code:
function y_add_up_function() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var range = sheet.getRange("I2:J15"); // <-- fix here
var data = range.getValues();
var sum = 0;
for (var i = 0; i < 14; ++i)
{
var row = data[i];
if(row[1] == "y")
{
sum = sum + parseFloat(row[0].substring(2)); // <-- fix to get floats from "£ <number>"
}
}
return sum;
}
Also note that since you have pound signs in your cost column, you need to strip the signs to get a number. Here I have used .substring(2), but this is not very fail safe. If you e.g. forget to put "£ " in a cell the number will be read incorrectly. I recommend putting the pound sign in the column header or use a more sophisticated method to remove it when parsing the numbers.
You can for example use row[0].replace(/^[0-9.]/g, ''). This will replace all characters that are not digits nor decimal points. This has no error-checking, but I think it is enough in a personal spreadsheet.

Related

Apps Script - For loop is slow. How to make it faster?

My spreadsheet has a column (A) with over 1000 rows of values like 10.99€, 25.99 € and so on. for optimizing purposes, I am looping through this column and removing the "EUR" mark and replacing "." with ",". While the code works, my problem is that it takes super long to execute and for thousands of products it sometimes time outs. I know I am probably not following the best practices, but this was the best solution I could come up with because of my limited JavaScript skills. Any help?
function myFunction() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName('Table');
var lastRow = sheet.getRange(1,1).getDataRegion(SpreadsheetApp.Dimension.ROWS).getLastRow();
for (var i = 1; i < lastRow +1; i++) {
var price = sheet.getRange(i,1).getValue();
var removeCur = price.toString().replace(" EUR","").replace(".",",");
sheet.getRange(i,1).setValue(removeCur);
}
}
It's a classic question. Classic answer -- you need to replace cell.getValue() with range.getValues(). To get this way 2D-array. Process the array with a loop (or map, etc). And then set all values of the array at once back on sheet with range.setValues()
https://developers.google.com/apps-script/guides/support/best-practices?hl=en
For this case it could be something like this:
function main() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName('Table');
var range = sheet.getDataRange();
var data = range.getValues(); // get a 2d array
// process the array (make changes in first column)
const changes = x => x.toString().replace(" EUR","").replace(".",",");
data = data.map(x => [changes(x[0])].concat(x.slice(1,)));
range.setValues(data); // set the 2d array back to the sheet
}
Just in case here is the same code with loop for:
function main() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName('Table');
var range = sheet.getDataRange();
var data = range.getValues();
for (var i=0; i<data.length; i++) {
data[i][0] = data[i][0].toString().replace(" EUR","").replace(".",",")
}
range.setValues(data);
}
Probably the loop for looks cleaner in this case than map.
And if you sure that all changes will be in column A you can make the script even faster if you change third line in the function this way:
var range = sheet.getRange("A1:A" + sheet.getLastRow());
It will narrow the range to one column.
Well, there's something you can do to improve your code, can't guarantee it will help you to make it faster, but we'll see.
Here's the updated version
function myFunction() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName('Table');
var lastRow = sheet.getRange(1,1).getDataRegion(SpreadsheetApp.Dimension.ROWS).getLastRow() + 1;
var price;
var removeCur;
for (var i = 1; i < lastRow; i++) {
price = sheet.getRange(i,1).getValue();
removeCur = price.toString().replace(" EUR","").replace(".",",");
sheet.getRange(i,1).setValue(removeCur);
}
}
What I did:
Line 5: I removed the +1 in the loop and added on lastRow directly. If you have 1000 rows, you'll save 1000 assignments
Line 6-7: removed declarations in the loop. If you have 1000 rows, you'll save 2000 re-declarations (not sure if it does, but it's best practise anyway)
You could use regex for the replace, so you do it only once, but I think it's slower, so I kept the 2 replaces there

Google Apps Script: Loop through a list

I have a deleteEachRow function that loops through a sheet and delete Rows that have a particular Column Value.
This works fine and was hoping to modify it in such a way that it loops through a multile sheets in the work-book and also delete rows based on multiple Column Values.
The deleteRow() script
//GLOBALS
var SS = SpreadsheetApp.openById("sheetID");
var SHEET = SS.getSheetByName("Sheet1");
var RANGE = SHEET.getDataRange();
var DELETE_VAL = "abc";
var COL_TO_SEARCH = 4; // The column to search for the DELETE_VAL (Zero is first)
function deleteEachRow(){
var rangeVals = RANGE.getValues();
//Reverse the 'for' loop.
for(var i = rangeVals.length-1; i >= 0; i--){
if(rangeVals[i][COL_TO_SEARCH] === DELETE_VAL){
SHEET.deleteRow(i+1);
};
};
};
What I have tried..
var SHEET = SS.getSheetByName(["Sheet1", "Sheet2"]);
var DELETE_VAL = ["abc","DEF"];
function deleteEachRow(){
var rangeVals = RANGE.getValues();
//Reverse the 'for' loop.
for(var i = rangeVals.length-1; i >= 0; i--){
for(var i=0; size = DELETE_VAL.length; i < size; i++){
if(rangeVals[i][COL_TO_SEARCH] === DELETE_VAL[i]){
for(var i=0; size = SHEET.length; i < size; i++){
SHEET[i].deleteRow(i+1);
};
};
};
};
};
Which completes executing from my logs, but does not actually work. I may have murdered some logic here, please pardon me, I am new to .gs/.js.
Thanks for your anticipated response.
Issue : You're passing array to getSheetByName, whereas as per documentation it accepts String only. i.e. Name of the single sheet you want to fetch.
https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet#getsheetbynamename
So you can modify your function to take sheet name as input and then delete rows in that sheet. Then call your function with desired sheet names. Something like this:
var spreadSheet = SpreadsheetApp.openById("sheetID");
var DELETE_VAL = "abc";
var COL_TO_SEARCH = 4; // The column to search for the DELETE_VAL (Zero is first)
function deleteEachRow(sheetName){
var SHEET = spreadSheet.getSheetByName(sheetName);
var RANGE = SHEET.getDataRange();
var rangeVals = RANGE.getValues();
// existing logic
};
// Invoke deleteEachRow() for each sheet you want to delete the rows
["Sheet1", "Sheet2"].forEach((sheetName) => deleteEachRow(sheetName));
Umair is right, there was a simply error in the first line. But I'd want to add that the sheet.deleteRow(row) is not the best practice in case if there are many rows to delete. This command is quite time consuming.
If you have more than dozen rows to delete it's better to grab all data from a sheet (or range) var data = range.getValues(), clear the sheet (or the range), to process the array inside the script and refill the sheet back with new data new_range.setValues(array). It will work much faster.

google-apps-script cannot read array value ("typeError: "cannot read property "1" ) [duplicate]

This question already has an answer here:
Freezing rows in Sheets with Google Apps throws type error
(1 answer)
Closed 2 years ago.
I'm trying to make my code sending an email by referring to my google sheet data. Im using Apps Script and here is the code. However, as I run my function "sendEmail()", I got "typeError: "cannot read property "1" of undefined(line 17)".
Code line 17
var currentEmail = rows[i][1];
Here is the full code.
var ss = "1kuTkOuCd-wKTS2564oHdxALFbFo-IeyjzToYYhB6NrQ";
var SheetName = "FormResp";
function getRows(){
var rangeName = 'FormResp!A2:E';
var rows = Sheets.Spreadsheets.Values.get(ss, rangeName).values;
return rows;
}
function sendEmails() {
var ss_1 = SpreadsheetApp.openById(ss);
var sheet = ss_1.getSheetByName(SheetName);
var lr = sheet.getLastRow();
for (var i = 0;i<=lr;i++){
rows=getRows();
var currentEmail = rows[i][1];
var startingdate = rows[i][3];
var endingdate = rows[i][4];
MailApp.sendEmail(currentEmail,"Thank You for Applying Leave via Leave form: your request leave starting" + startingdate + "until" + endingdate,"Hello");
}
}
function testgetrow(){
var nama = getRows();
var x = "";
}
I do make a test function "testgetrow()" to check my data, and I do manage to run the function without any error and I do confirm that there is values in my getRows() function.
my getRows() function working, and there is a value in the array as shown in the picture below.
I suppose you can do it any way you wish but this seems a lot simpler to me.
function sendEmails() {
const ss = SpreadsheetApp.openById("1kuTkOuCd-wKTS2564oHdxALFbFo-IeyjzToYYhB6NrQ");
const sh = ss.getSheetByName('FormResp');
const rg = sh.getRange(2,1,sh.getLastRow()-1,5);
const vs=rg.getValues();
vs.forEach(r=>{
var currentEmail = r[1];
var startingdate = r[3];
var endingdate = r[4];
MailApp.sendEmail(currentEmail,"Thank You for Applying Leave via Leave form: your request leave starting" + startingdate + "until" + endingdate,"Hello");
});
}
Answer
The main problem is that you have not declared properly the variable rows in your line 16 rows=getRows(); because you forgot to use the keyword var. Change that line with var rows = getRows() and try it again.
Take a look
You are mixing SpreadsheetApp with Sheets API. I recommend you to pick one and stay there to have a clear and less confusing code.
Try use less functions if they are not really necessary, as #Cooper suggested, you can define the variable rows with getRange that can handle A1 notation ranges and getValues.
Define properly your range in A1 notation: if you write A2:E, your range goes from column A, row 2 until column E, row 1000. You have to add the number of the last row in your range, for example A2:E10.
With the last change, you do not have to calculate the last row, you can simply use rows.length.
It is not necessary to have the id of the spreadsheet if your script is a Container-bound Scripts and not a Standalone Script with getActiveSpreadsheet
I attach you a snippet of the code:
var sheetName = "FormResp";
var spreadsheet_id = "1kuTkOuCd-wKTS2564oHdxALFbFo-IeyjzToYYhB6NrQ";
var ss = SpreadsheetApp.openById(spreadsheet_id).getSheetByName(sheetName)
function sendEmails() {
var rangeName = 'A1:B9';
var rows = ss.getRange(rangeName).getValues()
for (var i = 0; i <= rows.length; i++){
var currentEmail = rows[i][1];
var startingdate = rows[i][3];
var endingdate = rows[i][4];
MailApp.sendEmail(currentEmail,"Thank You for Applying Leave via Leave form: your request leave starting" + startingdate + "until" + endingdate,"Hello");
}
}
I hope that it helps you!
References
SpreadsheetApp
Sheets API
getRange
getValues
getActiveSpreadsheet
Container-bound Scripts
Standalone Script

Google Apps Script - Usage of "indexOf" method

first: I really tried hard to get along, but I am more a supporter than a programmer.
I put some Text in Google Calc and wanted to check the amount of the occurances of "Mueller, Klaus" (It appears 5 times within the data range). The sheet contains 941 rows and 1 Column ("A").
Here is my code to find out:
function countKlaus() {
// Aktives Spreadsheet auswählen
var ss = SpreadsheetApp.getActiveSpreadsheet();
// Aktives Tabellenblatt auswählen
var sheet = ss.getSheetByName("Tabellenblatt1");
var start = 1;
var end = sheet.getLastRow();
var data = sheet.getRange(start,1,end,1).getValues();
var curRow = start;
var cntKlaus = 0;
for( x in data )
{
var value = daten[x];
//ui.alert(value);
if(value.indexOf("Mueller, Klaus")> -1){
cntKlaus = cntKlaus + 1;
}
}
ui.alert(cntKlaus);
}
The result message is "0" but should be "5".
Issues:
You are very close to the solution, except for these two issues:
daten[x] should be replaced by data[x].
ui.alert(cntKlaus) should be replaced by SpreadsheetApp.getUi().alert(cntKlaus).
Solution (optimized by me) - Recommended:
function countKlaus() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("Tabellenblatt1");
const cntKlaus = sheet
.getRange('A1:A' + sheet.getLastRow())
.getValues()
.flat()
.filter(r=>r.includes("Mueller, Klaus"))
.length;
SpreadsheetApp.getUi().alert(cntKlaus);
}
You can leave out this term + sheet.getLastRow() since we are filtering on a non-blank value. But I think it will be faster to have less data to use filter on in the first place.
References:
flat : convert the 2D array to 1D array.
filter : filter only on "Mueller, Klaus".
Array.prototype.length: get the length of the filtered data
which is the desired result.
includes: check if Mueller, Klaus is included in the text.
Bonus info
Just for your information, my solution can be rewritten in one line of code if that's important to you:
SpreadsheetApp.getUi().alert(SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Tabellenblatt1").getRange('A1:A').getValues().flat().filter(r=>r.includes("Mueller, Klaus")).length);

javascript if function not working properly for index match (apps script)

I'm trying to check whether there are duplicate phone numbers in a column and to then return the first id number associated with the phone match in google apps script.
I created an if statement, according to every reference I've checked I've coded it right. The script runs but nothing populates??
I defined the ranges for the count that has the phone number and id number associated with it. I ensured its in the correct array my using map and used a basic if else statement.
Everything seems to run but the statement doesn't throw out the id numbers. In fact nothing happens.
Please help...
function myFunction() {
var sss = SpreadsheetApp.openById('spreadsheetid'); // sss = source spreadsheet
var ss = sss.getSheetByName('spreadsheetname'); // ss = source sheet
var lr = ss.getLastRow();
for (var i=3;i<lr;i++) {
var phone = ss.getRange(i,4,lr).getValues();
var id = ss.getRange(i,1,lr).getValues();
var phonearray = phone.map(function(r){return r[0]});
if(phonearray == phone[i]){
//emailarray.indexOf(email[i]) &&
ss.getRange(i,30,lr).setValue(id[i]);
} else
ss.getRange(i,30,lr).setValue("");
}
}
There are some things you need to consider
A valuerange is a 2-dimensional array, thus to access the value of an individual cell you need to define phone[i][0] instead of phone[i]
The method getRange requires the syntax getRange(startRow, startColumn, numRows) rather than getRange(startRow, startColumn, lastRow). Keep this in mind when you retrieve your range of interest.
Searching for duplicates will require in your case two (nested) for loops.
Please find below a sample of how you can modify your code to achieve the desired functionality:
function myFunction() {
var sss = SpreadsheetApp.openById('spreadsheetid'); // sss = source spreadsheet
var ss = sss.getSheetByName('spreadsheetname'); // ss = source sheet
var lr = ss.getLastRow();
var phones = ss.getRange(3,4,lr-3+1).getValues();
var ids = ss.getRange(3,1,lr-3+1).getValues();
var phonearray = phones.map(function(r){return r[0]});
for (var i=0;i<=(lr-3);i++) {
var val = phones[i][0];
var duplicates = [];
for(var j = 0; j < phonearray.length; j++){
if (phonearray[j] == val){
duplicates.push(j);
}
}
if(duplicates.length>1){
Logger.log("duplicate");
//emailarray.indexOf(email[i]) &&
ss.getRange((i+3),30).setValue(ids[i]);
} else{
Logger.log("no duplicate");
ss.getRange((i+3),30).setValue("");
}
}
}

Categories

Resources