First row of Google-Sheet is not being considered in JavaScript Loop - javascript

I am trying to send an automatic email on a reoccurring basis. My Google-App-script is working fine with the exception that it does not email the 1st email, i.e. first row. It starts the looping process of emails from the second row on. How do I tweak this issue in my code?
function sendemail() {
var spreadSheet = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var secondSheet = spreadSheet[1];
var dataRange = secondSheet.getDataRange();
var data = dataRange.getValues();
for (var i = 1; i < data.length; i++) {
(function(val) {
var row = data[i];
var emailAddress = row[0];
var message = 'Test Email';
MailApp.sendEmail(emailAddress, message);
})(i);
}
}
It only emails 'email2' and 'email3' but not to 'email1' in this case. How do I get it to send emails to all emails?

Your for-loop should start with zero. When you start with 1, you skip the first email in the array.
for (var i = 0; i < data.length; i++) { ... }
From Mozilla:
JavaScript arrays are zero-indexed. The first element of an array is at index 0, and the last element is at the index value equal to the value of the array's length property minus 1.
getValues() returns an array, so you need to be careful to use the correct index. This is different from the range indexes used by Google, but is noted in the documentation.
Remember that while a range index starts at 1, 1, the JavaScript array is indexed from [0][0].

Related

Return an array of (edited) matched items of two other arrays and (unedited) non-matched items

My goal
I'm trying to link rows in two different sheets in the same spreadsheet according to a value in a cell. The basic idea is that if a value in column B in Sheet1 has a matched value in column B in Sheet2, an hyperlink should be added to the cell with the matched value in Sheet1 linking to the whole row of the matched value in the Sheet2.
What I did
As you can see from the code below, it looks for the matches, if it's found, it edits the matched values to add the hyperlinks. I don't only want to push the "linked" matched values into the new array, I also want the non-matched values without the link. The idea is that the link will be added if a match is found, otherwise the value will still be added, just without the link.
function linkToContacts(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var Sheet1 = ss.getSheetByName("Sheet1");
var Sheet2 = ss.getSheetByName("Sheet2");
var Sheet2ID = Sheet2.getSheetId();
var arrSheet1 = Sheet1.getRange(4,2,Sheet1.getLastRow()-3).getValues(); // -3 because I have 3 row as headers before the data, which start in row 4 col B
var arrSheet2 = Sheet2.getRange(4,2,Sheet2.getLastRow()-3).getValues(); // -3 because I have 3 row as headers before the data, which start in row 4 col B
var arrOutput = [];
for(var i = 0; i<arrSheet1.length;i++){
for(var j = 0; j<arrSheet2.length;j++) {
if(arrSheet1[i][0] === arrSheet2[j][0]){
arrOutput.push(['=HYPERLINK("#gid=' + Sheet2ID + '&range=' + Sheet2.getRange(j+4,2,1,Sheet2.getLastColumn()-1).getA1Notation() + '";"' + arrSheet1[i][0] + '")']);
} else {
arrOutput.push([arrSheet1[i][0]]);
}
}
}
Sheet1.getRange(4,2,Sheet1.getLastRow()).clearContent();
Sheet1.getRange(4,2,arrOutput.length).setValues(arrOutput);
}
The problem
The two arrays contain only unique values respectively. The problem is that, because of the double loop, each item is checked by the length of arrSheet2. So for instance let's imagine this scenario:
var arrSheet1 = [apple,avocado,banana];
var arrSheet2 = [apple,banana,mango,];
the arrOutput will result in:
arrOutput = [
apple(link),apple,apple,
avocado,avocado,avocado,
banana(link),banana,banana
];
In a quick, probably unelegant, way to solve the issue I've tried to delete the duplicates frm arrOutput but obviously the value with the link and the values without are different so the best it can get with this solution is this:
arrOutput = [
apple(link),apple,
avocado,
banana(link),banana
];
Question
Is there a smarter/more efficient way to get to
arrOutput = [apple(link),avocado,banana(link)];
or in case what I'm doing actually makes sense, what I should do to get to the result above?
You could use Array.prototype.filter to create a new array that contains only matching elements. If no elements are found this will return an empty array.
var arrSheet1 = ['apple','avocado','banana'];
var arrSheet2 = ['apple','banana','mango'];
var intersect = arrSheet1.filter(function (element) {
return arrSheet2.includes(element);
});
// ['apple','banana']
Or without an Array.prototype.includes polyfill:
var intersect = arrSheet1.filter(function (element) {
return arrSheet2.indexOf(element) !== -1);
})
Issue:
Double looping and pushing array elements multiple times
Solution:
Use break and conditional if statements to control logic
Flow:
If hyperlink is pushed, break the second sheet loop
Only push Sheet1 element if there's no hyperlinks in Sheet2(i.e., Wait till the last element of Sheet2 is iterated)
Snippet:
for(var i = 0; i<arrSheet1.length;i++){
for(var j = 0, k = arrSheet2.length-1; j<=k; j++) {// modified
if(arrSheet1[i][0] === arrSheet2[j][0]){
arrOutput.push(['=HYPERLINK("#gid=' + Sheet2ID + '&range=' + Sheet2.getRange(j+4,2,1,Sheet2.getLastColumn()-1).getA1Notation() + '";"' + arrSheet1[i][0] + '")']);
break; //break j loop to continue next i loop
} else if(j === k){//push sheet1 only on the last element
arrOutput.push([arrSheet1[i][0]]);
}
}
}
Note:
Use of objects {} might be better. Convert sheet2 array to object {val1:hash,val2:hash,...}. Then you can easily check if sheet1 elements are present in sheet2 using in

Comparing two columns, same row, for first value that doesn't match

I have two sheets, one is a mirror sheet, "Sheet2," that I use to store the values of the other sheet, "Sheet1." My goal is to have a function compare the two sheets for differences. The best way i could think of was by comparing column A from Sheet1 to column A from Sheet2. I found a few functions that compared 2 columns but it did it looking for values from one column and finding it in the other column. Or by returning all the values in those cells that had a matching value, regardless of what row it was in. But I don't want the values in the cells, necessarily. I want to find the first row where the two columns stop matching. I'm fairly new to Javascript so I still can't comprehend the whole for (var j = 0; j < range.length; j++) stuff.
But I'm sure I will need to know how to use it for this function I need. Here's what I tried using but instead of giving me row ranges, it gave me an array of values that were the same, if I changed it to if(lookup[i][0]!==range[j][0]){ it gave me all the possible combinations that weren't matching. This is from stackoverflow.com/questions/42044903
function findDifference() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s=ss.getSheetByName("Sheet1")
var lr=s.getLastRow()
var lookup = s.getRange(2,1,lr-1,2).getValues();
var s1=ss.getSheetByName("Sheet2")
var lr1=s1.getLastRow()
var range = s1.getRange(2,1,lr1-1,2).getValues();
var lookupRange = [];
for (var i = 0; i < lookup.length; i++) {
for (var j = 0; j < range.length; j++) {
var test=lookup[i][0]
if(lookup[i][0]!==range[j][0]){
lookupRange.push([range[j][0],range[j][1],lookup[i][0],lookup[i][1],]);
}}}
s1.getRange(10,1,lookupRange.length,4).setValues(lookupRange);
}
I feel like there's a very similar function for what I'm trying to do that already exists, but I can't seem to find it or come up with how it would work because I'm new and don't know all the tricks.
Something like:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s1=ss.getSheetByName("Sheet1")
var s2=ss.getSheetByName("Sheet2")
var ColumnA1 = s1.getRange('A:A').getValues()
var ColumnA2 = s2.getRange('A:A').getValues()
var Row = Function()
///Some function I can't think of using where
if(ColumnA1 + Row !== ColumnA2 + Row){
???.getRow()
}
The code that you had was "kinda' helpful but it did not solve your particular question. On the other hand, your if(ColumnA1 + Row !== ColumnA2 + Row){ wasn't really helpful either.
Regrettably you DO need to "comprehend the whole for (var j = 0; j < range.length; j++) stuff", though it isn't actually that complicated.
In the following answer, there are basically three elements.
setup sheet1, and get the data
setup sheet2, and get the data
loop through the rows and compare the value on a given line from one sheet to the other.
the for statement signifies the loop
i is simply a counter variable
i=0 means that the starting value is zero. In javascript arrays, zero always the first value set.
i < Sheet1Data.length signifies how many time the loop will run. In this case, it will run while i is less then the number of lines in the array. Remember, i starts with zero, so "less than" the totoal number of lines will be fine.
i++ means that each time the code loops, it increments i by one.. So, i starts with 0, then 1, 2, 3 and so on.
How to find the first row where the two columns stop matching
View the Logs (View > Logs).
You can see on line 32 and 38 of the code Logger.log statements. These record the line number and whether the line values in each sheet match.
function so56195933() {
// setup Spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
// setup Sheet1
var s1 = ss.getSheetByName("Sheet1")
var s1LastRow = s1.getLastRow();
//Logger.log("DEBUG: Sheet 1 Last row = "+s1LastRow);
var Sheet1DataRange = s1.getRange(1,1,s1LastRow);
var Sheet1Data = Sheet1DataRange.getValues();
//Logger.log("DEBUG: Sheet 1 data range = "+Sheet1DataRange.getA1Notation());
var Sheet1length = Sheet1Data.length;
//Logger.log("DEBUG: Sheet1 length = "+Sheet1length);
// setup Sheet2
var s2=ss.getSheetByName("Sheet2")
var s2LastRow=s2.getLastRow();
//Logger.log("DEBUG: Sheet 2 Last row = "+s2LastRow);
var Sheet2DataRange = s2.getRange(1,1,s2LastRow);
var Sheet2Data = Sheet2DataRange.getValues();
//Logger.log("DEBUG: Sheet 2 data range = "+Sheet2DataRange.getA1Notation());
var Sheet2length = Sheet2Data.length;
//Logger.log("DEBUG: Sheet2 length = "+Sheet2length);
// Loop through rows compare value per each sheet
for (var i = 0; i < Sheet1Data.length; i++) {
var s1data = Sheet1Data[i][0];
var s2data = Sheet2Data[i][0];
//Logger.log("DEBUG: Line: "+i+", s1data: "+s1data+" Vs s2data: "+s2data);
if (s1data !=s2data){
// sheets values don't balance
Logger.log("Line: "+i+". Sheets are NOT equal. Sheet1 = "+s1data+", Sheet2 = "+s2data);
return false;
}
else
{
// sheets values balance
Logger.log("Line: "+i+". Sheets are equal, value: "+s1data);
}
}
}
This is my test data

Apps Script doesn't compare 2 values in if-statement

I've created a new project that should compare a name from Sheet1 with a list of names in Sheet2 and check if the name is already in that list. For that I chose a for-loop to get through the list in Sheet2 and compare every list entry with the name from Sheet1. Only if the name already exists in the list stuff should happen.
function myFunction() {
var tabSheet1 = 'Sheet1';
var tabSheet2 = 'Sheet2';
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName(tabSheet1);
var sheet2 = ss.getSheetByName(tabSheet2);
var lastRow1 = sheet2.getLastRow() + 1;
var playerNameSheet1 = sheet1.getRange(1, 1).getValue();
for (var j = 1; j < lastRow1; j++) {
var playerNameSheet2 = sheet2.getRange(j, 1).getValue();
if (playerNameSheet2 == playerNameSheet1) {
...stuff...
}
}
}
Now my problem is that it seems like the script isn't able to identify that a name already exists in the list. Both values (playerNameSheet1 and playerNameSheet2) are completely identical (no space or other hidden obstacles), however the script would never continue with stuff in the if-statement. My example name to test my script was "Oliver Baumann".
I'm a bit confused about it - even more, because another comparison a bit later in the script code works just fine.
I've already tried to change the operator into === but that wouldn't work either.
if (playerNameSheet2 === playerNameSheet1) {
...stuff...
}
I've also observed that if I put a dot behind both variables I'm only able to choose further functions with playerNameSheet2, but not with playerNameSheet1. Maybe I did a typing error and am just too blind to see it? I don't know. Anyone an idea how to resolve the issue?
The complete project can be found here. However, a lot of stuff is in german and very rudimental. I just started it and haven't got time to clean it up. Just so you don't wonder.
You will likely benefit from a change to your inspection routine - currently what you have is not scalable due to the slow, repeated calls to the Spreadsheet Service. Use a batch method - getValues() - to return a Javascript Array that contains all the content you could want from your 'master list' of names:
// Create an N x 1 array of arrays, e.g. [ [r1c1], [r2c1], [r3c1], ... [rNc1] ],
// of data in column A in sheet2. There will be blanks at the end if other columns have more data.
var allNames = sheet2.getRange(1, 1, sheet2.getLastRow(), 1).getValues();
To check if the name from the first sheet is present, we can replace this code:
for (var j = 1; j < lastRow1; j++) {
var playerNameSheet2 = sheet2.getRange(j, 1).getValue();
if (playerNameSheet2 == playerNameSheet1) {
/* do stuff */
with this code (note j now starts at 0):
for (var j = 0; j < allNames.length; ++j) {
if (playerNameSheet1 === allNames[j][0]) {
/* do stuff */
If you only need to do stuff on a name once in the function call (e.g. you don't need to execute the loop body twenty times when the sheet 1 name is "Bob" and there are twenty instances of "Bob" on sheet 2), you can simplify checking allNames for a value with the Array#indexOf method. First, one must collapse the "2D" array of arrays of values into an array of values. We want to apply a function to every element of the outer array and construct an array of its outputs, so we choose to call Array#map on it:
var db = allNames.map(function (row) { return row[0]; });
The function we use simply returns the first element of the passed element - i.e. the value in the first column, resulting in an output like [ r1c1, r2c1, r3c1, ... rNc1 ].
The replacement code is then:
if (db.indexOf(playerNameSheet1) === -1) {
console.log({
message: "Did not find '" + playerNameSheet1 + "' in database.",
database: db, original: allNames, searched: playerNameSheet1
});
return;
}
/* do stuff */
Which says "if the name is not on sheet 2, log the failed lookup and then quit running the function." To promote actual logging, the log is sent to Stackdriver, which will keep it for much longer than the native Logger class would.
If your do stuff bits use the j index, you can still obtain that index and use the associated row in sheet 2:
var index = db.indexOf(playerNameSheet1);
if (index === -1) {
console.log({
message: "Did not find '" + playerNameSheet1 + "' in database.",
database: db, original: allNames, searched: playerNameSheet1
});
return;
}
/* do stuff with the user's existing row of data, e.g.
var userDataRow = sheet2.getRange(index + 1, 1, 1, sheet2.getLastColumn()).getValues();
var userData = userDataRow[0];
...
*/
A possible improvement to the indexOf modification, which I leave for you to investigate and/or implement, would be to use an Object to hold the names as "keys" (object properties) and the index of the associated sheet data (or even the data directly) as the associated value of the key-value pair.
you can try to convert data in array and compare in for-loop:
var dataRangeSpieler = sheetSpieler.getDataRange().getValues();
var dataRangeDBSpiele = sheetDBSpieler.getDataRange().getValues();
for (i in dataRangeSpieler ) {
for (j in dataRangeDBSpiele) {
if (dataRangeSpieler[i][1] == dataRangeDBSpiele[j][0]) {
Logger.log(dataRangeSpieler[i][1]); //Oliver Baumann
}
}
}

Based on the number in one cell, I want to use Google Script Editor send an email with the name from the adjacent cell

I want to send an email when a cell in column B reaches 5. However, I want part of the email to have the individual's name from column A. Here's my code so far:
function ifstatement() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Total");
var value = sheet.getRange("B3:B241").getValue();
if(value === 5) {
MailApp.sendEmail("jdoe#gmail.com", "Update", "Hi," name "are you alright?");
}
}
I'm trying to define the variable "name" so that it adds the person's name from column A. Could anyone please help me!
The ideal way to get values from different ranges in a sheet is to pull all the data from the sheet as a single 2D array (A table essentially), and work through that to determine what you want to do. This avoids unnecessary complexity, and also ensures that your execution time stays low since you don't need to call the sheet.getRange() service multiple times.
On String concatenation: your string concatenation would not work. You need + between the strings. Go from "Hi," name "are you alright?" to "Hi, "+ name +" are you alright?".
Here is an example solution for you:
Using this example data:
Note: You don't need to know how the columns bit works, just how to use it, think of it as a small service to make life easier if you ever decide to add, or rearrange the spreadsheet's columns.
/*
* Run this to check the sheets values
* This is more verbose to aid with understanding
*/
function checkSheet() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Total');
var range = sheet.getDataRange();
var values = range.getValues();
var columns = getColumnHeaders(values);
//Loop through all the rows in the sheet and check if the 'Number' column is 5
for(var i = 0; i < values.length; i++){
var number = values[i][columns['Number']]; //Get the number from the table of values, utalizing the columns object to get the index
if(number === 5){
var name = values[i][columns['Name']];
var email = values[i][columns['Email']];
Logger.log(name);
Logger.log(email);
//MailApp.sendEmail(email, "Update", "Hi, "+ name +" are you alright?");
}
}
}
/*
* Generates a columns object that lets you reference columns by name instead of index
* Can be used like column['headerText'] to get the index of a column
* Is just a dynamic convenience to avoid referencing columns by index
*/
function getColumnHeaders(array){
var columns = {};
for(var i = 0; i < array[0].length; i++){
columns[array[0][i]] = i;
}
return columns;
}
Condensed checkSheet():
//Condensed version
function checkSheet2() {
var values = SpreadsheetApp.getActive().getSheetByName('Total').getDataRange().getValues();
var columns = getColumnHeaders(values);
for(var i = 0; i < values.length; i++){
if(Number(values[i][columns['Number']]) === 5){
//MailApp.sendEmail(values[i][columns['Email']], "Update", "Hi, "+ values[i][columns['Name']] +" are you alright?");
}
}
}
At this line:
var value = sheet.getRange("B3:B241").getValue();
You're using the method getValue() and it only returns the value of the top-left cell of the range, and you need to to get the values of the whole column A an B, so first set the range to A3:B241 then use the method getValues() to get the values as a two-dimensional array. The line should look like this:
var values = sheet.getRange("A3:B241").getValues();
Once you have the Array you need to loop through the values and check if the element at the index 1 values[i][1] is equal to 5. The line should look like this:
for (var i = 0; i < values.length; i++) {
if(values[i][1] === 5){
// Block of code to be executed if the condition is true
}
}
Finally, the configuration of paramaters you're using for the sendEmail() method is: (recipient, subject, body) the body of the message needs to be a String, you need to concatenate the "Hi,", the name that is in the index 1 values[i][1] and "are you alright?", to achieve that you need to use the the concatenation operator (+), the line should look like this:
MailApp.sendEmail("jdoe#gmail.com", "Update", "Hi," + values[i][0] + " are you alright?");
The complete code:
function ifstatement() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Total");
var values = sheet.getRange("A3:B241").getValues();
for (var i = 0; i < values.length; i++) {
if(values[i][1] === 5){
MailApp.sendEmail("jdoe#gmail.com", "Update", "Hi," + values[i][0] + " are you alright?");
}
}

JavaScript: If statement not working inside Loop

I'm writing the following code (a test as of now) using Google Scripts to pass data from one spreadsheet to another. The passing of the code is working just fine, however my second For loop – which I intend to use to detect duplicate values and avoid passing those rows over – is not working.
Checking the logs I see that even though the "i" and "j" values are correctly being passed inside the If block, the "if(sheetsIDHome[i] == sheetsIDTarget[j])" statement is never triggering, even when I confirm that both values are the same.
Any help would be greatly appreciated, thank you in advance!
function move(){
var homeBook = SpreadsheetApp.getActiveSpreadsheet();
var sheet = homeBook.getSheets()[0];//Sheet where my Home data is stored
var limit = sheet.getLastRow(); //number of rows with content in them
var evento = sheet.getRange(2, 1, limit-1).getValues(); //Even titles array
var descript = sheet.getRange(2,2,limit-1).getValues(); //Event Descriptions array
var tags = sheet.getRange(2,3,limit-1).getValues(); //Tags array
var sheetsIDHome = sheet.getRange(2,4,limit-1).getValues(); //ID's array
var targetBook = SpreadsheetApp.openById("1t3qMTu2opYffLmFfTuIbV6BrwsDe9iLHZJ_ZT89kHr8"); // Traget Workbook
var target = targetBook.getSheets()[0]; //Sheet1, this is my Target sheet
if (target.getLastRow() > 1){
var sheetsIDTarget = target.getRange(2, 4,target.getLastRow()-1).getValues();}
else{
var sheetsIDTarget = target.getRange(2, 4, 1).getValues();}
var targetRow = target.getLastRow()+1; //Target row to start pasting content
for (var i = 0; i < evento.length; i++) { //Loops throught every value from my Home sheet in order to pass it to my Target Sheet
var isKlar = 1; //This works as a switch, data passing will not activate if isKlar set to 0
Logger.log("Switch is: "+isKlar);
for(var j = 0; j < sheetsIDTarget.length; j++){ //While having a certain "i" value in place, will loop though all my values in my target array using the counter "j"
if(sheetsIDHome[i] == sheetsIDTarget[j]){ //If the ID of my curent row from Home matches any of the values in my target sheet, my "isKlar" switch should turn off and the break loop will be exited.
Logger.log("If Activated");
isKlar = 0;
break;}
else{Logger.log("ID's: "+sheetsIDHome[i] + " vs " + sheetsIDTarget[j]);}
}
if(isKlar === 1){ //data passing will not activate if isKlar set to 0
//pass data to the Target sheet
target.getRange(targetRow,1).setValue(evento[i]);
target.getRange(targetRow,2).setValue(descript[i]);
target.getRange(targetRow,3).setValue(tags[i]);
target.getRange(targetRow,4).setValue(sheetsIDHome[i]);
targetRow++; //select the next available row in ny Target sheet
}
}
}
Edit. - Right now I'm testing both ID arrays with the same numbers (e.g. 1, 2, 3, 4). The log inside my else statement does show the correct values being read for both arrays... I thought it was a scope issue, but now I'm not sure where the problem is.
the issue is a sheet range.getValues() returns an array of arrays, not an array of values.
values[0] is the first row, and values[0][0] is the first value in that first row. rework your code knowing this.

Categories